From 452c57692727193dd722d3670ad3d4e809ebe065 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 2 Mar 2026 10:45:43 +0100 Subject: [PATCH 0001/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] =?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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] =?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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] =?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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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/2155] 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 0d02d0bc51f2fd431f81daa39a970d5dea279f29 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 22 Apr 2026 17:19:21 +0100 Subject: [PATCH 1172/2155] sysupdate: Prevent a possible invalid partial+pending state on an instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the resource code is recursing, it’s possible for one iteration to set a partial flag, and then a recursive iteration to set a pending flag (or vice-versa). It doesn’t make sense to have both set at the same time for a specific instance, so make sure to clear the other flag when setting one of them. Add some assertions to make this invariant clearer and easier to debug if it fails. Signed-off-by: Philip Withnall --- src/sysupdate/sysupdate-resource.c | 8 ++++++++ src/sysupdate/sysupdate.c | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index acd9604f2945d..bd1560371d42e 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -139,9 +139,11 @@ static int resource_load_from_directory_recursive( if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) { de_d_name_stripped = stripped; is_partial = true; + is_pending = false; } else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) { de_d_name_stripped = stripped; is_pending = true; + is_partial = false; } else de_d_name_stripped = de->d_name; @@ -192,6 +194,9 @@ static int resource_load_from_directory_recursive( if (instance->metadata.mode == MODE_INVALID) instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */ + /* Can’t be both partial and pending. */ + assert(!(is_partial && is_pending)); + instance->is_partial = is_partial; instance->is_pending = is_pending; } @@ -313,6 +318,9 @@ static int resource_load_from_blockdev(Resource *rr) { if (instance->metadata.read_only < 0) instance->metadata.read_only = instance->partition_info.read_only; + /* Can’t be both partial and pending. */ + assert(!(is_partial && is_pending)); + instance->is_partial = is_partial; instance->is_pending = is_pending; } diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 76b6507f9a438..2dd1bfdaac38e 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -410,7 +410,10 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags extra_flags |= UPDATE_PROTECTED; /* Partial or pending updates by definition are not incomplete, they’re - * partial/pending instead */ + * partial/pending instead. While an individual Instance cannot be both partial and + * pending, an UpdateSet as a whole can contain both partial and pending instances. */ + assert(!match || !(match->is_partial && match->is_pending)); + if (match && match->is_partial) extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE; From fcff95831909f0ff9eb8be6ac61c411bc09fa566 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 8 Apr 2026 14:31:00 +0000 Subject: [PATCH 1173/2155] 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 1174/2155] 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 1175/2155] 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 1176/2155] 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 1177/2155] 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 1178/2155] 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 1179/2155] 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 1180/2155] 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 1181/2155] 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 1182/2155] 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 1183/2155] 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 1184/2155] 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 1185/2155] 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 1186/2155] 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 1187/2155] 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 1188/2155] 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 1189/2155] 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 1190/2155] 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 1191/2155] 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 1192/2155] 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 1193/2155] 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 1194/2155] 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 1195/2155] 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 1196/2155] 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 1197/2155] 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 1198/2155] 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 1199/2155] 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 1200/2155] 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 1201/2155] 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 1202/2155] 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 1203/2155] 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 1204/2155] 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 1205/2155] 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 1206/2155] 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 1207/2155] 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 1208/2155] 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 1209/2155] 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 1210/2155] 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 1211/2155] 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 1212/2155] 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 1213/2155] 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 1214/2155] 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 1215/2155] 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 1216/2155] 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 1217/2155] 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 1218/2155] 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 1219/2155] 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 1220/2155] 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 1221/2155] 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 1222/2155] 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 1223/2155] 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 1224/2155] 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 1225/2155] 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 1226/2155] 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 1227/2155] 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 1228/2155] 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 1229/2155] 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 1230/2155] 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 1231/2155] 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 1232/2155] 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 1233/2155] 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 1234/2155] 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 1235/2155] 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 1236/2155] 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 1237/2155] 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 1238/2155] 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 1239/2155] 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 1240/2155] 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 1241/2155] 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 1242/2155] 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 1243/2155] 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 1244/2155] 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 1245/2155] 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 1246/2155] 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 1247/2155] 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 1248/2155] 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 1249/2155] 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 1250/2155] 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 1251/2155] 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 1252/2155] 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 1253/2155] 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 1254/2155] 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 1255/2155] 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 1256/2155] 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 1257/2155] 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 1258/2155] 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 1259/2155] 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 1260/2155] 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 1261/2155] 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 1262/2155] 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 1263/2155] 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 1264/2155] 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 1265/2155] 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 1266/2155] 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 b2d19b4651fb87b8f6dc8a427a96d7a9d3a16961 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 22 Apr 2026 17:23:49 +0100 Subject: [PATCH 1267/2155] sysupdate: Allow partial+pending flags in a few more places for UpdateSets While a resource Instance can either be partial or pending, but not both; an UpdateSet (which potentially comprises several Instances) can be both partial *and* pending if it contains Instances in both those states. Amend a few bits of internal code to allow that in situations which were previously overlooked. Signed-off-by: Philip Withnall --- src/sysupdate/sysupdate-update-set-flags.c | 5 +++++ src/sysupdate/sysupdate.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sysupdate/sysupdate-update-set-flags.c b/src/sysupdate/sysupdate-update-set-flags.c index 36801938f65f1..7b684576b0614 100644 --- a/src/sysupdate/sysupdate-update-set-flags.c +++ b/src/sysupdate/sysupdate-update-set-flags.c @@ -70,6 +70,11 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) { case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED: case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST: case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED: + /* can also contain pending instances: */ + case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST: + case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED: + case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST: + case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED: return "current+partial"; case UPDATE_AVAILABLE|UPDATE_NEWEST: diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 2dd1bfdaac38e..89efe36c857f0 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -669,7 +669,7 @@ static int context_show_version(Context *c, const char *version) { Instance *i = *inst; if (!i) { - assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE)); + assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING)); continue; } From 2babac90137ea4b6a70958133c783131d2619051 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 22 Apr 2026 17:25:44 +0100 Subject: [PATCH 1268/2155] sysupdate: Allow a partial version to be the candidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we allowed a pending version to be the candidate — but if there are no better choices, then we might as well allow a partial version to be candidate as well. The alternative is having no update candidate when a new version is partially installed (i.e. downloaded but not moved into place). This would mean that an update which is interrupted then needs to be re-run with an explicit version number to progress, rather than being able to be re-run without a version number (as it was in the first place). Signed-off-by: Philip Withnall Helps: https://github.com/systemd/systemd/issues/41502 --- src/sysupdate/sysupdate.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 89efe36c857f0..cba7960f0be67 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -505,8 +505,9 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0) c->candidate = NULL; - /* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */ - if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) && + /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */ + if (c->newest_installed && + (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) && !c->candidate) c->candidate = c->newest_installed; From e1146fe710d0d1bbe0fdca974f66edd7f6c573cc Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 22 Apr 2026 17:28:31 +0100 Subject: [PATCH 1269/2155] sysupdate: Allow a partial version to be vacuumed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we prevented partial and pending versions from being vacuumed. But until we support resuming downloads, there’s nothing else which can be done with a partial version except to vacuum it and try again. Accordingly, allow partial versions (but not pending versions) to be vacuumed. This behaviour can be changed again once resuming downloads is supported — at that point I expect we’ll want to try resuming the partial download rather than throwing it all away and trying again. Signed-off-by: Philip Withnall Helps: https://github.com/systemd/systemd/issues/41502 --- src/sysupdate/sysupdate-transfer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 3ed7a2ae3a488..8db1c81962f70 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -816,10 +816,13 @@ int transfer_vacuum( continue; } - /* If this is listed among the protected versions, then let's not remove it */ - if (strv_contains(t->protected_versions, instance->metadata.version) || - (extra_protected_version && streq(extra_protected_version, instance->metadata.version))) { - log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version); + /* If this is pending and listed among the protected versions, then let's not remove it. + * In future, we will also want to keep partial protected versions, but that’s only useful + * once we support resuming downloads. */ + if (instance->is_pending && + (strv_contains(t->protected_versions, instance->metadata.version) || + (extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) { + log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version); i++; continue; } From 66b950cd3f47f49087ddd4a2f4812e82b209f2b7 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 22 Apr 2026 17:31:27 +0100 Subject: [PATCH 1270/2155] updatectl: Show a helpful error if an update is partially downloaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If an update is partially downloaded and the user tries to update again, `updatectl` can’t currently do anything (it doesn’t yet support resuming downloads). At the moment, though, it’ll return success as if the system was up to date, even though it isn’t up to date. Instead, print a more helpful error message telling the user to try vacuuming the partial version and trying again. I decided not to make it automatically vacuum the partial version, as that seems like a way to get into a nasty retry loop if, for example, the checksum provided by the server doesn’t match that of the downloaded file (which is one way to trigger this code path). Add an integration test which simulates this failure by corrupting the `SHA256SUMS` file, trying to download an update, and then working through the recovery steps. Signed-off-by: Philip Withnall Fixes: https://github.com/systemd/systemd/issues/41502 --- src/sysupdate/sysupdate.c | 4 +-- src/sysupdate/updatectl.c | 4 +++ test/units/TEST-72-SYSUPDATE.sh | 50 +++++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index cba7960f0be67..4d083db220b1e 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1029,9 +1029,7 @@ static int context_acquire( if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE)) log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version); else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) { - log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version); - - return 0; + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version); } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) { log_info("Selected update '%s' is already acquired and pending installation.", us->version); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index c6e5c33fdfe48..16e9d21ae91b4 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -867,6 +867,10 @@ static int update_render_progress(sd_event_source *source, void *userdata) { clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK()); n--; /* Don't consider this target in the total */ + } else if (progress == -EUCLEAN) { + clear_progress_bar_unbuffered(target); + fprintf(stderr, "%s: %s Update is already acquired and partially installed. Vacuum it to try installing again.\n", target, RED_CROSS_MARK()); + total += 100; } else if (progress < 0) { clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress)); diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 27268c250b5e6..6709cd543f926 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -66,6 +66,7 @@ update_checksums_with_best_before() { new_version() { local sector_size="${1:?}" local version="${2:?}" + local corrupt="${3:-}" # Create a pair of random partition payloads, and compress one. # To make not the initial bytes of part1-xxx.raw accidentally match one of the compression header, @@ -90,11 +91,26 @@ new_version() { echo $RANDOM >"$WORKDIR/source/dir-$version/bar.txt" tar --numeric-owner -C "$WORKDIR/source/dir-$version/" -czf "$WORKDIR/source/dir-$version.tar.gz" . - update_checksums + if [[ "$corrupt" == "corrupt-checksum" ]]; then + # As requested, add a deliberately corrupt checksum for this file. This + # will get overwritten next time update_checksums() is called, but the + # integration test will probably have moved on to other things by then. + { + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part1-$version.raw" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw.gz" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-$version.efi" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-extra-$version.efi" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea dir-$version.tar.gz" + } >> "$WORKDIR/source/SHA256SUMS" + else + update_checksums + fi } update_now() { local update_type="${1:?}" + local checks="${2:-}" # Update to newest version. First there should be an update ready, then we # do the update, and then there should not be any ready anymore @@ -105,7 +121,10 @@ update_now() { # modes. Some updates in the test suite need to be monolithic (e.g. when # repairing an installation), so that can be overridden via the local. - "$SYSUPDATE" --verify=no check-new + if [[ "$checks" != "no-checks" ]]; then + "$SYSUPDATE" --verify=no check-new + fi + if [[ "$update_type" == "monolithic" ]]; then "$SYSUPDATE" --verify=no update elif [[ "$update_type" == "split-offline" ]]; then @@ -125,7 +144,10 @@ update_now() { else exit 1 fi - (! "$SYSUPDATE" --verify=no check-new) + + if [[ "$checks" != "no-checks" ]]; then + (! "$SYSUPDATE" --verify=no check-new) + fi } verify_version() { @@ -462,6 +484,28 @@ EOF verify_version_current "$blockdev" "$sector_size" v8 1 verify_version "$blockdev" "$sector_size" v7 2 + # Create a 9th version but corrupt the checksum in SHA256SUMS so pulling it + # fails when verifying the checksum, in order to create a current+partial + # state. Try to update again and verify that this results in an error. + # Vacuum the partial version, regenerate it on the server, try updating + # again and it should succeed. + new_version "$sector_size" v9 "corrupt-checksum" + (! update_now "$update_type") + "$SYSUPDATE" --offline list v9 | grep "partial" >/dev/null + verify_version_current "$blockdev" "$sector_size" v8 1 + # don’t verify the other part of the block device as it’s in an indeterminate state + (! update_now "$update_type" "no-checks") |& tee "$WORKDIR"/update_now-9 + cat "$WORKDIR"/update_now-9 + grep "is already acquired and partially installed. Vacuum it to try installing again." "$WORKDIR"/update_now-9 + "$SYSUPDATE" --offline vacuum |& grep "Removing old partial" >/dev/null + verify_version_current "$blockdev" "$sector_size" v8 1 + # don’t verify the other part of the block device as it’s in an indeterminate state + "$SYSUPDATE" --verify=no list v9 | grep "candidate" >/dev/null + new_version "$sector_size" v9 + update_now "$update_type" + verify_version "$blockdev" "$sector_size" v8 1 + verify_version_current "$blockdev" "$sector_size" v9 2 + # Cleanup [[ -b "$blockdev" ]] && losetup --detach "$blockdev" rm "$BACKING_FILE" From 143126cbcd1511dec8e241ca47f48bdf20a0f3dd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 15:19:06 +0200 Subject: [PATCH 1271/2155] 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 1272/2155] 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 1273/2155] 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 1274/2155] 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 1275/2155] 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 1276/2155] 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 1277/2155] 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 1278/2155] 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 1279/2155] 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 a8400c8f1a61223e4905e2939f8d71be82831c8c Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 23 Apr 2026 13:44:59 +0200 Subject: [PATCH 1280/2155] test: wrap even more binaries when running with sanitizers Turns out that the util-linux dep on libsystemd caused more fun than I originally anticipated: $ lddtree /usr/bin/dfuzzer dfuzzer => /usr/bin/dfuzzer (interpreter => /lib64/ld-linux-x86-64.so.2) libgio-2.0.so.0 => /lib64/libgio-2.0.so.0 libgmodule-2.0.so.0 => /lib64/libgmodule-2.0.so.0 libz.so.1 => /lib64/libz.so.1 libmount.so.1 => /lib64/libmount.so.1 libblkid.so.1 => /lib64/libblkid.so.1 libsystemd.so.0 => /lib64/libsystemd.so.0 libm.so.6 => /lib64/libm.so.6 ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 libselinux.so.1 => /lib64/libselinux.so.1 libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 ... Also, the tpm2 utils now depend on libudev through libcurl -> libssh -> libfido2 dep chain: $ lddtree /usr/bin/tpm2_pcrread tpm2_pcrread => /usr/bin/tpm2_pcrread (interpreter => /lib64/ld-linux-x86-64.so.2) ... libcurl.so.4 => /lib64/libcurl.so.4 ... libssh.so.4 => /lib64/libssh.so.4 libfido2.so.1 => /lib64/libfido2.so.1 libcbor.so.0.13 => /lib64/libcbor.so.0.13 libudev.so.1 => /lib64/libudev.so.1 libgcc_s.so.1 => /lib64/libgcc_s.so.1 ... Follow-up for 8030e0b19ef7c0e823d84dd08ad38a2d88e0a230. --- mkosi/mkosi.sanitizers/mkosi.postinst | 9 +++++++++ test/units/TEST-07-PID1.user-namespace-path.sh | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 88ba2c2bc098e..988a604c63eb0 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -56,6 +56,7 @@ wrap=( dbus-broker-launch dbus-daemon delv + dfuzzer dhcpd dig dnf @@ -70,6 +71,7 @@ wrap=( groups id integritysetup + iscsiadm iscsid keymgr knotc @@ -79,12 +81,15 @@ wrap=( login ls lsblk + lsns lsof lvm mdadm mkfs.btrfs + mkfs.ext4 mksquashfs mount + mountpoint multipath multipathd nvme @@ -98,8 +103,12 @@ wrap=( stat stress-ng su + swapoff + swapon tar tgtd + # The tpm2 tools (tpm2_readpublic, tpm2_pcrextend, ...) all are symlinks to tpm2 + tpm2 umount unix_chkpwd useradd diff --git a/test/units/TEST-07-PID1.user-namespace-path.sh b/test/units/TEST-07-PID1.user-namespace-path.sh index 437968e8c2531..fda83a9566f80 100755 --- a/test/units/TEST-07-PID1.user-namespace-path.sh +++ b/test/units/TEST-07-PID1.user-namespace-path.sh @@ -6,15 +6,6 @@ set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh -# When sanitizers are used, export LD_PRELOAD with the sanitizers path, -# lsns doesn't work otherwise. -if [ -f /usr/lib/systemd/systemd-asan-env ]; then - # shellcheck source=/dev/null - . /usr/lib/systemd/systemd-asan-env - export LD_PRELOAD - export ASAN_OPTIONS -fi - # Only reuse the user namespace systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600 OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') From e3aaf3d76eb0990ca015961703e905977df8faf7 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 24 Apr 2026 14:17:23 +0200 Subject: [PATCH 1281/2155] test: drop any memory limits from units when running with sanitizers As the memory usage under sanitizers is quite unpredictable. This is currently relevant mainly for Polkit, as it introduced memory limits for its polkitd.service unit in the latest version [0] which are very easy to trigger when running under sanitizers (as polkitd depends on libsystemd which brings ASan into polkitd's address space). [0] https://github.com/polkit-org/polkit/commit/7d9c06c58a957ee3f2a4383ade6f207b05207e3e --- mkosi/mkosi.sanitizers/mkosi.postinst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 988a604c63eb0..a59b5d0478552 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -9,13 +9,17 @@ if [[ ! -f "$BUILDROOT/$LIBSYSTEMD" ]]; then exit 0 fi -# ASAN and syscall filters aren't compatible with each other. +# ASAN and syscall filters aren't compatible with each other. Also, drop any memory limits +# as these are quite unpredictable when running under sanitizers. find "$BUILDROOT"/usr "$BUILDROOT"/etc -name '*.service' -type f | while read -r unit; do - if grep -q -e MemoryDeny -e SystemCall "$unit" ; then + if grep -q -e MemoryDeny -e MemoryMax -e MemoryHigh -e MemorySwapMax -e SystemCall "$unit" ; then mkdir -p "$unit.d" cat > "$unit.d/sanitizer-compat.conf" < Date: Thu, 23 Apr 2026 15:11:01 +0200 Subject: [PATCH 1282/2155] test: temporarily ignore sanitizer warning about blocked ptrace() LLVM 22 introduced an additional check [0] for ptrace() syscall when invoking sanitizers [0] which currently produces a false-positive warning when running some of our units under sanitizers: [ 47.524680] systemd-timedated[740]: ==740==WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang. [ 47.524680] systemd-timedated[740]: ==740==Child exited with signal 15. ... [ 1555.734223] systemd-oomd[93]: ==93==WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang. [ 1555.734223] systemd-oomd[93]: ==93==Child exited with signal 15. ... It is a false positive because we disable the seccomp filters system-wide for our units in the sanitizer jobs. Now, from what I've seen so far this happens only in Type=notify(-reload) units that also utilize bus_event_loop_with_idle(). This, combined with the fact that the ptrace()-check child process from [0] checks only if the child process was killed by _any_ signal, means that if the systemd unit exits on its own after becoming idle and then something sends it SIGTERM (either via explicit `systemctl stop` or during system shutdown), this SIGTERM might hit the ptrace()-check child process from the sanitizer handler (as we also send the signal to all processes in the target cgroup), which the parent process then mistakenly evaluates as a blocked ptrace() syscall, even though the check process wasn't killed by SIGSYS. I filed this as [1] to the LLVM project, but let's also temporarily ignore the warning in the sanitizer report processing, as it currently causes annoying test fails. [0] https://github.com/llvm/llvm-project/commit/a708b4bf21d7c2298224cdacf7d424abc3c8fed4 [1] https://github.com/llvm/llvm-project/issues/193714 --- test/integration-tests/integration-test-wrapper.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 93e1e0d91b54f..69b4333fee3bd 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -142,7 +142,19 @@ def process_sanitizer_report(args: argparse.Namespace, journal_file: Path) -> bo fatal_end = re.compile(r'==[0-9]+==HINT:\s+\w+Sanitizer') # 'Standard' errors: - standard_begin = re.compile(r'([0-9]+: runtime error|==[0-9]+==.+?\w+Sanitizer)') + # + # TODO: there's currently a bug in LLVM 22 due to which certain systemd + # units can throw the following warning: + # [ 3366.747202] systemd-oomd[93]: ==93==WARNING: ptrace appears to be blocked (is seccomp enabled?). + # LeakSanitizer may hang. + # [ 3366.747202] systemd-oomd[93]: ==93==Child exited with signal 15. + # + # which is then picked up by the following regex and causes the test to + # fail. Let's, temporarily, exclude this warning from the regex to mitigate + # this. + # + # See: https://github.com/llvm/llvm-project/issues/193714 + standard_begin = re.compile(r'([0-9]+: runtime error|==[0-9]+==(?!WARNING: ptrace).+?\w+Sanitizer)') standard_end = re.compile(r'SUMMARY:\s+(\w+)Sanitizer') # extract COMM From 035ba3ea571bad6772cf3731f6b5379ccb08267f Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sat, 25 Apr 2026 16:37:34 +0200 Subject: [PATCH 1283/2155] test: slightly reduce the performance/memory overhead for wrapped binaries Let's drop the quarantine that ASan uses for use-after-free detection, as it's pointless in wrapped binaries and can consume up to 256 MiB of memory (with the default configuration). Also, don't keep any stack traces for allocations & deallocations, which should (slightly) help with both memory & performance overhead. --- mkosi/mkosi.sanitizers/mkosi.postinst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index a59b5d0478552..42bbd41063c34 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -140,8 +140,11 @@ for bin in "${wrap[@]}"; do # Preload the ASan runtime DSO, otherwise ASan will complain export LD_PRELOAD="$ASAN_RT_PATH" # Disable LSan to speed things up, since we don't care about leak reports -# from 'external' binaries -export ASAN_OPTIONS=detect_leaks=$enable_lsan +# from 'external' binaries. In addition, disable quarantine (for use-after-free +# detection) and malloc stack frame collection as we don't care about these in +# 'external' binaries either, and they just unnecessarily hog up memory & cpu +# cycles. +export ASAN_OPTIONS=detect_leaks=$enable_lsan:quarantine_size_mb=0:malloc_context_size=0 # Set argv[0] to the original binary name without the ".orig" suffix exec -a "\$0" -- "${target}.orig" "\$@" EOF From ef3c07352b5dfe04afefdf66f4693986562ddc2b Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sat, 25 Apr 2026 23:39:47 +0200 Subject: [PATCH 1284/2155] test: don't strip directives from test units The original find was matching even our test units, which caused issues when the check was extended with Memory*= directives, as we stripped them off from test units for TEST-55-OOMD where we certainly need them. Since the stripping was meant primarily for "production-grade" units, let's limit it to units under /etc/systemd/system/ and /usr/lib/systemd/system/. --- mkosi/mkosi.sanitizers/mkosi.postinst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 42bbd41063c34..f420a31b633e7 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -11,7 +11,7 @@ fi # ASAN and syscall filters aren't compatible with each other. Also, drop any memory limits # as these are quite unpredictable when running under sanitizers. -find "$BUILDROOT"/usr "$BUILDROOT"/etc -name '*.service' -type f | while read -r unit; do +find "$BUILDROOT"/{etc,usr/lib}/systemd/system/ -name '*.service' -type f | while read -r unit; do if grep -q -e MemoryDeny -e MemoryMax -e MemoryHigh -e MemorySwapMax -e SystemCall "$unit" ; then mkdir -p "$unit.d" cat > "$unit.d/sanitizer-compat.conf" < Date: Thu, 23 Apr 2026 09:29:20 +0200 Subject: [PATCH 1285/2155] 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 1286/2155] 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 1287/2155] 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 1288/2155] 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 1289/2155] 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 1290/2155] 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 1291/2155] 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 1292/2155] 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 1293/2155] 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 1294/2155] 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 1295/2155] 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 1296/2155] 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 1297/2155] 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 1298/2155] 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 1299/2155] 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 1300/2155] 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 1301/2155] 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 1302/2155] 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 1303/2155] 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 1304/2155] 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 2d892207a11153750ed090548a29aacb4d38afd2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 27 Apr 2026 11:01:40 +0200 Subject: [PATCH 1305/2155] fs-util: Some followups for xopenat_full() --- src/basic/fs-util.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 3960938309fcd..84b76072d7c63 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1136,33 +1136,34 @@ static int openat_with_automount(int dir_fd, const char *path, int open_flags, m * 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. */ + * mount namespace anyway. open_tree() only ever returns O_PATH fds, so this helper is for O_PATH + * acquisition only. */ static bool can_open_tree = true; - int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(path); + assert(FLAGS_SET(open_flags, O_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) { + int fd = RET_NERRNO(open_tree(dir_fd, path, + OPEN_TREE_CLOEXEC | + (FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0))); + if (fd >= 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); + int q = fd_verify_directory(fd); if (q < 0) { - safe_close(r); + safe_close(fd); return q; } } - return r; + return fd; } - if (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return r; + if (fd != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(fd)) + return fd; can_open_tree = false; } @@ -1178,9 +1179,7 @@ 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 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))); + assert(FLAGS_SET(open_flags, O_DIRECTORY) + FLAGS_SET(xopen_flags, XO_REGULAR) + FLAGS_SET(xopen_flags, XO_SOCKET) <= 1); /* 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 From 5ff0ccaf8baf51445038780813b9a036e3a666a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 26 Apr 2026 23:22:27 +0200 Subject: [PATCH 1306/2155] tree-wide: change option_parse() to return option and arg via internal state It was requested to make the 'c', 'opt', and 'arg' params the same, i.e. defined through the FOREACH_OPTION macro. But we can't do that easily, because 'c' was defined in the for loop definition, and we can only define variables of the same type in that way. Also, in some cases we need only 'c', in other cases with need 'c' and 'arg, in some cases 'c' and 'opt', and in other cases all three. We'd need to either conditionalize or mark those variables with _unused_ to deal with compiler warnings. But a different approach works quite nicely: add state.opt and state.arg to show the current option and it's argument. (The short names are picked on purpose to reduce verbosity since those are used a lot.) --- src/ac-power/ac-power.c | 7 +- src/ask-password/ask-password.c | 27 +- src/battery-check/battery-check.c | 6 +- src/binfmt/binfmt.c | 7 +- src/bless-boot/bless-boot.c | 9 +- src/bless-boot/boot-check-no-failures.c | 4 +- src/bootctl/bootctl.c | 57 ++-- src/cgls/cgls.c | 27 +- src/cgtop/cgtop.c | 37 ++- src/core/executor.c | 29 +- src/creds/creds.c | 59 ++-- src/cryptenroll/cryptenroll.c | 75 +++-- src/cryptsetup/cryptsetup.c | 7 +- src/delta/delta.c | 11 +- src/detect-virt/detect-virt.c | 7 +- src/dissect/dissect.c | 68 +++-- src/escape/escape-tool.c | 21 +- src/factory-reset/factory-reset-tool.c | 7 +- src/firstboot/firstboot.c | 55 ++-- src/growfs/growfs.c | 9 +- src/hibernate-resume/hibernate-resume.c | 9 +- src/hostname/hostnamectl.c | 13 +- src/hwdb/hwdb.c | 9 +- src/id128/id128.c | 13 +- src/imds/imds-tool.c | 25 +- src/imds/imdsd.c | 109 ++++---- src/import/export.c | 15 +- src/import/import-fs.c | 19 +- src/import/import.c | 33 ++- src/import/importctl.c | 33 ++- src/import/pull.c | 51 ++-- src/journal-remote/journal-gatewayd.c | 17 +- src/journal-remote/journal-remote-main.c | 55 ++-- src/journal/bsod.c | 9 +- src/journal/cat.c | 17 +- src/keyutil/keyutil.c | 23 +- src/libsystemd-network/test-ndisc-send.c | 47 ++-- .../sd-journal/test-journal-append.c | 15 +- src/libudev/test-libudev.c | 9 +- src/login/inhibit.c | 17 +- src/machine-id-setup/machine-id-setup-main.c | 13 +- src/measure/measure-tool.c | 38 ++- src/modules-load/modules-load.c | 6 +- src/mute-console/mute-console.c | 9 +- .../generator/network-generator-main.c | 9 +- src/notify/notify.c | 35 ++- src/nspawn/nspawn.c | 260 +++++++++--------- src/oom/oomctl.c | 7 +- src/oom/oomd.c | 9 +- src/path/path-tool.c | 9 +- src/pcrextend/pcrextend.c | 37 ++- src/pcrlock/pcrlock.c | 45 ++- src/ptyfwd/ptyfwd-tool.c | 13 +- src/random-seed/random-seed-tool.c | 7 +- src/repart/repart.c | 143 +++++----- src/report/report-basic-server.c | 6 +- src/report/report.c | 29 +- src/sbsign/sbsign.c | 21 +- src/shared/options.c | 27 +- src/shared/options.h | 22 +- src/shutdown/shutdown.c | 41 ++- src/sleep/sleep.c | 6 +- src/socket-activate/socket-activate.c | 15 +- src/socket-proxy/socket-proxyd.c | 15 +- src/ssh-generator/ssh-issue.c | 17 +- src/stdio-bridge/stdio-bridge.c | 11 +- src/storagetm/storagetm.c | 13 +- src/sysctl/sysctl.c | 9 +- src/sysupdate/sysupdate.c | 35 ++- src/sysupdate/updatectl.c | 9 +- src/sysusers/sysusers.c | 21 +- src/test/test-chase-manual.c | 12 +- src/test/test-options.c | 148 +++++----- src/timedate/timedatectl.c | 16 +- src/tmpfiles/test-offline-passwd.c | 9 +- src/tmpfiles/tmpfiles.c | 25 +- src/tpm2-setup/tpm2-clear.c | 6 +- src/tpm2-setup/tpm2-setup.c | 15 +- .../tty-ask-password-agent.c | 13 +- src/udev/ata_id/ata_id.c | 7 +- src/udev/cdrom_id/cdrom_id.c | 7 +- src/udev/dmi_memory_id/dmi_memory_id.c | 9 +- src/udev/fido_id/fido_id.c | 7 +- src/udev/iocost/iocost.c | 7 +- src/udev/mtd_probe/mtd_probe.c | 7 +- src/udev/scsi_id/scsi_id.c | 35 ++- src/udev/v4l_id/v4l_id.c | 7 +- src/update-done/update-done.c | 9 +- src/validatefs/validatefs.c | 15 +- src/varlinkctl/varlinkctl.c | 33 ++- src/vmspawn/vmspawn.c | 197 +++++++------ src/vpick/vpick-tool.c | 51 ++-- 92 files changed, 1271 insertions(+), 1368 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index e773d8d4314f5..530ee82ff0665 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -41,10 +41,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -61,7 +60,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 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 4bd618b2a7f09..129fbf4d7e753 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -73,10 +73,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -85,13 +84,13 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("icon", "NAME", "Icon name"): - arg_icon = arg; + arg_icon = opts.arg; break; OPTION_LONG("timeout", "SEC", "Timeout in seconds"): - r = parse_sec(arg, &arg_timeout); + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --timeout= parameter: %s", opts.arg); break; /* Note the asymmetry: the long option --echo= allows an optional argument, @@ -99,15 +98,15 @@ static int parse_argv(int argc, char *argv[]) { 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) { + if (!opts.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(arg) || streq(arg, "masked")) + } else if (isempty(opts.arg) || streq(opts.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=", arg, NULL); + r = parse_boolean_argument("--echo=", opts.arg, NULL); if (r < 0) return r; @@ -117,7 +116,7 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("emoji", "yes|no|auto", "Show a lock and key emoji"): - emoji = arg; + emoji = opts.arg; break; OPTION_LONG("no-tty", NULL, "Ask question via agent even on TTY"): @@ -133,11 +132,11 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("id", "ID", "Query identifier (e.g. \"cryptsetup:/dev/sda5\")"): - arg_id = arg; + arg_id = opts.arg; break; OPTION_LONG("keyname", "NAME", "Kernel key name for caching passwords"): - arg_key_name = arg; + arg_key_name = opts.arg; break; OPTION_LONG("no-output", NULL, "Do not print password to standard output"): @@ -146,7 +145,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("credential", "NAME", "Credential name for ImportCredential=, LoadCredential= or SetCredential= credentials"): - arg_credential_name = arg; + arg_credential_name = opts.arg; break; OPTION_LONG("user", NULL, "Ask only our own user's agents"): @@ -172,7 +171,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); + char **args = option_parser_get_args(&opts); if (!strv_isempty(args)) { arg_message = strv_join(args, " "); diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index 706a7d869c53a..3e957d9fa71df 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -81,9 +81,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -93,7 +93,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - if (option_parser_get_n_args(&state) != 0) + if (option_parser_get_n_args(&opts) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index f8c2b55595e07..ed37fba276afb 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -139,10 +139,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -167,7 +166,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); 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 33fbdbb760832..8d2fe21a11f66 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -79,10 +79,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -91,13 +90,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION_LONG("path", "PATH", "Path to the $BOOT partition (may be used multiple times)"): - r = strv_extend(&arg_path, arg); + r = strv_extend(&arg_path, opts.arg); if (r < 0) return log_oom(); break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/bless-boot/boot-check-no-failures.c b/src/bless-boot/boot-check-no-failures.c index bea5e5791665e..37b0f7fd6d2b2 100644 --- a/src/bless-boot/boot-check-no-failures.c +++ b/src/bless-boot/boot-check-no-failures.c @@ -44,9 +44,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 942ef4d681875..04213dc8e17aa 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -416,10 +416,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_GROUP("Block Device Discovery Commands"): {} @@ -464,53 +463,53 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = free_and_strdup(&arg_esp_path, opts.arg); if (r < 0) return log_oom(); break; OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): - r = free_and_strdup(&arg_xbootldr_path, arg); + r = free_and_strdup(&arg_xbootldr_path, opts.arg); if (r < 0) return log_oom(); break; OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("install-source", "SOURCE", "Where to pick files when using --root=/--image= (auto, image, host)"): { - InstallSource is = install_source_from_string(arg); + InstallSource is = install_source_from_string(opts.arg); if (is < 0) - return log_error_errno(is, "Unexpected parameter for --install-source=: %s", arg); + return log_error_errno(is, "Unexpected parameter for --install-source=: %s", opts.arg); arg_install_source = is; break; } OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"): - r = parse_tristate_argument_with_auto("--variables=", arg, &arg_touch_variables); + r = parse_tristate_argument_with_auto("--variables=", opts.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.", arg); + "Compiled without support for EFI, --variables=%s cannot be specified.", opts.arg); #endif break; @@ -519,7 +518,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("random-seed", "BOOL", "Whether to create random-seed file during install"): - r = parse_boolean_argument("--random-seed=", arg, &arg_install_random_seed); + r = parse_boolean_argument("--random-seed=", opts.arg, &arg_install_random_seed); if (r < 0) return r; break; @@ -540,17 +539,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; 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 */ + if (streq(opts.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=", arg, NULL); + r = parse_boolean_argument("--make-entry-directory=", opts.arg, NULL); if (r < 0) return r; @@ -559,7 +558,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -570,23 +569,23 @@ 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 (!string_is_safe(arg, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { - _cleanup_free_ char *escaped = cescape(arg); + if (!string_is_safe(opts.arg, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { + _cleanup_free_ char *escaped = cescape(opts.arg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --efi-boot-option-description=: %s", strna(escaped)); } - if (strlen(arg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) + if (strlen(opts.arg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--efi-boot-option-description= too long: %zu > %zu", - strlen(arg), EFI_BOOT_OPTION_DESCRIPTION_MAX); - r = free_and_strdup_warn(&arg_efi_boot_option_description, arg); + strlen(opts.arg), EFI_BOOT_OPTION_DESCRIPTION_MAX); + r = free_and_strdup_warn(&arg_efi_boot_option_description, opts.arg); if (r < 0) return r; break; 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, + r = parse_boolean_argument("--efi-boot-option-description-with-device=", opts.arg, &arg_efi_boot_option_description_with_device); if (r < 0) return r; @@ -597,20 +596,20 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("secure-boot-auto-enroll", "BOOL", "Set up secure boot auto-enrollment"): - r = parse_boolean_argument("--secure-boot-auto-enroll=", arg, + r = parse_boolean_argument("--secure-boot-auto-enroll=", opts.arg, &arg_secure_boot_auto_enroll); if (r < 0) return r; break; OPTION_COMMON_PRIVATE_KEY("Private key for Secure Boot auto-enrollment"): - r = free_and_strdup_warn(&arg_private_key, arg); + r = free_and_strdup_warn(&arg_private_key, opts.arg); if (r < 0) return r; break; OPTION_COMMON_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument(arg, + r = parse_openssl_key_source_argument(opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -618,13 +617,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_CERTIFICATE("PEM certificate to use when setting up Secure Boot auto-enrollment"): - r = free_and_strdup_warn(&arg_certificate, arg); + r = free_and_strdup_warn(&arg_certificate, opts.arg); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE_SOURCE: - r = parse_openssl_certificate_source_argument(arg, + r = parse_openssl_certificate_source_argument(opts.arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) @@ -632,7 +631,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); 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), diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index 60d8e7701235b..9ed57c35cdf4f 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -69,11 +69,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 1); assert(argv); - OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -97,7 +96,7 @@ static int parse_argv(int argc, char *argv[]) { "Cannot combine --unit with --user-unit."); arg_show_unit = SHOW_UNIT_SYSTEM; - if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ + if (strv_extend(&arg_names, opts.arg) < 0) /* push arg if not empty */ return log_oom(); break; @@ -108,17 +107,17 @@ static int parse_argv(int argc, char *argv[]) { "Cannot combine --user-unit with --unit."); arg_show_unit = SHOW_UNIT_USER; - if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ + if (strv_extend(&arg_names, opts.arg) < 0) /* push arg if not empty */ return log_oom(); break; 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 (opts.arg) { + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --xattr= value: %s", arg); + return log_error_errno(r, "Failed to parse --xattr= value: %s", opts.arg); } else r = true; @@ -128,10 +127,10 @@ static int parse_argv(int argc, char *argv[]) { 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 (opts.arg) { + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", arg); + return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", opts.arg); } else r = true; @@ -147,11 +146,11 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_COMMON_MACHINE: - arg_machine = arg; + arg_machine = opts.arg; break; OPTION_POSITIONAL: - if (strv_extend(&arg_names, arg) < 0) /* push arg */ + if (strv_extend(&arg_names, opts.arg) < 0) /* push arg */ return log_oom(); break; } @@ -160,7 +159,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit or --user-unit with --machine=."); - assert(option_parser_get_n_args(&state) == 0); + assert(option_parser_get_n_args(&opts) == 0); return 1; } diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index b8194de3d3eb6..d1d20992bd159 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -719,11 +719,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 1); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -734,11 +733,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("order", "PROPERTY", "Order by specified property (path, tasks, cpu, memory, io)"): - arg_order = order_from_string(arg); + arg_order = order_from_string(opts.arg); if (arg_order < 0) return log_error_errno(arg_order, "Invalid argument to --order=: %s", - arg); + opts.arg); break; OPTION_SHORT('p', NULL, "Same as --order=path, order by path"): @@ -767,12 +766,12 @@ static int parse_argv(int argc, char *argv[]) { 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 (opts.arg) { + arg_cpu_type = cpu_type_from_string(opts.arg); if (arg_cpu_type < 0) return log_error_errno(arg_cpu_type, "Unknown argument to --cpu=: %s", - arg); + opts.arg); } else arg_cpu_type = CPU_TIME; break; @@ -786,7 +785,7 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("recursive", "BOOL", "Sum up process count recursively"): - r = parse_boolean_argument("--recursive=", arg, &arg_recursive); + r = parse_boolean_argument("--recursive=", opts.arg, &arg_recursive); if (r < 0) return r; @@ -794,19 +793,19 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION('d', "delay", "DELAY", "Delay between updates"): - r = parse_sec(arg, &arg_delay); + r = parse_sec(opts.arg, &arg_delay); if (r < 0) - return log_error_errno(r, "Failed to parse delay parameter '%s': %m", arg); + return log_error_errno(r, "Failed to parse delay parameter '%s': %m", opts.arg); if (arg_delay <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid delay parameter '%s'", - arg); + opts.arg); break; OPTION('n', "iterations", "N", "Run for N iterations before exiting"): - r = safe_atou(arg, &arg_iterations); + r = safe_atou(opts.arg, &arg_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", arg); + return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", opts.arg); break; OPTION_SHORT('1', NULL, "Shortcut for --iterations=1"): @@ -819,22 +818,22 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("depth", "DEPTH", "Maximum traversal depth (default: "STRINGIFY(DEFAULT_MAXIMUM_DEPTH)")"): - r = safe_atou(arg, &arg_depth); + r = safe_atou(opts.arg, &arg_depth); if (r < 0) - return log_error_errno(r, "Failed to parse depth parameter '%s': %m", arg); + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", opts.arg); break; OPTION_COMMON_MACHINE: - arg_machine = arg; + arg_machine = opts.arg; break; } - size_t n_args = option_parser_get_n_args(&state); + size_t n_args = option_parser_get_n_args(&opts); 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]; + arg_root = option_parser_get_args(&opts)[0]; return 1; } diff --git a/src/core/executor.c b/src/core/executor.c index 94bb481db43a4..20bc65b63e6de 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -62,10 +62,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -75,39 +74,39 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_COMMON_LOG_LEVEL: - r = log_set_max_level_from_string(arg); + r = log_set_max_level_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse log level \"%s\": %m", arg); + return log_error_errno(r, "Failed to parse log level \"%s\": %m", opts.arg); break; OPTION_COMMON_LOG_TARGET: - r = log_set_target_from_string(arg); + r = log_set_target_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse log target \"%s\": %m", arg); + return log_error_errno(r, "Failed to parse log target \"%s\": %m", opts.arg); break; OPTION_COMMON_LOG_COLOR: - r = log_show_color_from_string(arg); + r = log_show_color_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", arg); + return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", opts.arg); break; OPTION_COMMON_LOG_LOCATION: - r = log_show_location_from_string(arg); + r = log_show_location_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", arg); + return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", opts.arg); break; OPTION_COMMON_LOG_TIME: - r = log_show_time_from_string(arg); + r = log_show_time_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", arg); + return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", opts.arg); break; OPTION_LONG("deserialize", "FD", "Deserialize process config from FD"): { - int fd = parse_fd(arg); + int fd = parse_fd(opts.arg); if (fd < 0) - return log_error_errno(fd, "Failed to parse serialization fd \"%s\": %m", arg); + return log_error_errno(fd, "Failed to parse serialization fd \"%s\": %m", opts.arg); /* Set O_CLOEXEC and as a side effect, verify that the fd is valid. */ r = fd_cloexec(fd, /* cloexec= */ true); diff --git a/src/creds/creds.c b/src/creds/creds.c index a133a27dd2b07..e14a9a921cda7 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -823,11 +823,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -845,7 +844,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -856,19 +855,19 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("transcode", "METHOD", "Transcode credential data (base64, unbase64, hex, unhex)"): - if (streq(arg, "help")) { + if (streq(opts.arg, "help")) { if (arg_legend) puts("Supported transcode types:"); return DUMP_STRING_TABLE(transcode_mode, TranscodeMode, _TRANSCODE_MAX); } - if (parse_boolean(arg) == 0) /* If specified as "false", turn transcoding off */ + if (parse_boolean(opts.arg) == 0) /* If specified as "false", turn transcoding off */ arg_transcode = TRANSCODE_OFF; else { TranscodeMode m; - m = transcode_mode_from_string(arg); + m = transcode_mode_from_string(opts.arg); if (m < 0) return log_error_errno(m, "Failed to parse transcode mode: %m"); @@ -877,7 +876,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("newline", "auto|yes|no", "Suffix output with newline"): - r = parse_tristate_argument_with_auto("--newline=", arg, &arg_newline); + r = parse_tristate_argument_with_auto("--newline=", opts.arg, &arg_newline); if (r < 0) return r; break; @@ -888,45 +887,45 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("name", "NAME", "Override filename included in encrypted credential"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_name = NULL; arg_name_any = true; break; } - if (!credential_name_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", arg); + if (!credential_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", opts.arg); - arg_name = arg; + arg_name = opts.arg; arg_name_any = false; break; OPTION_LONG("timestamp", "TIME", "Include specified timestamp in encrypted credential"): - r = parse_timestamp(arg, &arg_timestamp); + r = parse_timestamp(opts.arg, &arg_timestamp); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp: %s", arg); + return log_error_errno(r, "Failed to parse timestamp: %s", opts.arg); break; OPTION_LONG("not-after", "TIME", "Include specified invalidation time in encrypted credential"): - r = parse_timestamp(arg, &arg_not_after); + r = parse_timestamp(opts.arg, &arg_not_after); if (r < 0) - return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", arg); + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", opts.arg); break; OPTION_LONG("with-key", "KEY", "Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"): - if (streq(arg, "help")) { + if (streq(opts.arg, "help")) { if (arg_legend) puts("Supported key types:"); return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX); } - if (isempty(arg)) + if (isempty(opts.arg)) arg_with_key = _CRED_AUTO; else { - CredKeyType t = cred_key_type_from_string(arg); + CredKeyType t = cred_key_type_from_string(opts.arg); if (t < 0) return log_error_errno(t, "Failed to parse key type: %m"); @@ -943,23 +942,23 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): - if (streq(arg, "list")) + if (streq(opts.arg, "list")) return tpm2_list_devices(arg_legend, arg_quiet); - arg_tpm2_device = streq(arg, "auto") ? NULL : arg; + arg_tpm2_device = streq(opts.arg, "auto") ? NULL : opts.arg; break; 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); + r = tpm2_parse_pcr_argument_to_mask(opts.arg, &arg_tpm2_pcr_mask); if (r < 0) return r; break; OPTION_LONG("tpm2-public-key", "PATH", "Specify PEM certificate to seal against"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; break; @@ -967,14 +966,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = tpm2_parse_pcr_argument_to_mask(opts.arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; break; OPTION_LONG("tpm2-signature", "PATH", "Specify signature for public key PCR policy"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; break; @@ -985,12 +984,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("uid", "UID", "Select user for scoped credentials"): - if (isempty(arg)) + if (isempty(opts.arg)) arg_uid = UID_INVALID; - else if (streq(arg, "self")) + else if (streq(opts.arg, "self")) arg_uid = getuid(); else { - const char *name = arg; + const char *name = opts.arg; r = get_user_creds( &name, @@ -1001,7 +1000,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", - arg, STRERROR_USER(r)); + opts.arg, STRERROR_USER(r)); } break; @@ -1053,7 +1052,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); arg_varlink = r; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 46b3a546c9b9b..5d0c782689392 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -276,11 +276,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -301,7 +300,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("wipe-slot", "SLOT1,SLOT2,…", "Wipe specified slots"): - r = parse_wipe_slot(arg); + r = parse_wipe_slot(opts.arg); if (r < 0) return r; break; @@ -314,7 +313,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple unlock methods specified at once, refusing."); - r = parse_path_argument(arg, /* suppress_root= */ true, &arg_unlock_keyfile); + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_unlock_keyfile); if (r < 0) return r; @@ -331,8 +330,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_fido2_device); - if (!streq(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -352,8 +351,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_tpm2_device); - if (!streq(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -389,22 +388,22 @@ static int parse_argv(int argc, char *argv[]) { "Enroll a PKCS#11 security token or list them"): { _cleanup_free_ char *uri = NULL; - if (streq(arg, "list")) + if (streq(opts.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(arg, "auto")) { + if (streq(opts.arg, "auto")) { r = pkcs11_find_token_auto(&uri); if (r < 0) return r; } else { - if (!pkcs11_uri_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); + if (!pkcs11_uri_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", opts.arg); - uri = strdup(arg); + uri = strdup(opts.arg); if (!uri) return log_oom(); } @@ -420,15 +419,15 @@ static int parse_argv(int argc, char *argv[]) { "Enroll a FIDO2-HMAC security token or list them"): { _cleanup_free_ char *device = NULL; - if (streq(arg, "list")) + if (streq(opts.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(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -440,28 +439,28 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_fido2_salt_file); if (r < 0) return r; break; 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); + r = parse_boolean_argument("--fido2-parameters-in-header=", opts.arg, &arg_fido2_parameters_in_header); if (r < 0) return r; break; OPTION_LONG("fido2-credential-algorithm", "STRING", "Specify COSE algorithm for FIDO2 credential"): - r = parse_fido2_algorithm(arg, &arg_fido2_cred_alg); + r = parse_fido2_algorithm(opts.arg, &arg_fido2_cred_alg); if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", arg); + return log_error_errno(r, "Failed to parse COSE algorithm: %s", opts.arg); break; 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); + r = parse_boolean_argument("--fido2-with-client-pin=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); @@ -469,7 +468,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--fido2-with-user-presence=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); @@ -477,7 +476,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--fido2-with-user-verification=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); @@ -489,15 +488,15 @@ static int parse_argv(int argc, char *argv[]) { "Enroll a TPM2 device or list them"): { _cleanup_free_ char *device = NULL; - if (streq(arg, "list")) + if (streq(opts.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(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -513,7 +512,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; @@ -522,14 +521,14 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = safe_atou32_full(opts.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", arg); + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", opts.arg); break; 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); + r = tpm2_parse_pcr_argument_append(opts.arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; break; @@ -537,13 +536,13 @@ static int parse_argv(int argc, char *argv[]) { 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(arg)) { + if (isempty(opts.arg)) { arg_tpm2_load_public_key = false; arg_tpm2_public_key = mfree(arg_tpm2_public_key); break; } - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; arg_tpm2_load_public_key = true; @@ -552,21 +551,21 @@ static int parse_argv(int argc, char *argv[]) { 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(arg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(opts.arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; break; 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; break; OPTION_LONG("tpm2-pcrlock", "PATH", "Specify pcrlock policy to lock against"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; auto_pcrlock = false; @@ -574,13 +573,13 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--tpm2-with-pin=", opts.arg, &arg_tpm2_pin); if (r < 0) return r; break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index d772ba9a9afee..43f2873da262c 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2505,10 +2505,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -2518,7 +2517,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/delta/delta.c b/src/delta/delta.c index 4a134ad2355f3..92b77f9ddce5b 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -517,11 +517,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -535,7 +534,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION('t', "type", "TYPE...", "Only display a selected set of override types"): - r = parse_flags(arg, arg_flags); + r = parse_flags(opts.arg, arg_flags); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse flags field."); @@ -544,14 +543,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "diff", "yes|no", "Show a diff when overridden files differ"): - r = parse_boolean_argument("--diff", arg, NULL); + r = parse_boolean_argument("--diff", opts.arg, NULL); if (r < 0) return r; arg_diff = r; break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index 3c5b3dd39cb34..f88528fccf992 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -53,10 +53,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -95,7 +94,7 @@ static int parse_argv(int argc, char *argv[]) { return DUMP_STRING_TABLE(confidential_virtualization, ConfidentialVirtualization, _CONFIDENTIAL_VIRTUALIZATION_MAX); } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 973a954acd1c2..338aed8391d97 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -230,11 +230,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const Option *current; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_NO_PAGER: @@ -260,15 +258,15 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("discard", "MODE", "Choose discard mode (disabled, loop, all, crypto)"): { DissectImageFlags flags; - if (streq(arg, "disabled")) + if (streq(opts.arg, "disabled")) flags = 0; - else if (streq(arg, "loop")) + else if (streq(opts.arg, "loop")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP; - else if (streq(arg, "all")) + else if (streq(opts.arg, "all")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; - else if (streq(arg, "crypt")) + else if (streq(opts.arg, "crypt")) flags = DISSECT_IMAGE_DISCARD_ANY; - else if (streq(arg, "list")) { + else if (streq(opts.arg, "list")) { puts("disabled\n" "all\n" "crypt\n" @@ -276,7 +274,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --discard= parameter: %s", arg); + "Unknown --discard= parameter: %s", opts.arg); arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags; break; @@ -290,18 +288,18 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("usr-hash", "HASH", "Same, but for the usr partition"): { _cleanup_(iovec_done) struct iovec roothash = {}; - PartitionDesignator d = streq(current->long_code, "root-hash") ? PARTITION_ROOT : PARTITION_USR; + PartitionDesignator d = streq(opts.opt->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."); - r = unhexmem(arg, &roothash.iov_base, &roothash.iov_len); + r = unhexmem(opts.arg, &roothash.iov_base, &roothash.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash '%s': %m", arg); + return log_error_errno(r, "Failed to parse root hash '%s': %m", opts.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", arg); + "Root hash must be at least 128-bit long: %s", opts.arg); iovec_done(&arg_verity_settings.root_hash); arg_verity_settings.root_hash = TAKE_STRUCT(roothash); @@ -317,20 +315,20 @@ static int parse_argv(int argc, char *argv[]) { const char *value; _cleanup_(iovec_done) struct iovec sig = {}; - PartitionDesignator d = streq(current->long_code, "root-hash-sig") ? PARTITION_ROOT : PARTITION_USR; + PartitionDesignator d = streq(opts.opt->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."); - if ((value = startswith(arg, "base64:"))) { + if ((value = startswith(opts.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", arg); + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", opts.arg); } else { - r = read_full_file(arg, (char**) &sig.iov_base, &sig.iov_len); + r = read_full_file(opts.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", arg); + return log_error_errno(r, "Failed to read root hash signature file '%s': %m", opts.arg); } iovec_done(&arg_verity_settings.root_hash_sig); @@ -341,44 +339,44 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, false, &arg_verity_settings.data_path); if (r < 0) return r; break; OPTION_LONG("fsck", "BOOL", "Run fsck before mounting"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --fsck= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --fsck= parameter: %s", opts.arg); SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r); break; OPTION_LONG("growfs", "BOOL", "Grow file system to partition size, if marked"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --growfs= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --growfs= parameter: %s", opts.arg); SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r); break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; OPTION_LONG("loop-ref", "NAME", "Set reference string for loopback device"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_loop_ref = mfree(arg_loop_ref); arg_loop_ref_auto = false; break; } - 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); + if (strlen(opts.arg) >= sizeof_field(struct loop_info64, lo_file_name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", opts.arg); - r = free_and_strdup_warn(&arg_loop_ref, arg); + r = free_and_strdup_warn(&arg_loop_ref, opts.arg); if (r < 0) return r; @@ -391,13 +389,13 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("mtree-hash", "BOOL", "Whether to include SHA256 hash in the mtree output"): - r = parse_boolean_argument("--mtree-hash=", arg, &arg_mtree_hash); + r = parse_boolean_argument("--mtree-hash=", opts.arg, &arg_mtree_hash); if (r < 0) return r; break; @@ -420,9 +418,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): { _cleanup_(image_filter_freep) ImageFilter *f = NULL; - r = image_filter_parse(arg, &f); + r = image_filter_parse(opts.arg, &f); if (r < 0) - return log_error_errno(r, "Failed to parse image filter expression: %s", arg); + return log_error_errno(r, "Failed to parse image filter expression: %s", opts.arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); @@ -430,7 +428,7 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("copy-ownership", "BOOL", "Whether to copy ownership when copying files"): - r = parse_tristate_argument_with_auto("--copy-ownership=", arg, &arg_copy_ownership); + r = parse_tristate_argument_with_auto("--copy-ownership=", opts.arg, &arg_copy_ownership); if (r < 0) return r; break; @@ -518,7 +516,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); + char **args = option_parser_get_args(&opts); switch (arg_action) { diff --git a/src/escape/escape-tool.c b/src/escape/escape-tool.c index aa4129cdeed98..98f0b9a0146a0 100644 --- a/src/escape/escape-tool.c +++ b/src/escape/escape-tool.c @@ -56,10 +56,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -69,20 +68,20 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION_LONG("suffix", "SUFFIX", "Unit suffix to append to escaped strings"): { - UnitType t = unit_type_from_string(arg); + UnitType t = unit_type_from_string(opts.arg); if (t < 0) - return log_error_errno(t, "Invalid unit suffix type \"%s\".", arg); + return log_error_errno(t, "Invalid unit suffix type \"%s\".", opts.arg); - arg_suffix = arg; + arg_suffix = opts.arg; break; } OPTION_LONG("template", "TEMPLATE", "Insert strings as instance into template"): - if (!unit_name_is_valid(arg, UNIT_NAME_TEMPLATE)) + if (!unit_name_is_valid(opts.arg, UNIT_NAME_TEMPLATE)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Template name %s is not valid.", arg); + "Template name %s is not valid.", opts.arg); - arg_template = arg; + arg_template = opts.arg; break; OPTION_LONG("instance", NULL, "With --unescape, show just the instance part"): @@ -103,7 +102,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - if (option_parser_get_n_args(&state) == 0) + if (option_parser_get_n_args(&opts) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough arguments."); @@ -131,7 +130,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--instance may not be combined with --template."); - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index ec3f7e43c492b..975c391fc8fae 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -70,10 +70,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -96,7 +95,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r > 0) arg_varlink = true; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 3006d93407255..721fbba21e102 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1267,11 +1267,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -1281,61 +1280,61 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, true, &arg_root); + r = parse_path_argument(opts.arg, true, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, false, &arg_image); + r = parse_path_argument(opts.arg, false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"): - r = free_and_strdup_warn(&arg_locale, arg); + r = free_and_strdup_warn(&arg_locale, opts.arg); if (r < 0) return r; break; OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"): - r = free_and_strdup_warn(&arg_locale_messages, arg); + r = free_and_strdup_warn(&arg_locale_messages, opts.arg); if (r < 0) return r; break; OPTION_LONG("keymap", "KEYMAP", "Set keymap"): - if (!keymap_is_valid(arg)) + if (!keymap_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Keymap %s is not valid.", arg); + "Keymap %s is not valid.", opts.arg); - r = free_and_strdup_warn(&arg_keymap, arg); + r = free_and_strdup_warn(&arg_keymap, opts.arg); if (r < 0) return r; break; OPTION_LONG("timezone", "TIMEZONE", "Set timezone"): - if (!timezone_is_valid(arg, LOG_ERR)) + if (!timezone_is_valid(opts.arg, LOG_ERR)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone %s is not valid.", arg); + "Timezone %s is not valid.", opts.arg); - r = free_and_strdup_warn(&arg_timezone, arg); + r = free_and_strdup_warn(&arg_timezone, opts.arg); if (r < 0) return r; break; OPTION_LONG("hostname", "NAME", "Set hostname"): - if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + if (!hostname_is_valid(opts.arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Host name %s is not valid.", arg); + "Host name %s is not valid.", opts.arg); - r = free_and_strdup_warn(&arg_hostname, arg); + r = free_and_strdup_warn(&arg_hostname, opts.arg); if (r < 0) return r; @@ -1349,13 +1348,13 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("machine-id", "ID", "Set specified machine ID"): - r = sd_id128_from_string(arg, &arg_machine_id); + r = sd_id128_from_string(opts.arg, &arg_machine_id); if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", arg); + return log_error_errno(r, "Failed to parse machine id %s.", opts.arg); break; OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): - r = free_and_strdup_warn(&arg_root_password, arg); + r = free_and_strdup_warn(&arg_root_password, opts.arg); if (r < 0) return r; @@ -1365,15 +1364,15 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("root-password-file", "FILE", "Set root password from file"): arg_root_password = mfree(arg_root_password); - r = read_one_line_file(arg, &arg_root_password); + r = read_one_line_file(opts.arg, &arg_root_password); if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", arg); + return log_error_errno(r, "Failed to read %s: %m", opts.arg); arg_root_password_is_hashed = false; break; OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): - r = free_and_strdup_warn(&arg_root_password, arg); + r = free_and_strdup_warn(&arg_root_password, opts.arg); if (r < 0) return r; @@ -1381,13 +1380,13 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("root-shell", "SHELL", "Set root shell"): - r = free_and_strdup_warn(&arg_root_shell, arg); + r = free_and_strdup_warn(&arg_root_shell, opts.arg); if (r < 0) return r; break; OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): - r = free_and_strdup_warn(&arg_kernel_cmdline, arg); + r = free_and_strdup_warn(&arg_kernel_cmdline, opts.arg); if (r < 0) return r; break; @@ -1462,21 +1461,21 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"): - r = parse_boolean_argument("--welcome=", arg, &arg_welcome); + r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome); if (r < 0) return r; break; OPTION_LONG("chrome", "BOOL", "Whether to show a color bar at top and bottom of terminal"): - r = parse_boolean_argument("--chrome=", arg, &arg_chrome); + r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome); if (r < 0) return r; break; 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); + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); if (r < 0) return r; break; diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index ff6a5909e795c..3e9eb678bf038 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -160,10 +160,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -177,12 +176,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) != 1) + if (option_parser_get_n_args(&opts) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = option_parser_get_args(&state)[0]; + arg_target = option_parser_get_args(&opts)[0]; return 1; } diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index cc48bf22fb3dc..5f42097194e18 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -55,10 +55,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -73,11 +72,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - if (option_parser_get_n_args(&state) > 0 && arg_clear) + if (option_parser_get_n_args(&opts) > 0 && arg_clear) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments specified with --clear, refusing."); - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 67e82c1e7c028..9ad2a0b4ec05e 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -765,10 +765,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -782,11 +781,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = arg; + arg_host = opts.arg; break; OPTION_COMMON_MACHINE: - r = parse_machine_argument(arg, &arg_host, &arg_transport); + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; @@ -804,7 +803,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -814,7 +813,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 141387da4faf7..286ea000dbeec 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -81,10 +81,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -99,7 +98,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): - arg_root = arg; + arg_root = opts.arg; break; OPTION_LONG("usr", NULL, @@ -108,7 +107,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/id128/id128.c b/src/id128/id128.c index 1616bfea65bbe..fbcacdbe4608b 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -237,10 +237,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -258,7 +257,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("json", "FORMAT", "Output inspection data in JSON (takes one of pretty, short, off)"): - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -279,11 +278,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION('a', "app-specific", "ID", "Generate app-specific IDs"): - r = id128_from_string_nonzero(arg, &arg_app); + r = id128_from_string_nonzero(opts.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", arg); + return log_error_errno(r, "Failed to parse \"%s\" as application ID: %m", opts.arg); break; OPTION('u', "uuid", NULL, "Output in UUID format"): @@ -291,7 +290,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index 0d71801aec659..7752e1f769cfe 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -82,10 +82,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -98,32 +97,32 @@ static int parse_argv(int argc, char *argv[]) { "Select well-known key/base, one of:" " hostname, region, zone, ipv4-public, ipv6-public, ssh-key," " userdata, userdata-base, userdata-base64"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_well_known = _IMDS_WELL_KNOWN_INVALID; break; } - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX); - ImdsWellKnown wk = imds_well_known_from_string(arg); + ImdsWellKnown wk = imds_well_known_from_string(opts.arg); if (wk < 0) - return log_error_errno(wk, "Failed to parse --well-known= argument: %s", arg); + return log_error_errno(wk, "Failed to parse --well-known= argument: %s", opts.arg); arg_well_known = wk; break; } OPTION_LONG("refresh", "SEC", "Set minimum freshness time for returned data"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_refresh_usec_set = false; break; } usec_t t; - r = parse_sec(arg, &t); + r = parse_sec(opts.arg, &t); if (r < 0) - return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); + return log_error_errno(r, "Failed to parse refresh timeout: %s", opts.arg); arg_refresh_usec = t; arg_refresh_usec_set = true; @@ -131,7 +130,7 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("cache", "BOOL", "Control cache use"): - r = parse_tristate_argument_with_auto("--cache=", arg, &arg_cache); + r = parse_tristate_argument_with_auto("--cache=", opts.arg, &arg_cache); if (r < 0) return r; break; @@ -147,8 +146,8 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&state); - size_t n_args = option_parser_get_n_args(&state); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) { if (n_args != 0) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index b3d177116119e..c803b27829aba 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -2249,10 +2249,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -2262,30 +2261,30 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION('i', "interface", "INTERFACE", "Use the specified interface"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_ifname = mfree(arg_ifname); break; } - 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); + if (!ifname_valid_full(opts.arg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", opts.arg); - r = free_and_strdup_warn(&arg_ifname, arg); + r = free_and_strdup_warn(&arg_ifname, opts.arg); if (r < 0) return r; break; OPTION_LONG("refresh", "SEC", "Set token refresh time"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_refresh_usec = REFRESH_USEC_DEFAULT; break; } usec_t t; - r = parse_sec(arg, &t); + r = parse_sec(opts.arg, &t); if (r < 0) - return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); + return log_error_errno(r, "Failed to parse refresh timeout: %s", opts.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; @@ -2295,32 +2294,32 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("fwmark", "INTEGER", "Choose firewall mark for HTTP traffic"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_fwmark_set = false; break; } - if (streq(arg, "default")) { + if (streq(opts.arg, "default")) { arg_fwmark = FWMARK_DEFAULT; arg_fwmark_set = true; break; } - r = safe_atou32(arg, &arg_fwmark); + r = safe_atou32(opts.arg, &arg_fwmark); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", opts.arg); arg_fwmark_set = true; break; OPTION_LONG("cache", "BOOL", "Enable/disable cache use"): - r = parse_boolean_argument("--cache", arg, &arg_cache); + r = parse_boolean_argument("--cache", opts.arg, &arg_cache); if (r < 0) return r; break; OPTION_LONG("wait", "BOOL", "Whether to wait for connectivity"): - r = parse_boolean_argument("--wait", arg, &arg_wait); + r = parse_boolean_argument("--wait", opts.arg, &arg_wait); if (r < 0) return r; break; @@ -2330,12 +2329,12 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION('K', "well-known", "KEY", "Select well-known key"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_well_known = _IMDS_WELL_KNOWN_INVALID; break; } - ImdsWellKnown wk = imds_well_known_from_string(arg); + ImdsWellKnown wk = imds_well_known_from_string(opts.arg); if (wk < 0) return log_error_errno(wk, "Failed to parse --well-known= parameter: %m"); @@ -2351,139 +2350,139 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Manual Endpoint Configuration"): {} OPTION_LONG("vendor", "VENDOR", "Specify IMDS vendor literally"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_vendor = mfree(arg_vendor); break; } - r = free_and_strdup_warn(&arg_vendor, arg); + r = free_and_strdup_warn(&arg_vendor, opts.arg); if (r < 0) return r; break; OPTION_LONG("token-url", "URL", "URL for acquiring token"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_token_url = mfree(arg_token_url); break; } - if (!http_url_is_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); + if (!http_url_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", opts.arg); - r = free_and_strdup_warn(&arg_token_url, arg); + r = free_and_strdup_warn(&arg_token_url, opts.arg); if (r < 0) return r; break; OPTION_LONG("refresh-header-name", "NAME", "Header name for passing refresh time"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_refresh_header_name = mfree(arg_refresh_header_name); break; } - if (!http_header_name_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); + if (!http_header_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", opts.arg); - r = free_and_strdup_warn(&arg_refresh_header_name, arg); + r = free_and_strdup_warn(&arg_refresh_header_name, opts.arg); if (r < 0) return r; break; OPTION_LONG("data-url", "URL", "Base URL for acquiring data"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_data_url = mfree(arg_data_url); break; } - if (!http_url_is_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); + if (!http_url_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", opts.arg); - r = free_and_strdup_warn(&arg_data_url, arg); + r = free_and_strdup_warn(&arg_data_url, opts.arg); if (r < 0) return r; break; OPTION_LONG("data-url-suffix", "STRING", "Suffix to append to data URL"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_data_url_suffix = mfree(arg_data_url_suffix); break; } - if (!ascii_is_valid(arg) || string_has_cc(arg, /* ok= */ NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", arg); + if (!ascii_is_valid(opts.arg) || string_has_cc(opts.arg, /* ok= */ NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", opts.arg); - r = free_and_strdup_warn(&arg_data_url_suffix, arg); + r = free_and_strdup_warn(&arg_data_url_suffix, opts.arg); if (r < 0) return r; break; OPTION_LONG("token-header-name", "NAME", "Header name for passing token string"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_token_header_name = mfree(arg_token_header_name); break; } - if (!http_header_name_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); + if (!http_header_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", opts.arg); - r = free_and_strdup_warn(&arg_token_header_name, arg); + r = free_and_strdup_warn(&arg_token_header_name, opts.arg); if (r < 0) return r; break; OPTION_LONG("extra-header", "NAME: VALUE", "Additional header to pass to data transfer"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_extra_header = strv_free(arg_extra_header); break; } - if (!http_header_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg); + if (!http_header_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", opts.arg); - if (strv_extend(&arg_extra_header, arg) < 0) + if (strv_extend(&arg_extra_header, opts.arg) < 0) return log_oom(); break; OPTION_LONG("address-ipv4", "ADDRESS", "Configure IPv4 address of the IMDS server"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_address_ipv4 = (struct in_addr) {}; break; } union in_addr_union u; - r = in_addr_from_string(AF_INET, arg, &u); + r = in_addr_from_string(AF_INET, opts.arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse IPv4 address: %s", arg); + return log_error_errno(r, "Failed to parse IPv4 address: %s", opts.arg); arg_address_ipv4 = u.in; break; } OPTION_LONG("address-ipv6", "ADDRESS", "Configure IPv6 address of the IMDS server"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_address_ipv6 = (struct in6_addr) {}; break; } union in_addr_union u; - r = in_addr_from_string(AF_INET6, arg, &u); + r = in_addr_from_string(AF_INET6, opts.arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse IPv6 address: %s", arg); + return log_error_errno(r, "Failed to parse IPv6 address: %s", opts.arg); arg_address_ipv6 = u.in6; break; } OPTION_LONG("well-known-key", "NAME:KEY", "Configure the location of well-known keys"): { - if (isempty(arg)) { + if (isempty(opts.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(arg, ':'); + const char *e = strchr(opts.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(arg, e - arg); + _cleanup_free_ char *name = strndup(opts.arg, e - opts.arg); if (!name) return log_oom(); @@ -2512,8 +2511,8 @@ 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); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); if (arg_setup_network) { if (n_args != 0) diff --git a/src/import/export.c b/src/import/export.c index 71f892feb001f..5b233e71a5bfd 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -238,10 +238,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -251,17 +250,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION_LONG("format", "FORMAT", "Select format"): - arg_compress = compression_from_string_harder(arg); + arg_compress = compression_from_string_harder(opts.arg); if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", arg); + "Unknown format: %s", opts.arg); break; OPTION_LONG("class", "CLASS", "Select image class (machine, sysext, confext, portable)"): - arg_class = image_class_from_string(arg); + arg_class = image_class_from_string(opts.arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; OPTION_LONG("system", NULL, "Operate in per-system mode"): @@ -276,7 +275,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 3605320300d3a..878d0c5b8f14a 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -313,11 +313,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -331,7 +330,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("image-root", "PATH", "Image root directory"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; break; @@ -346,29 +345,29 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = parse_boolean_argument("--btrfs-subvol=", opts.arg, &arg_btrfs_subvol); if (r < 0) return r; break; OPTION_LONG("btrfs-quota", "BOOL", "Controls whether to set up quota for btrfs subvolume"): - r = parse_boolean_argument("--btrfs-quota=", arg, &arg_btrfs_quota); + r = parse_boolean_argument("--btrfs-quota=", opts.arg, &arg_btrfs_quota); if (r < 0) return r; break; OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): - r = parse_boolean_argument("--sync=", arg, &arg_sync); + r = parse_boolean_argument("--sync=", opts.arg, &arg_sync); if (r < 0) return r; break; OPTION_LONG("class", "CLASS", "Select image class (machine, sysext, confext, portable)"): - arg_class = image_class_from_string(arg); + arg_class = image_class_from_string(opts.arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; OPTION_LONG("system", NULL, "Operate in per-system mode"): @@ -386,7 +385,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(r, "Failed to pick image root: %m"); } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/import/import.c b/src/import/import.c index c678c68f11565..43740aeac7d46 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -317,10 +317,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -334,7 +333,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("image-root", "PATH", "Image root directory"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; break; @@ -349,7 +348,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("btrfs-subvol", "BOOL", "Controls whether to create a btrfs subvolume instead of a directory"): - r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); + r = parse_boolean_argument("--btrfs-subvol=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); @@ -357,7 +356,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("btrfs-quota", "BOOL", "Controls whether to set up quota for btrfs subvolume"): - r = parse_boolean_argument("--btrfs-quota=", arg, NULL); + r = parse_boolean_argument("--btrfs-quota=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); @@ -365,14 +364,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("convert-qcow2", "BOOL", "Controls whether to convert QCOW2 images to regular disk images"): - r = parse_boolean_argument("--convert-qcow2=", arg, NULL); + r = parse_boolean_argument("--convert-qcow2=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): - r = parse_boolean_argument("--sync=", arg, NULL); + r = parse_boolean_argument("--sync=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_SYNC, r); @@ -381,11 +380,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(arg, &u); + r = safe_atou64(opts.arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", opts.arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", opts.arg); arg_offset = u; break; @@ -394,11 +393,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(arg, 1024, &u); + r = parse_size(opts.arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", opts.arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", opts.arg); arg_size_max = u; break; @@ -406,9 +405,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("class", "CLASS", "Select image class (machine, sysext, confext, portable)"): - arg_class = image_class_from_string(arg); + arg_class = image_class_from_string(opts.arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; OPTION_LONG("system", NULL, "Operate in per-system mode"): @@ -438,7 +437,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/import/importctl.c b/src/import/importctl.c index dceb03a6d6da5..65fff5f5f3a43 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1113,10 +1113,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -1139,11 +1138,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = arg; + arg_host = opts.arg; break; OPTION_COMMON_MACHINE: - r = parse_machine_argument(arg, &arg_host, &arg_transport); + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; @@ -1166,7 +1165,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; arg_legend = false; @@ -1179,21 +1178,21 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("verify", "MODE", "Verification mode for downloaded images (no, checksum, signature)"): - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - r = import_verify_from_string(arg); + r = import_verify_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --verify= setting: %s", arg); + return log_error_errno(r, "Failed to parse --verify= setting: %s", opts.arg); arg_verify = r; break; OPTION_LONG("format", "FORMAT", "Desired output format for export (zstd, xz, gzip, bzip2)"): - if (!STR_IN_SET(arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) + if (!STR_IN_SET(opts.arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", arg); - arg_format = arg; + "Unknown format: %s", opts.arg); + arg_format = opts.arg; break; OPTION_LONG("force", NULL, "Install image even if already exists"): @@ -1202,9 +1201,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("class", "TYPE", "Install as the specified TYPE"): - arg_image_class = image_class_from_string(arg); + arg_image_class = image_class_from_string(opts.arg); if (arg_image_class < 0) - return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", arg); + return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", opts.arg); break; OPTION_SHORT('m', NULL, "Install as --class=machine, machine image"): @@ -1225,9 +1224,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("keep-download", "BOOL", "Control whether to keep pristine copy of download"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= value: %s", arg); + return log_error_errno(r, "Failed to parse --keep-download= value: %s", opts.arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; @@ -1239,7 +1238,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/import/pull.c b/src/import/pull.c index 651e6b5ff5b60..0cc23dc6ed4b2 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -364,10 +364,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -381,7 +380,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("image-root", "PATH", "Image root directory"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; break; @@ -390,7 +389,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Verify downloaded image, one of: 'no', 'checksum', 'signature' or literal SHA256 hash"): { ImportVerify v; - v = import_verify_from_string(arg); + v = import_verify_from_string(opts.arg); if (v < 0) { _cleanup_free_ void *h = NULL; size_t n; @@ -398,10 +397,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { /* If this is not a valid verification mode, maybe it's a literally specified * SHA256 hash? We can handle that too... */ - r = unhexmem(arg, &h, &n); + r = unhexmem(opts.arg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid verification setting: %s", arg); + "Invalid verification setting: %s", opts.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); @@ -418,7 +417,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { } OPTION_LONG("settings", "BOOL", "Download settings file with image"): - r = parse_boolean_argument("--settings=", arg, NULL); + r = parse_boolean_argument("--settings=", opts.arg, NULL); if (r < 0) return r; @@ -427,7 +426,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("roothash", "BOOL", "Download root hash file with image"): - r = parse_boolean_argument("--roothash=", arg, NULL); + r = parse_boolean_argument("--roothash=", opts.arg, NULL); if (r < 0) return r; @@ -440,7 +439,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("roothash-signature", "BOOL", "Download root hash signature file with image"): - r = parse_boolean_argument("--roothash-signature=", arg, NULL); + r = parse_boolean_argument("--roothash-signature=", opts.arg, NULL); if (r < 0) return r; @@ -448,7 +447,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("verity", "BOOL", "Download verity file with image"): - r = parse_boolean_argument("--verity=", arg, NULL); + r = parse_boolean_argument("--verity=", opts.arg, NULL); if (r < 0) return r; @@ -466,7 +465,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("btrfs-subvol", "BOOL", "Controls whether to create a btrfs subvolume instead of a directory"): - r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); + r = parse_boolean_argument("--btrfs-subvol=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); @@ -474,7 +473,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("btrfs-quota", "BOOL", "Controls whether to set up quota for btrfs subvolume"): - r = parse_boolean_argument("--btrfs-quota=", arg, NULL); + r = parse_boolean_argument("--btrfs-quota=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); @@ -482,14 +481,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("convert-qcow2", "BOOL", "Controls whether to convert QCOW2 images to regular disk images"): - r = parse_boolean_argument("--convert-qcow2=", arg, NULL); + r = parse_boolean_argument("--convert-qcow2=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): - r = parse_boolean_argument("--sync=", arg, NULL); + r = parse_boolean_argument("--sync=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_SYNC, r); @@ -498,11 +497,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(arg, &u); + r = safe_atou64(opts.arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", opts.arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", opts.arg); arg_offset = u; break; @@ -511,11 +510,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(arg, 1024, &u); + r = parse_size(opts.arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", opts.arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", opts.arg); arg_size_max = u; break; @@ -523,16 +522,16 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("class", "CLASS", "Select image class (machine, sysext, confext, portable)"): - arg_class = image_class_from_string(arg); + arg_class = image_class_from_string(opts.arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; OPTION_LONG("keep-download", "BOOL", "Keep a pristine copy of the downloaded file around"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= argument: %s", arg); + return log_error_errno(r, "Failed to parse --keep-download= argument: %s", opts.arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); auto_keep_download = false; @@ -577,7 +576,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 7052fd000bd5e..ffef7edea5ec8 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -1118,11 +1118,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -1136,7 +1135,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, opts.arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL); @@ -1150,7 +1149,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file specified twice"); r = read_full_file_full( - AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, opts.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); @@ -1165,7 +1164,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "CA certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, opts.arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL); @@ -1191,19 +1190,19 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION('D', "directory", "PATH", "Serve journal files in directory"): - r = free_and_strdup_warn(&arg_directory, arg); + r = free_and_strdup_warn(&arg_directory, opts.arg); if (r < 0) return r; break; OPTION_LONG("file", "PATH", "Serve this journal file"): - r = glob_extend(&arg_file, arg, GLOB_NOCHECK); + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 1fbcc27815210..5709f87f74617 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -905,10 +905,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -918,19 +917,19 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("url", "URL", "Read events from systemd-journal-gatewayd at URL"): - r = free_and_strdup_warn(&arg_url, arg); + r = free_and_strdup_warn(&arg_url, opts.arg); if (r < 0) return r; break; OPTION_LONG("getter", "COMMAND", "Read events from the output of COMMAND"): - r = free_and_strdup_warn(&arg_getter, arg); + r = free_and_strdup_warn(&arg_getter, opts.arg); if (r < 0) return r; break; OPTION_LONG("listen-raw", "ADDR", "Listen for connections at ADDR"): - r = free_and_strdup_warn(&arg_listen_raw, arg); + r = free_and_strdup_warn(&arg_listen_raw, opts.arg); if (r < 0) return r; break; @@ -940,11 +939,11 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-http= more than once"); - r = negative_fd(arg); + r = negative_fd(opts.arg); if (r >= 0) http_socket = r; else { - r = free_and_strdup_warn(&arg_listen_http, arg); + r = free_and_strdup_warn(&arg_listen_http, opts.arg); if (r < 0) return r; } @@ -955,24 +954,24 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-https= more than once"); - r = negative_fd(arg); + r = negative_fd(opts.arg); if (r >= 0) https_socket = r; else { - r = free_and_strdup_warn(&arg_listen_https, arg); + r = free_and_strdup_warn(&arg_listen_https, opts.arg); if (r < 0) return r; } break; OPTION_LONG("key", "FILENAME", "SSL key in PEM format (default: \"" PRIV_KEY_FILE "\")"): - r = free_and_strdup_warn(&arg_key, arg); + r = free_and_strdup_warn(&arg_key, opts.arg); if (r < 0) return r; break; OPTION_LONG("cert", "FILENAME", "SSL certificate in PEM format (default: \"" CERT_FILE "\")"): - r = free_and_strdup_warn(&arg_cert, arg); + r = free_and_strdup_warn(&arg_cert, opts.arg); if (r < 0) return r; break; @@ -980,7 +979,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("trust", "FILENAME|all", "SSL CA certificate or disable checking (default: \"" TRUST_FILE "\")"): #if HAVE_GNUTLS - r = free_and_strdup_warn(&arg_trust, arg); + r = free_and_strdup_warn(&arg_trust, opts.arg); if (r < 0) return r; #else @@ -989,34 +988,34 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION('o', "output", "FILE|DIR", "Write output to FILE or DIR/external-*.journal"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; OPTION_LONG("split-mode", "none|host", "How many output files to create"): - arg_split_mode = journal_write_split_mode_from_string(arg); + arg_split_mode = journal_write_split_mode_from_string(opts.arg); if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) - return log_error_errno(arg_split_mode, "Invalid split mode: %s", arg); + return log_error_errno(arg_split_mode, "Invalid split mode: %s", opts.arg); break; OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "compress", "BOOL", "Use compression in the output journal (default: yes)"): - r = parse_boolean_argument("--compress", arg, &arg_compress); + r = parse_boolean_argument("--compress", opts.arg, &arg_compress); if (r < 0) return r; break; OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "seal", "BOOL", "Use event sealing (default: no)"): - r = parse_boolean_argument("--seal", arg, &arg_seal); + r = parse_boolean_argument("--seal", opts.arg, &arg_seal); if (r < 0) return r; break; OPTION_LONG("gnutls-log", "CATEGORY,...", "Specify a list of gnutls logging categories"): #if HAVE_GNUTLS - for (const char *p = arg;;) { + for (const char *p = opts.arg;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); @@ -1034,31 +1033,31 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("max-use", "BYTES", "Maximum disk space to use"): - r = parse_size(arg, 1024, &arg_max_use); + r = parse_size(opts.arg, 1024, &arg_max_use); if (r < 0) - return log_error_errno(r, "Failed to parse --max-use= value: %s", arg); + return log_error_errno(r, "Failed to parse --max-use= value: %s", opts.arg); break; OPTION_LONG("keep-free", "BYTES", "Minimum disk space to keep free"): - r = parse_size(arg, 1024, &arg_keep_free); + r = parse_size(opts.arg, 1024, &arg_keep_free); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-free= value: %s", arg); + return log_error_errno(r, "Failed to parse --keep-free= value: %s", opts.arg); break; OPTION_LONG("max-file-size", "BYTES", "Maximum size of individual journal files"): - r = parse_size(arg, 1024, &arg_max_size); + r = parse_size(opts.arg, 1024, &arg_max_size); if (r < 0) - return log_error_errno(r, "Failed to parse --max-file-size= value: %s", arg); + return log_error_errno(r, "Failed to parse --max-file-size= value: %s", opts.arg); break; OPTION_LONG("max-files", "N", "Maximum number of journal files to keep"): - r = safe_atou64(arg, &arg_n_max_files); + r = safe_atou64(opts.arg, &arg_n_max_files); if (r < 0) - return log_error_errno(r, "Failed to parse --max-files= value: %s", arg); + return log_error_errno(r, "Failed to parse --max-files= value: %s", opts.arg); break; } - arg_files = strv_copy(option_parser_get_args(&state)); + arg_files = strv_copy(option_parser_get_args(&opts)); if (!arg_files) return log_oom(); diff --git a/src/journal/bsod.c b/src/journal/bsod.c index b314c08660ac8..1701605590209 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -247,11 +247,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -265,13 +264,13 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("tty", "TTY", "Specify path to TTY to use"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tty); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tty); if (r < 0) return r; break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); diff --git a/src/journal/cat.c b/src/journal/cat.c index 62249663c581b..f8b5e0df31727 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -59,11 +59,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -73,11 +72,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION('t', "identifier", "STRING", "Set syslog identifier"): - arg_identifier = empty_to_null(arg); + arg_identifier = empty_to_null(opts.arg); break; OPTION('p', "priority", "PRIORITY", "Set priority value (0..7)"): - arg_priority = log_level_from_string(arg); + arg_priority = log_level_from_string(opts.arg); if (arg_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse priority value."); @@ -85,7 +84,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("stderr-priority", "PRIORITY", "Set priority value (0..7) used for stderr"): - arg_stderr_priority = log_level_from_string(arg); + arg_stderr_priority = log_level_from_string(opts.arg); if (arg_stderr_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse stderr priority value."); @@ -93,18 +92,18 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("level-prefix", "BOOL", "Control whether level prefix shall be parsed"): - r = parse_boolean_argument("--level-prefix=", arg, &arg_level_prefix); + r = parse_boolean_argument("--level-prefix=", opts.arg, &arg_level_prefix); if (r < 0) return r; break; OPTION_LONG("namespace", "NAMESPACE", "Connect to specified journal namespace"): - arg_namespace = empty_to_null(arg); + arg_namespace = empty_to_null(opts.arg); break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 5b4d6f451e5f1..474f42fec72ec 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -87,11 +87,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -101,14 +100,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION_COMMON_PRIVATE_KEY("Private key in PEM format"): - r = free_and_strdup_warn(&arg_private_key, arg); + r = free_and_strdup_warn(&arg_private_key, opts.arg); if (r < 0) return r; break; OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - arg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -116,14 +115,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): - r = free_and_strdup_warn(&arg_certificate, arg); + r = free_and_strdup_warn(&arg_certificate, opts.arg); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - arg, + opts.arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) @@ -131,24 +130,24 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("signature", "PATH", "PKCS#1 signature to embed in PKCS#7 signature"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signature); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_signature); if (r < 0) return r; break; OPTION_LONG("content", "PATH", "Raw data content to embed in PKCS#7 signature"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_content); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_content); if (r < 0) return r; break; OPTION_LONG("hash-algorithm", "ALGORITHM", "Hash algorithm used to create the PKCS#1 signature"): - arg_hash_algorithm = arg; + arg_hash_algorithm = opts.arg; break; OPTION_LONG("output", "PATH", "Where to write the PKCS#7 signature"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; @@ -157,7 +156,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index e0bbcfe6c243a..de04198d370c0 100644 --- a/src/libsystemd-network/test-ndisc-send.c +++ b/src/libsystemd-network/test-ndisc-send.c @@ -78,88 +78,87 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_VERSION: return version(); OPTION('i', "interface", "INTERFACE", "Network interface"): - r = rtnl_resolve_interface_or_warn(&rtnl, arg); + r = rtnl_resolve_interface_or_warn(&rtnl, opts.arg); if (r < 0) return r; arg_ifindex = r; break; OPTION('t', "type", "TYPE", "ICMPv6 message type"): - r = parse_icmp6_type(arg); + r = parse_icmp6_type(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse message type: %m"); arg_icmp6_type = r; break; OPTION('d', "dest", "ADDRESS", "Destination address"): - r = in_addr_from_string(AF_INET6, arg, &arg_dest); + r = in_addr_from_string(AF_INET6, opts.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.", arg); + "The destination address %s is not a link-local address.", opts.arg); break; OPTION_GROUP("Router Advertisement"): {} OPTION_LONG("hop-limit", "LIMIT", "Hop limit"): - r = safe_atou8(arg, &arg_hop_limit); + r = safe_atou8(opts.arg, &arg_hop_limit); if (r < 0) return log_error_errno(r, "Failed to parse hop limit: %m"); break; OPTION_LONG("managed", "BOOL", "Managed flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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; OPTION_LONG("other", "BOOL", "Other flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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; OPTION_LONG("home-agent", "BOOL", "Home-agent flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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; OPTION_LONG("preference", "PREF", "Preference"): - r = parse_preference(arg); + r = parse_preference(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse preference: %m"); arg_preference = r; break; OPTION_LONG("lifetime", "SECS", "Lifetime"): - r = parse_sec(arg, &arg_lifetime); + r = parse_sec(opts.arg, &arg_lifetime); if (r < 0) return log_error_errno(r, "Failed to parse lifetime: %m"); break; OPTION_LONG("reachable-time", "SECS", "Reachable time"): - r = parse_sec(arg, &arg_reachable); + r = parse_sec(opts.arg, &arg_reachable); if (r < 0) return log_error_errno(r, "Failed to parse reachable time: %m"); break; OPTION_LONG("retransmit-timer", "SECS", "Retransmit timer"): - r = parse_sec(arg, &arg_retransmit); + r = parse_sec(opts.arg, &arg_retransmit); if (r < 0) return log_error_errno(r, "Failed to parse retransmit timer: %m"); break; @@ -167,21 +166,21 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Neighbor Advertisement"): {} OPTION_LONG("is-router", "BOOL", "Router flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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; OPTION_LONG("is-solicited", "BOOL", "Solicited flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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; OPTION_LONG("is-override", "BOOL", "Override flag"): - r = parse_boolean(arg); + r = parse_boolean(opts.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); @@ -190,7 +189,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = in_addr_from_string(AF_INET6, opts.arg, &arg_target_address); if (r < 0) return log_error_errno(r, "Failed to parse target address: %m"); break; @@ -198,7 +197,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Redirect"): {} OPTION_LONG("redirect-destination", "ADDRESS", "Redirect destination address"): - r = in_addr_from_string(AF_INET6, arg, &arg_redirect_destination); + r = in_addr_from_string(AF_INET6, opts.arg, &arg_redirect_destination); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); break; @@ -206,14 +205,14 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("NDisc Options"): {} OPTION_LONG("source-ll-address", "BOOL", "Include source link-layer address"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse source LL address option: %m"); arg_set_source_mac = r; break; OPTION_LONG("target-ll-address", "ADDRESS", "Target link-layer address"): - r = parse_ether_addr(arg, &arg_target_mac); + r = parse_ether_addr(opts.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; @@ -223,7 +222,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_free_ void *p = NULL; size_t len; - r = unbase64mem(arg, &p, &len); + r = unbase64mem(opts.arg, &p, &len); if (r < 0) return log_error_errno(r, "Failed to parse redirected header: %m"); @@ -235,7 +234,7 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("mtu", "MTU", "MTU"): - r = safe_atou32(arg, &arg_mtu); + r = safe_atou32(opts.arg, &arg_mtu); if (r < 0) return log_error_errno(r, "Failed to parse MTU: %m"); arg_set_mtu = true; diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index a07634a249c00..75a1fce6fc98c 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -151,10 +151,9 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: { @@ -180,27 +179,27 @@ int main(int argc, char *argv[]) { "Offset at which to start corrupting the journal " "(default: random offset is picked, unless --sequential is used" " - in that case we use 0 + iteration)"): - r = safe_atou64(arg, &start_offset); + r = safe_atou64(opts.arg, &start_offset); if (r < 0) return log_error_errno(r, "Invalid starting offset: %m"); break; OPTION_LONG("iterations", "ITER", "Number of iterations to perform before exiting (default: 100)"): - r = safe_atou64(arg, &iterations); + r = safe_atou64(opts.arg, &iterations); if (r < 0) return log_error_errno(r, "Invalid value for iterations: %m"); break; OPTION_LONG("corrupt-step", "STEP", "Corrupt every n-th byte starting from OFFSET (default: 31)"): - r = safe_atou64(arg, &corrupt_step); + r = safe_atou64(opts.arg, &corrupt_step); if (r < 0) return log_error_errno(r, "Invalid value for corrupt-step: %m"); break; OPTION_LONG("iteration-step", "STEP", "Iteration step (default: 1)"): - r = safe_atou64(arg, &iteration_step); + r = safe_atou64(opts.arg, &iteration_step); if (r < 0) return log_error_errno(r, "Invalid value for iteration-step: %m"); break; @@ -215,7 +214,7 @@ int main(int argc, char *argv[]) { OPTION_LONG("run-one", "OFFSET", "Single shot mode for reproducing issues. " "Takes the same offset as --start-offset= and does only one iteration"): - r = safe_atou64(arg, &start_offset); + r = safe_atou64(opts.arg, &start_offset); if (r < 0) return log_error_errno(r, "Invalid offset: %m"); diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index 48d5ac4d9d960..a653f0c6c8fdd 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -428,10 +428,9 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * assert(syspath); assert(subsystem); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -442,11 +441,11 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * return 0; OPTION('p', "syspath", "PATH", "Syspath to test"): - *syspath = arg; + *syspath = opts.arg; break; OPTION('s', "subsystem", "SUBSYSTEM", "Subsystem to enumerate"): - *subsystem = arg; + *subsystem = opts.arg; break; OPTION('d', "debug", NULL, "Enable debug logging"): diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 4002d3fe4ae76..4abfc1c6d3acd 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -200,11 +200,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -226,7 +225,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -236,21 +235,21 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { "(shutdown, sleep, idle, handle-power-key, " "handle-suspend-key, handle-hibernate-key, " "handle-lid-switch)"): - arg_what = arg; + arg_what = opts.arg; break; OPTION_LONG("who", "STRING", "A descriptive string who is inhibiting"): - arg_who = arg; + arg_who = opts.arg; break; OPTION_LONG("why", "STRING", "A descriptive string why is being inhibited"): - arg_why = arg; + arg_why = opts.arg; break; OPTION_LONG("mode", "MODE", "One of block, block-weak, or delay"): - arg_mode = arg; + arg_mode = opts.arg; break; OPTION_LONG("list", NULL, "List active inhibitors"): @@ -258,7 +257,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (arg_action == ACTION_INHIBIT && strv_isempty(args)) arg_action = ACTION_LIST; diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 4fb70821123ae..9dd389dbaa4f9 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -75,11 +75,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -95,19 +94,19 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Options"): {} OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, true, &arg_root); + r = parse_path_argument(opts.arg, true, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, false, &arg_image); + r = parse_path_argument(opts.arg, false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; @@ -117,7 +116,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments"); diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 44619cab7db4b..a92a418f61fce 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -136,12 +136,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -163,7 +161,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Specify a boot phase to sign for"): { char *n; - n = normalize_phase(arg); + n = normalize_phase(opts.arg); if (!n) return log_oom(); @@ -182,9 +180,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return r; - implementation = sym_EVP_get_digestbyname(arg); + implementation = sym_EVP_get_digestbyname(opts.arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", opts.arg); if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); @@ -196,11 +194,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Use specified TPM2 device"): { _cleanup_free_ char *device = NULL; - if (streq(arg, "list")) + if (streq(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -210,7 +208,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { } OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): - r = free_and_strdup_warn(&arg_private_key, arg); + r = free_and_strdup_warn(&arg_private_key, opts.arg); if (r < 0) return r; @@ -218,7 +216,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - arg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -228,21 +226,21 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("public-key", "KEY", "Public key (PEM) to validate against"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_public_key); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_public_key); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): - r = free_and_strdup_warn(&arg_certificate, arg); + r = free_and_strdup_warn(&arg_certificate, opts.arg); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - arg, + opts.arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) @@ -250,7 +248,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; @@ -262,7 +260,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("append", "PATH", "Load specified JSON signature, and append new signature to it"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_append); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_append); if (r < 0) return r; @@ -301,9 +299,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Path to EFI firmware file (→ .efifw)"): {} /* Make sure that if new sections are added, the list here is updated. */ assert_cc(UNIFIED_SECTION_EFIFW + 1 == _UNIFIED_SECTION_MAX); - assert(opt->data < _UNIFIED_SECTION_MAX); + assert(opts.opt->data < _UNIFIED_SECTION_MAX); - r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, arg_sections + opts.opt->data); if (r < 0) return r; break; @@ -349,7 +347,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { log_debug("Measuring boot phases: %s", j); - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index 5435de4e2e988..f6806d604ab55 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -359,9 +359,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -371,7 +371,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index b40f86fafb877..be6b5fac09166 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -60,11 +60,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -74,13 +73,13 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("kernel", "BOOL", "Mute kernel log output"): - r = parse_boolean_argument("--kernel=", arg, &arg_mute_kernel); + r = parse_boolean_argument("--kernel=", opts.arg, &arg_mute_kernel); if (r < 0) return r; break; OPTION_LONG("pid1", "BOOL", "Mute PID 1 status output"): - r = parse_boolean_argument("--pid1=", arg, &arg_mute_pid1); + r = parse_boolean_argument("--pid1=", opts.arg, &arg_mute_pid1); if (r < 0) return r; break; diff --git a/src/network/generator/network-generator-main.c b/src/network/generator/network-generator-main.c index f9acf405006ef..721d36b831945 100644 --- a/src/network/generator/network-generator-main.c +++ b/src/network/generator/network-generator-main.c @@ -172,10 +172,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -186,11 +185,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - arg_root = arg; + arg_root = opts.arg; break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/notify/notify.c b/src/notify/notify.c index 33d8fdf2c0912..00f915dc7bff1 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -153,10 +153,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -184,28 +183,28 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Set main PID of daemon"): pidref_done(&arg_pid); - if (isempty(arg) || streq(arg, "auto")) + if (isempty(opts.arg) || streq(opts.arg, "auto")) r = pidref_parent_if_applicable(&arg_pid); - else if (streq(arg, "parent")) + else if (streq(opts.arg, "parent")) r = pidref_set_parent(&arg_pid); - else if (streq(arg, "self")) + else if (streq(opts.arg, "self")) r = pidref_set_self(&arg_pid); else - r = pidref_set_pidstr(&arg_pid, arg); + r = pidref_set_pidstr(&arg_pid, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to refer to --pid='%s': %m", arg); + return log_error_errno(r, "Failed to refer to --pid='%s': %m", opts.arg); break; OPTION_LONG("uid", "USER", "Set user to send from"): - r = get_user_creds(&arg, &arg_uid, &arg_gid, NULL, NULL, 0); + r = get_user_creds(&opts.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); + r = parse_uid(opts.arg, &arg_uid); if (r < 0) - return log_error_errno(r, "Can't resolve user %s: %m", arg); + return log_error_errno(r, "Can't resolve user %s: %m", opts.arg); break; OPTION_LONG("status", "TEXT", "Set status text"): - arg_status = arg; + arg_status = opts.arg; break; OPTION_LONG("booted", NULL, "Check if the system was booted up with systemd"): @@ -223,9 +222,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("fd", "FD", "Pass specified file descriptor along with the message"): { _cleanup_close_ int owned_fd = -EBADF; - int fdnr = parse_fd(arg); + int fdnr = parse_fd(opts.arg); if (fdnr < 0) - return log_error_errno(fdnr, "Failed to parse file descriptor: %s", arg); + return log_error_errno(fdnr, "Failed to parse file descriptor: %s", opts.arg); if (!passed) { /* Take possession of all passed fds */ @@ -259,10 +258,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { } 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 (!fdname_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", opts.arg); - if (free_and_strdup(&arg_fdname, arg) < 0) + if (free_and_strdup(&arg_fdname, opts.arg) < 0) return log_oom(); break; @@ -278,7 +277,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); + char **args = option_parser_get_args(&opts); switch (arg_action) { diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b1c8defb6c46a..0a99c14e45141 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -602,11 +602,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) { + FOREACH_OPTION(c, &opts, /* on_error= */ return c) { switch (c) { OPTION_COMMON_HELP: @@ -630,19 +628,19 @@ static int parse_argv(int argc, char *argv[]) { * trusted → read files, do not override cmdline, trust all */ - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) { - if (streq(arg, "trusted")) { + if (streq(opts.arg, "trusted")) { mask_all_settings = false; mask_no_settings = false; arg_settings_trusted = true; - } else if (streq(arg, "override")) { + } else if (streq(opts.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); + return log_error_errno(r, "Failed to parse --settings= argument: %s", opts.arg); } else if (r > 0) { /* yes */ mask_all_settings = false; @@ -668,7 +666,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Image"): {} OPTION('D', "directory", "PATH", "Root directory for the container"): - r = parse_path_argument(arg, false, &arg_directory); + r = parse_path_argument(opts.arg, false, &arg_directory); if (r < 0) return r; arg_settings_mask |= SETTING_DIRECTORY; @@ -676,7 +674,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("template", "PATH", "Initialize root directory from template directory, if missing"): - r = parse_path_argument(arg, false, &arg_template); + r = parse_path_argument(opts.arg, false, &arg_template); if (r < 0) return r; arg_settings_mask |= SETTING_DIRECTORY; @@ -690,27 +688,27 @@ static int parse_argv(int argc, char *argv[]) { OPTION('i', "image", "PATH", "Root file system disk image (or device node) for the container"): - r = parse_path_argument(arg, false, &arg_image); + r = parse_path_argument(opts.arg, false, &arg_image); if (r < 0) return r; arg_settings_mask |= SETTING_DIRECTORY; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("mstack", "PATH", /* help= */ NULL): - r = parse_path_argument(arg, false, &arg_mstack); + r = parse_path_argument(opts.arg, false, &arg_mstack); if (r < 0) return r; arg_settings_mask |= SETTING_DIRECTORY; break; OPTION_LONG("oci-bundle", "PATH", "OCI bundle directory"): - r = parse_path_argument(arg, false, &arg_oci_bundle); + r = parse_path_argument(opts.arg, false, &arg_oci_bundle); if (r < 0) return r; break; @@ -721,17 +719,17 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "volatile", "MODE", "Run the system in volatile mode"): - if (!arg) + if (!opts.arg) arg_volatile_mode = VOLATILE_YES; - else if (streq(arg, "help")) + else if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(volatile_mode, VolatileMode, _VOLATILE_MODE_MAX); else { VolatileMode m; - m = volatile_mode_from_string(arg); + m = volatile_mode_from_string(opts.arg); if (m < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --volatile= argument: %s", arg); + "Failed to parse --volatile= argument: %s", opts.arg); else arg_volatile_mode = m; } @@ -741,11 +739,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("root-hash", "HASH", "Specify verity root hash for root disk image"): { _cleanup_(iovec_done) struct iovec k = {}; - r = unhexmem(arg, &k.iov_base, &k.iov_len); + r = unhexmem(opts.arg, &k.iov_base, &k.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash: %s", arg); + return log_error_errno(r, "Failed to parse root hash: %s", opts.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); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash must be at least 128-bit long: %s", opts.arg); iovec_done(&arg_verity_settings.root_hash); arg_verity_settings.root_hash = TAKE_STRUCT(k); @@ -757,15 +755,15 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_(iovec_done) struct iovec p = {}; const char *value; - if ((value = startswith(arg, "base64:"))) { + if ((value = startswith(opts.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); + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", opts.arg); } else { - r = read_full_file(arg, (char**) &p.iov_base, &p.iov_len); + r = read_full_file(opts.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); + return log_error_errno(r, "Failed to parse root hash signature file '%s': %m", opts.arg); } iovec_done(&arg_verity_settings.root_hash_sig); @@ -774,16 +772,16 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("verity-data", "PATH", "Specify hash device for verity"): - r = parse_path_argument(arg, false, &arg_verity_settings.data_path); + r = parse_path_argument(opts.arg, false, &arg_verity_settings.data_path); if (r < 0) return r; break; 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); + r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", arg); + return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", opts.arg); arg_settings_mask |= SETTING_PIVOT_ROOT; break; @@ -808,13 +806,13 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("chdir", "PATH", "Set working directory in the container"): { _cleanup_free_ char *wd = NULL; - if (!path_is_absolute(arg)) + if (!path_is_absolute(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Working directory %s is not an absolute path.", arg); + "Working directory %s is not an absolute path.", opts.arg); - r = path_simplify_alloc(arg, &wd); + r = path_simplify_alloc(opts.arg, &wd); if (r < 0) - return log_error_errno(r, "Failed to simplify path %s: %m", arg); + return log_error_errno(r, "Failed to simplify path %s: %m", opts.arg); if (!path_is_normalized(wd)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); @@ -828,38 +826,38 @@ static int parse_argv(int argc, char *argv[]) { } OPTION('E', "setenv", "NAME[=VALUE]", "Pass an environment variable to PID 1"): - r = strv_env_replace_strdup_passthrough(&arg_setenv, arg); + r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.arg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg); arg_settings_mask |= SETTING_ENVIRONMENT; break; OPTION('u', "uid", "USER", "Run the command under specified user or UID"): - r = free_and_strdup(&arg_user, arg); + r = free_and_strdup(&arg_user, opts.arg); if (r < 0) return log_oom(); arg_settings_mask |= SETTING_USER; break; OPTION_LONG("kill-signal", "SIGNAL", "Select signal to use for shutting down PID 1"): - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(signal, int, _NSIG); - arg_kill_signal = signal_from_string(arg); + arg_kill_signal = signal_from_string(opts.arg); if (arg_kill_signal < 0) - return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", arg); + return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", opts.arg); arg_settings_mask |= SETTING_KILL_SIGNAL; break; OPTION_LONG("notify-ready", "BOOLEAN", "Receive notifications from the child init process"): - r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); + r = parse_boolean_argument("--notify-ready=", opts.arg, &arg_notify_ready); if (r < 0) return r; arg_settings_mask |= SETTING_NOTIFY_READY; break; OPTION_LONG("suppress-sync", "BOOLEAN", "Suppress any form of disk data synchronization"): - r = parse_boolean_argument("--suppress-sync=", arg, &arg_suppress_sync); + r = parse_boolean_argument("--suppress-sync=", opts.arg, &arg_suppress_sync); if (r < 0) return r; arg_settings_mask |= SETTING_SUPPRESS_SYNC; @@ -868,31 +866,31 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("System Identity"): {} OPTION('M', "machine", "NAME", "Set the machine name for the container"): - if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + if (!isempty(opts.arg) && !hostname_is_valid(opts.arg, /* flags= */ 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", arg); - r = free_and_strdup_warn(&arg_machine, arg); + "Invalid machine name: %s", opts.arg); + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; break; OPTION_LONG("hostname", "NAME", "Override the hostname for the container"): - if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + if (!isempty(opts.arg) && !hostname_is_valid(opts.arg, /* flags= */ 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid hostname: %s", arg); - r = free_and_strdup_warn(&arg_hostname, arg); + "Invalid hostname: %s", opts.arg); + r = free_and_strdup_warn(&arg_hostname, opts.arg); if (r < 0) return r; arg_settings_mask |= SETTING_HOSTNAME; break; OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the container"): - r = id128_from_string_nonzero(arg, &arg_uuid); + r = id128_from_string_nonzero(opts.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); + return log_error_errno(r, "Invalid UUID: %s", opts.arg); arg_settings_mask |= SETTING_MACHINE_ID; break; @@ -901,7 +899,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION('S', "slice", "SLICE", "Place the container in the specified slice"): { _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); + r = unit_name_mangle_with_suffix(opts.arg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) return log_oom(); @@ -911,12 +909,12 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): - if (strv_extend(&arg_property, arg) < 0) + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); break; OPTION_LONG("register", "BOOLEAN", "Register container as machine"): - r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); + r = parse_tristate_argument_with_auto("--register=", opts.arg, &arg_register); if (r < 0) return r; break; @@ -930,7 +928,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_private_users(opts.arg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); if (r < 0) return r; arg_settings_mask |= SETTING_USERNS; @@ -938,12 +936,12 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("private-users-ownership", "MODE", "Adjust ('chown') or map ('map') OS tree ownership to private UID/GID range"): - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); - arg_userns_ownership = user_namespace_ownership_from_string(arg); + arg_userns_ownership = user_namespace_ownership_from_string(opts.arg); if (arg_userns_ownership < 0) - return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", arg); + return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", opts.arg); arg_settings_mask |= SETTING_USERNS; break; @@ -954,9 +952,9 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = safe_atou(opts.arg, &arg_delegate_container_ranges); if (r < 0) - return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", opts.arg); arg_settings_mask |= SETTING_USERNS; break; @@ -979,7 +977,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("network-interface", "HOSTIF[:CONTAINERIF]", "Assign an existing network interface to the container"): - r = interface_pair_parse(&arg_network_interfaces, arg); + r = interface_pair_parse(&arg_network_interfaces, opts.arg); if (r < 0) return r; arg_private_network = true; @@ -988,7 +986,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = macvlan_pair_parse(&arg_network_macvlan, opts.arg); if (r < 0) return r; arg_private_network = true; @@ -997,7 +995,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = ipvlan_pair_parse(&arg_network_ipvlan, opts.arg); if (r < 0) return r; arg_private_network = true; @@ -1013,19 +1011,19 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = veth_extra_parse(&arg_network_veth_extra, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", opts.arg); arg_private_network = true; arg_settings_mask |= SETTING_NETWORK; break; 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)) + if (!ifname_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Bridge interface name not valid: %s", arg); - r = free_and_strdup(&arg_network_bridge, arg); + "Bridge interface name not valid: %s", opts.arg); + r = free_and_strdup(&arg_network_bridge, opts.arg); if (r < 0) return log_oom(); arg_network_veth = true; @@ -1037,7 +1035,7 @@ static int parse_argv(int argc, char *argv[]) { "Similar, but attach the new interface to an automatically managed bridge interface"): { _cleanup_free_ char *j = NULL; - j = strjoin("vz-", arg); + j = strjoin("vz-", opts.arg); if (!j) return log_oom(); @@ -1054,7 +1052,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, false, &arg_network_namespace_path); if (r < 0) return r; arg_settings_mask |= SETTING_NETWORK; @@ -1062,11 +1060,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION('p', "port", "[PROTOCOL:]HOSTPORT[:CONTAINERPORT]", "Expose a container IP port on the host"): - r = expose_port_parse(&arg_expose_ports, arg); + r = expose_port_parse(&arg_expose_ports, opts.arg); if (r == -EEXIST) - return log_error_errno(r, "Duplicate port specification: %s", arg); + return log_error_errno(r, "Duplicate port specification: %s", opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse host port %s: %m", arg); + return log_error_errno(r, "Failed to parse host port %s: %m", opts.arg); arg_settings_mask |= SETTING_EXPOSE_PORTS; break; @@ -1077,11 +1075,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("drop-capability", "CAP", "Drop the specified capability from the default set"): { uint64_t m; - r = parse_capability_spec(arg, &m); + r = parse_capability_spec(opts.arg, &m); if (r <= 0) return r; - if (streq(opt->long_code, "capability")) + if (streq(opts.opt->long_code, "capability")) plus |= m; else minus |= m; @@ -1092,7 +1090,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("ambient-capability", "CAP", "Sets the specified capability for the started process"): { uint64_t m; - r = parse_capability_spec(arg, &m); + r = parse_capability_spec(opts.arg, &m); if (r <= 0) return r; arg_caps_ambient |= m; @@ -1102,7 +1100,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--no-new-privileges=", opts.arg, &arg_no_new_privileges); if (r < 0) return r; arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; @@ -1113,8 +1111,8 @@ static int parse_argv(int argc, char *argv[]) { bool negative; const char *items; - negative = arg[0] == '~'; - items = negative ? arg + 1 : arg; + negative = opts.arg[0] == '~'; + items = negative ? opts.arg + 1 : opts.arg; for (;;) { _cleanup_free_ char *word = NULL; @@ -1148,12 +1146,12 @@ static int parse_argv(int argc, char *argv[]) { OPTION('Z', "selinux-context", "SECLABEL", "Set the SELinux security context to be used by processes in the container"): - arg_selinux_context = arg; + arg_selinux_context = opts.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; + arg_selinux_apifs_context = opts.arg; break; OPTION_GROUP("Resources"): {} @@ -1163,15 +1161,15 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_free_ char *name = NULL; int rl; - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(rlimit, int, _RLIMIT_MAX); - eq = strchr(arg, '='); + eq = strchr(opts.arg, '='); if (!eq) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--rlimit= expects an '=' assignment."); - name = strndup(arg, eq - arg); + name = strndup(opts.arg, eq - opts.arg); if (!name) return log_oom(); @@ -1194,9 +1192,9 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("oom-score-adjust", "VALUE", "Adjust the OOM score value for the payload"): - r = parse_oom_score_adjust(arg, &arg_oom_score_adjust); + r = parse_oom_score_adjust(opts.arg, &arg_oom_score_adjust); if (r < 0) - return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", opts.arg); arg_oom_score_adjust_set = true; arg_settings_mask |= SETTING_OOM_SCORE_ADJUST; break; @@ -1204,9 +1202,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("cpu-affinity", "CPUS", "Adjust the CPU affinity of the container"): { CPUSet cpuset; - r = parse_cpu_set(arg, &cpuset); + r = parse_cpu_set(opts.arg, &cpuset); if (r < 0) - return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", arg); + return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", opts.arg); cpu_set_done_and_replace(arg_cpu_set, cpuset); arg_settings_mask |= SETTING_CPU_AFFINITY; @@ -1214,42 +1212,42 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("personality", "ARCH", "Pick personality for this container"): - arg_personality = personality_from_string(arg); + arg_personality = personality_from_string(opts.arg); if (arg_personality == PERSONALITY_INVALID) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown or unsupported personality '%s'.", arg); + "Unknown or unsupported personality '%s'.", opts.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")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(resolv_conf_mode, ResolvConfMode, _RESOLV_CONF_MODE_MAX); - arg_resolv_conf = resolv_conf_mode_from_string(arg); + arg_resolv_conf = resolv_conf_mode_from_string(opts.arg); if (arg_resolv_conf < 0) return log_error_errno(arg_resolv_conf, - "Failed to parse /etc/resolv.conf mode: %s", arg); + "Failed to parse /etc/resolv.conf mode: %s", opts.arg); arg_settings_mask |= SETTING_RESOLV_CONF; break; OPTION_LONG("timezone", "MODE", "Select mode of /etc/localtime initialization"): - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(timezone_mode, TimezoneMode, _TIMEZONE_MODE_MAX); - arg_timezone = timezone_mode_from_string(arg); + arg_timezone = timezone_mode_from_string(opts.arg); if (arg_timezone < 0) return log_error_errno(arg_timezone, - "Failed to parse /etc/localtime mode: %s", arg); + "Failed to parse /etc/localtime mode: %s", opts.arg); arg_settings_mask |= SETTING_TIMEZONE; break; 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); + r = parse_link_journal(opts.arg, &arg_link_journal, &arg_link_journal_try); if (r < 0) - return log_error_errno(r, "Failed to parse link journal mode %s", arg); + return log_error_errno(r, "Failed to parse link journal mode %s", opts.arg); arg_settings_mask |= SETTING_LINK_JOURNAL; break; @@ -1260,31 +1258,31 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.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); + r = parse_size(opts.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); + r = parse_size(opts.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); + r = parse_size(opts.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); + r = safe_atou64(opts.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; @@ -1295,26 +1293,26 @@ static int parse_argv(int argc, char *argv[]) { "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")); + r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, opts.arg, + streq(opts.opt->long_code, "bind-ro")); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", arg); + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", opts.arg); arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; 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); + r = inaccessible_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", arg); + return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", opts.arg); arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; 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); + r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", arg); + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", opts.arg); arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; @@ -1322,19 +1320,19 @@ static int parse_argv(int argc, char *argv[]) { "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")); + r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, opts.arg, + streq(opts.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); + return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", opts.arg); arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; 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) + if (!valid_user_group_name(opts.arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg); + if (strv_extend(&arg_bind_user, opts.arg) < 0) return log_oom(); arg_settings_mask |= SETTING_BIND_USER; break; @@ -1343,11 +1341,11 @@ static int parse_argv(int argc, char *argv[]) { "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(arg, &sh, ©); + r = parse_user_shell(opts.arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", arg); + return log_error_errno(r, "Invalid user shell to bind: %s", opts.arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; @@ -1357,9 +1355,9 @@ static int parse_argv(int argc, char *argv[]) { 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) + if (!valid_user_group_name(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", opts.arg); + if (strv_extend(&arg_bind_user_groups, opts.arg) < 0) return log_oom(); break; @@ -1367,12 +1365,12 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("console", "MODE", "Select how stdin/stdout/stderr and /dev/console are set up for the container"): - if (streq(arg, "help")) + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); - arg_console_mode = console_mode_from_string(arg); + arg_console_mode = console_mode_from_string(opts.arg); if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Unknown console mode: %s", arg); + return log_error_errno(arg_console_mode, "Unknown console mode: %s", opts.arg); arg_settings_mask |= SETTING_CONSOLE_MODE; break; @@ -1382,7 +1380,7 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("background", "COLOR", "Set ANSI color for background"): - r = parse_background_argument(arg, &arg_background); + r = parse_background_argument(opts.arg, &arg_background); if (r < 0) return r; break; @@ -1391,7 +1389,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to container"): - r = machine_credential_set(&arg_credentials, arg); + r = machine_credential_set(&arg_credentials, opts.arg); if (r < 0) return r; arg_settings_mask |= SETTING_CREDENTIALS; @@ -1399,7 +1397,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = machine_credential_load(&arg_credentials, opts.arg); if (r < 0) return r; arg_settings_mask |= SETTING_CREDENTIALS; @@ -1413,24 +1411,24 @@ 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 (opts.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 + * with the space-separated form (--user NAME), if the next opts.arg does not look * like an option, interpret it as a user name. */ - const char *t = option_parser_next_arg(&state); + const char *t = option_parser_next_arg(&opts); if (t && t[0] != '-') { - arg = option_parser_consume_next_arg(&state); + opts.arg = option_parser_consume_next_arg(&opts); log_warning("--user NAME is deprecated, use --uid=NAME instead."); } } - if (arg) { - r = free_and_strdup(&arg_user, arg); + if (opts.arg) { + r = free_and_strdup(&arg_user, opts.arg); if (r < 0) return log_oom(); arg_settings_mask |= SETTING_USER; @@ -1444,7 +1442,7 @@ static int parse_argv(int argc, char *argv[]) { } } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (!strv_isempty(args)) { strv_free(arg_parameters); arg_parameters = strv_copy(args); diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 51dfe1f1a40a3..b73e2eb5abfe5 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -91,10 +91,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -108,7 +107,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/oom/oomd.c b/src/oom/oomd.c index 54a3bb7a1d562..2250d7ec7f189 100644 --- a/src/oom/oomd.c +++ b/src/oom/oomd.c @@ -51,10 +51,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -72,12 +71,12 @@ static int parse_argv(int argc, char *argv[]) { "Write D-Bus XML introspection data"): return bus_introspect_implementations( stdout, - arg, + opts.arg, BUS_IMPLEMENTATIONS(&manager_object, &log_control_object)); } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 1920ff8d60028..22544b9463854 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -204,10 +204,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -217,7 +216,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); OPTION_LONG("suffix", "SUFFIX", "Suffix to append to paths"): - arg_suffix = arg; + arg_suffix = opts.arg; break; OPTION_COMMON_NO_PAGER: @@ -225,7 +224,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index c92d3f981124a..5b846b9d3a9dc 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -81,11 +81,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -101,9 +100,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return r; - implementation = sym_EVP_get_digestbyname(arg); + implementation = sym_EVP_get_digestbyname(opts.arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", opts.arg); if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); @@ -112,23 +111,23 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { } OPTION_LONG("pcr", "INDEX", "Select TPM PCR index (0…23)"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_pcr_mask = 0; break; } - r = tpm2_pcr_index_from_string(arg); + r = tpm2_pcr_index_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse PCR index: %s", arg); + return log_error_errno(r, "Failed to parse PCR index: %s", opts.arg); arg_pcr_mask |= INDEX_TO_MASK(uint32_t, r); break; 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); + if (!tpm2_nvpcr_name_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", opts.arg); - r = free_and_strdup_warn(&arg_nvpcr_name, arg); + r = free_and_strdup_warn(&arg_nvpcr_name, opts.arg); if (r < 0) return r; break; @@ -136,11 +135,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("tpm2-device", "PATH", "Use specified TPM2 device"): { _cleanup_free_ char *device = NULL; - if (streq(arg, "list")) + if (streq(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -156,7 +155,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_file_system); if (r < 0) return r; break; @@ -177,12 +176,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("event-type", "TYPE", "Event type to include in the event log"): - if (streq(arg, "help")) + if (streq(opts.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(arg); + arg_event_type = tpm2_userspace_event_type_from_string(opts.arg); if (arg_event_type < 0) - return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", arg); + return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", opts.arg); break; } @@ -208,7 +207,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index ecf7b18c351bf..752f67cbdb990 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5189,12 +5189,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; bool auto_location = true; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -5208,7 +5207,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -5220,21 +5219,21 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("pcr", "NR", "Generate .pcrlock for specified PCR"): - r = tpm2_parse_pcr_argument_to_mask(arg, &arg_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(opts.arg, &arg_pcr_mask); if (r < 0) - return log_error_errno(r, "Failed to parse PCR specification: %s", arg); + return log_error_errno(r, "Failed to parse PCR specification: %s", opts.arg); break; OPTION_LONG("nv-index", "NUMBER", "Use the specified NV index, instead of a random one"): - if (isempty(arg)) + if (isempty(opts.arg)) arg_nv_index = 0; else { uint32_t u; - r = safe_atou32_full(arg, 16, &u); + r = safe_atou32_full(opts.arg, 16, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --nv-index= argument: %s", arg); + return log_error_errno(r, "Failed to parse --nv-index= argument: %s", opts.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, @@ -5248,7 +5247,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Directory to read .pcrlock files from"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(arg, /* suppress_root= */ false, &p); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -5266,15 +5265,15 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { auto_location = false; - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_location_start = mfree(arg_location_start); arg_location_end = mfree(arg_location_end); break; } - e = strchr(arg, ':'); + e = strchr(opts.arg, ':'); if (e) { - start = strndup(arg, e - arg); + start = strndup(opts.arg, e - opts.arg); if (!start) return log_oom(); @@ -5282,11 +5281,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (!end) return log_oom(); } else { - start = strdup(arg); + start = strdup(opts.arg); if (!start) return log_oom(); - end = strdup(arg); + end = strdup(opts.arg); if (!end) return log_oom(); } @@ -5303,17 +5302,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + arg_recovery_pin = recovery_pin_mode_from_string(opts.arg); if (arg_recovery_pin < 0) - return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", arg); + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", opts.arg); break; OPTION_LONG("pcrlock", "PATH", ".pcrlock file to write expected PCR measurement to"): - if (empty_or_dash(arg)) + if (empty_or_dash(opts.arg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_pcrlock_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_pcrlock_path); if (r < 0) return r; } @@ -5323,10 +5322,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("policy", "PATH", "JSON file to write policy output to"): - if (empty_or_dash(arg)) + if (empty_or_dash(opts.arg)) arg_policy_path = mfree(arg_policy_path); else { - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_policy_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_policy_path); if (r < 0) return r; } @@ -5341,7 +5340,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; @@ -5372,7 +5371,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_pager_flags |= PAGER_DISABLE; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index 089c84a40c33c..6d98a8e7ef09e 100644 --- a/src/ptyfwd/ptyfwd-tool.c +++ b/src/ptyfwd/ptyfwd-tool.c @@ -62,11 +62,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -84,22 +83,22 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; OPTION_LONG("background", "COLOR", "Set ANSI color for background"): - r = parse_background_argument(arg, &arg_background); + r = parse_background_argument(opts.arg, &arg_background); if (r < 0) return r; break; OPTION_LONG("title", "TITLE", "Set terminal title"): - r = free_and_strdup_warn(&arg_title, arg); + r = free_and_strdup_warn(&arg_title, opts.arg); if (r < 0) return r; break; } - if (option_parser_get_n_args(&state) == 0) + if (option_parser_get_n_args(&opts) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line, refusing."); - *remaining_args = option_parser_get_args(&state); + *remaining_args = option_parser_get_args(&opts); return 1; } diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index 3e40fcdddaf36..2eabcea176c2a 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -349,11 +349,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -363,7 +362,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - r = dispatch_verb_with_args(option_parser_get_args(&state), NULL); + r = dispatch_verb_with_args(option_parser_get_args(&opts), NULL); if (r < 0) return r; diff --git a/src/repart/repart.c b/src/repart/repart.c index 0aab755d2cf25..2d35da28c2b12 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9684,12 +9684,11 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; bool auto_public_key_pcr_mask = true, auto_pcrlock = true; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_GROUP("Options"): {} @@ -9712,41 +9711,41 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("dry-run", "BOOL", "Whether to run dry-run operation"): - r = parse_boolean_argument("--dry-run=", arg, &arg_dry_run); + r = parse_boolean_argument("--dry-run=", opts.arg, &arg_dry_run); if (r < 0) return r; break; OPTION_LONG("empty", "MODE", "How to handle empty disks lacking partition tables (refuse, allow, require, force, create)"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_empty = EMPTY_UNSET; break; } - arg_empty = empty_mode_from_string(arg); + arg_empty = empty_mode_from_string(opts.arg); if (arg_empty < 0) - return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", arg); + return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", opts.arg); break; OPTION_LONG("offline", "BOOL", "Whether to build the image offline"): - r = parse_tristate_argument_with_auto("--offline=", arg, &arg_offline); + r = parse_tristate_argument_with_auto("--offline=", opts.arg, &arg_offline); if (r < 0) return r; break; OPTION_LONG("discard", "BOOL", "Whether to discard backing blocks for new partitions"): - r = parse_boolean_argument("--discard=", arg, &arg_discard); + r = parse_boolean_argument("--discard=", opts.arg, &arg_discard); if (r < 0) return r; break; OPTION_LONG("sector-size", "SIZE", "Set the logical sector size for the image"): - r = parse_sector_size(arg, &arg_sector_size); + r = parse_sector_size(opts.arg, &arg_sector_size); if (r < 0) return r; @@ -9754,9 +9753,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("grain-size", "BYTES", "Set the grain size for partition alignment"): - r = parse_size(arg, 1024, &arg_grain_size); + r = parse_size(opts.arg, 1024, &arg_grain_size); if (r < 0) - return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", opts.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."); @@ -9764,9 +9763,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("architecture", "ARCH", "Set the generic architecture for the image"): - r = architecture_from_string(arg); + r = architecture_from_string(opts.arg); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", opts.arg); arg_architecture = r; break; @@ -9775,15 +9774,15 @@ static int parse_argv(int argc, char *argv[]) { "Grow loopback file to specified size"): { uint64_t parsed, rounded; - if (streq(arg, "auto")) { + if (streq(opts.arg, "auto")) { arg_size = UINT64_MAX; arg_size_auto = true; break; } - r = parse_size(arg, 1024, &parsed); + r = parse_size(opts.arg, 1024, &parsed); if (r < 0) - return log_error_errno(r, "Failed to parse --size= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --size= parameter: %s", opts.arg); rounded = round_up_size(parsed, 4096); if (rounded == 0) @@ -9802,15 +9801,15 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("seed", "UUID", "128-bit seed UUID to derive all UUIDs from"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_seed = SD_ID128_NULL; arg_randomize = false; - } else if (streq(arg, "random")) + } else if (streq(opts.arg, "random")) arg_randomize = true; else { - r = sd_id128_from_string(arg, &arg_seed); + r = sd_id128_from_string(opts.arg, &arg_seed); if (r < 0) - return log_error_errno(r, "Failed to parse seed: %s", arg); + return log_error_errno(r, "Failed to parse seed: %s", opts.arg); arg_randomize = false; } @@ -9819,7 +9818,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("split", "BOOL", "Whether to generate split artifacts"): - r = parse_boolean_argument("--split=", arg, NULL); + r = parse_boolean_argument("--split=", opts.arg, NULL); if (r < 0) return r; @@ -9830,14 +9829,14 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("pretty", "BOOL", "Whether to show pretty summary before doing changes"): - r = parse_boolean_argument("--pretty=", arg, NULL); + r = parse_boolean_argument("--pretty=", opts.arg, NULL); if (r < 0) return r; arg_pretty = r; break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; @@ -9847,7 +9846,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("factory-reset", "BOOL", "Whether to remove data partitions before recreating them"): - r = parse_boolean_argument("--factory-reset=", arg, NULL); + r = parse_boolean_argument("--factory-reset=", opts.arg, NULL); if (r < 0) return r; arg_factory_reset = r; @@ -9862,14 +9861,14 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("root", "PATH", "Operate relative to root path"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate relative to image file"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; @@ -9879,7 +9878,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; @@ -9887,7 +9886,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("definitions", "DIR", "Find partition definitions in specified directory"): { _cleanup_free_ char *path = NULL; - r = parse_path_argument(arg, false, &path); + r = parse_path_argument(opts.arg, false, &path); if (r < 0) return r; if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) @@ -9906,14 +9905,14 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = free_and_strdup_warn(&arg_private_key, opts.arg); if (r < 0) return r; break; OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - arg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -9921,14 +9920,14 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_COMMON_CERTIFICATE("PEM certificate to use when generating verity roothash signatures"): - r = free_and_strdup_warn(&arg_certificate, arg); + r = free_and_strdup_warn(&arg_certificate, opts.arg); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - arg, + opts.arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) @@ -9939,7 +9938,7 @@ static int parse_argv(int argc, char *argv[]) { "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); + r = parse_join_signature(opts.arg, &arg_verity_settings); if (r < 0) return r; break; @@ -9948,7 +9947,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("key-file", "PATH", "Key to use when encrypting partitions"): - r = parse_key_file(arg, &arg_key); + r = parse_key_file(opts.arg, &arg_key); if (r < 0) return r; break; @@ -9957,11 +9956,11 @@ static int parse_argv(int argc, char *argv[]) { "Path to TPM2 device node to use"): { _cleanup_free_ char *device = NULL; - if (streq(arg, "list")) + if (streq(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(arg, "auto")) { - device = strdup(arg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -9973,7 +9972,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; @@ -9981,15 +9980,15 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = safe_atou32_full(opts.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", arg); + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", opts.arg); break; 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); + r = tpm2_parse_pcr_argument_append(opts.arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; @@ -9997,7 +9996,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; @@ -10006,7 +10005,7 @@ static int parse_argv(int argc, char *argv[]) { 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(arg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(opts.arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; @@ -10014,7 +10013,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("tpm2-pcrlock", "PATH", "Specify pcrlock policy to lock against"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; @@ -10029,7 +10028,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(opts.arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10043,7 +10042,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(opts.arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10053,7 +10052,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_partition_types(opts.arg, &arg_defer_partitions, &arg_n_defer_partitions); if (r < 0) return r; @@ -10061,7 +10060,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--defer-partitions-empty=", opts.arg, &arg_defer_partitions_empty); if (r < 0) return r; @@ -10069,7 +10068,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_boolean_argument("--defer-partitions-factory-reset=", opts.arg, &arg_defer_partitions_factory_reset); if (r < 0) return r; @@ -10079,7 +10078,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_copy_source); if (r < 0) return r; break; @@ -10088,7 +10087,7 @@ static int parse_argv(int argc, char *argv[]) { "Copy partitions from the given image"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(arg, /* suppress_root= */ false, &p); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -10102,10 +10101,10 @@ static int parse_argv(int argc, char *argv[]) { 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); + if (!filename_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", opts.arg); - r = free_and_strdup_warn(&arg_make_ddi, arg); + r = free_and_strdup_warn(&arg_make_ddi, opts.arg); if (r < 0) return r; break; @@ -10133,26 +10132,26 @@ static int parse_argv(int argc, char *argv[]) { 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)) { + if (isempty(opts.arg)) { arg_append_fstab = APPEND_AUTO; break; } - arg_append_fstab = append_mode_from_string(arg); + arg_append_fstab = append_mode_from_string(opts.arg); if (arg_append_fstab < 0) - return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", arg); + return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", opts.arg); break; OPTION_LONG("generate-fstab", "PATH", "Write fstab configuration to the given path"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_fstab); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_generate_fstab); if (r < 0) return r; break; OPTION_LONG("generate-crypttab", "PATH", "Write crypttab configuration to the given path"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_crypttab); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_generate_crypttab); if (r < 0) return r; break; @@ -10161,7 +10160,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("el-torito", "BOOL", "Whether to add a boot catalog to boot the ESP"): - r = parse_boolean_argument("--el-torito=", arg, &arg_eltorito); + r = parse_boolean_argument("--el-torito=", opts.arg, &arg_eltorito); if (r < 0) return r; @@ -10169,10 +10168,10 @@ static int parse_argv(int argc, char *argv[]) { 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); + if (!iso9660_system_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", opts.arg); - r = free_and_strdup_warn(&arg_eltorito_system, arg); + r = free_and_strdup_warn(&arg_eltorito_system, opts.arg); if (r < 0) return r; @@ -10180,10 +10179,10 @@ static int parse_argv(int argc, char *argv[]) { 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); + if (!iso9660_volume_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", opts.arg); - r = free_and_strdup_warn(&arg_eltorito_volume, arg); + r = free_and_strdup_warn(&arg_eltorito_volume, opts.arg); if (r < 0) return r; @@ -10191,17 +10190,17 @@ static int parse_argv(int argc, char *argv[]) { 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); + if (!iso9660_publisher_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", opts.arg); - r = free_and_strdup_warn(&arg_eltorito_publisher, arg); + r = free_and_strdup_warn(&arg_eltorito_publisher, opts.arg); if (r < 0) return r; break; } - if (option_parser_get_n_args(&state) > 1) + if (option_parser_get_n_args(&opts) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected at most one argument, the path to the block device or image file."); @@ -10281,7 +10280,7 @@ static int parse_argv(int argc, char *argv[]) { arg_relax_copy_block_security = true; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (!strv_isempty(args)) { if (empty_or_dash(args[0])) arg_node_none = true; diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index ea82dbd42fe26..1e2eca31eae68 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -57,9 +57,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - if (state.optind < argc) + if (opts.optind < argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/report/report.c b/src/report/report.c index c45c4da7ea63e..fef01c094ef9f 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -985,10 +985,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -1013,7 +1012,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -1024,49 +1023,49 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("url", "URL", "Upload to this address"): - r = free_and_strdup_warn(&arg_url, arg); + r = free_and_strdup_warn(&arg_url, opts.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); + r = free_and_strdup_warn(&arg_key, opts.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); + r = free_and_strdup_warn(&arg_cert, opts.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); + r = free_and_strdup_warn(&arg_trust, opts.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); + r = parse_sec(opts.arg, &arg_network_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse --network-timeout value: %s", arg); + return log_error_errno(r, "Failed to parse --network-timeout value: %s", opts.arg); break; OPTION_LONG("extra-header", "NAME: VALUE", "Inject additional header into the upload request"): - if (isempty(arg)) { + if (isempty(opts.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 (!http_header_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", opts.arg); - if (strv_extend(&arg_extra_headers, arg) < 0) + if (strv_extend(&arg_extra_headers, opts.arg) < 0) return log_oom(); break; } @@ -1074,7 +1073,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index 012f39dcc3094..7d866fde87555 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -93,11 +93,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -108,21 +107,21 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("output", "PATH", "Where to write the signed PE binary"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): - r = free_and_strdup_warn(&arg_certificate, arg); + r = free_and_strdup_warn(&arg_certificate, opts.arg); if (r < 0) return r; break; OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - arg, + opts.arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) @@ -130,7 +129,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): - r = free_and_strdup_warn(&arg_private_key, arg); + r = free_and_strdup_warn(&arg_private_key, opts.arg); if (r < 0) return r; @@ -138,7 +137,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - arg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -151,14 +150,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("signed-data", "PATH", /* help= */ NULL): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_signed_data); if (r < 0) return r; break; OPTION_LONG("signed-data-signature", "PATH", /* help= */ NULL): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data_signature); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_signed_data_signature); if (r < 0) return r; @@ -174,7 +173,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/shared/options.c b/src/shared/options.c index a4c6514508c43..644c7ae27c3db 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -70,9 +70,7 @@ static int partial_match_error( int option_parse( const Option options[], const Option options_end[], - OptionParser *state, - const Option **ret_option, - const char **ret_arg) { + OptionParser *state) { /* Check and initialize */ if (state->optind == 0) { @@ -96,7 +94,7 @@ int option_parse( /* Handle non-option parameters */ for (;;) { if (state->optind == state->argc) - return 0; + goto finished; if (streq(state->argv[state->optind], "--")) { /* No more options. Move "--" before positional args so that @@ -106,7 +104,7 @@ int option_parse( } if (state->parsing_stopped) - return 0; + goto finished; if (state->argv[state->optind][0] == '-' && state->argv[state->optind][1] != '\0') @@ -115,7 +113,7 @@ int option_parse( if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) { state->parsing_stopped = true; - return 0; + goto finished; } if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) { @@ -254,17 +252,14 @@ int option_parse( 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; - - if (ret_arg) - *ret_arg = optval; - else - /* It's fine to omit ret_arg, but only if no options return a value. */ - assert(!optval); - + state->opt = option; + state->arg = optval; return option->id; + + finished: + state->opt = NULL; + state->arg = NULL; + return 0; } char* option_parser_next_arg(const OptionParser *state) { diff --git a/src/shared/options.h b/src/shared/options.h index 59b20bc047cd3..10878c746208e 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -147,26 +147,26 @@ typedef struct OptionParser { * 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. */ + + /* The two variables below encompass the state of the last option_parse() call. + * Before parsing has commenced, and after it has finished, they will be NULL. */ + const Option *opt; /* … the matched option or NULL */ + const char *arg; /* … the argument or NULL */ } OptionParser; int option_parse( const Option options[], const Option options_end[], - OptionParser *state, - const Option **ret_option, - const char **ret_arg); + OptionParser *state); /* Iterate over options. */ -#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; \ +#define FOREACH_OPTION(c, state, on_error) \ + for (int c; (c = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, state)) != 0; ) \ + if (c < 0) { \ + on_error; \ + break; \ } else -#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); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 5e6ff9f68f591..c572138596d38 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -63,33 +63,32 @@ static int parse_argv(int argc, char *argv[]) { /* The interface is: the verb must stay in argv[1]. Any extra positional arguments * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. */ - OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_LOG_LEVEL: - r = log_set_max_level_from_string(arg); + r = log_set_max_level_from_string(opts.arg); if (r < 0) - log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", opts.arg); break; OPTION_COMMON_LOG_TARGET: - r = log_set_target_from_string(arg); + r = log_set_target_from_string(opts.arg); if (r < 0) - log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", opts.arg); break; OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-color", "BOOL", "Highlight important messages"): - if (arg) { - r = log_show_color_from_string(arg); + if (opts.arg) { + r = log_show_color_from_string(opts.arg); if (r < 0) - log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", opts.arg); } else log_show_color(true); @@ -97,10 +96,10 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-location", "BOOL", "Include code location in messages"): - if (arg) { - r = log_show_location_from_string(arg); + if (opts.arg) { + r = log_show_location_from_string(opts.arg); if (r < 0) - log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", opts.arg); } else log_show_location(true); @@ -108,10 +107,10 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-time", "BOOL", "Prefix messages with current time"): - if (arg) { - r = log_show_time_from_string(arg); + if (opts.arg) { + r = log_show_time_from_string(opts.arg); if (r < 0) - log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", opts.arg); } else log_show_time(true); @@ -119,23 +118,23 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("exit-code", "N", "Exit code for reboot/kexec"): - r = safe_atou8(arg, &arg_exit_code); + r = safe_atou8(opts.arg, &arg_exit_code); if (r < 0) - log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", opts.arg); break; OPTION_LONG("timeout", "TIME", "Overall shutdown timeout"): - r = parse_sec(arg, &arg_timeout); + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", arg); + log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", opts.arg); break; OPTION_POSITIONAL: if (!arg_verb) - arg_verb = arg; + arg_verb = opts.arg; else log_warning("Got extraneous argument, ignoring."); break; diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 38b38c62f8da5..3b2f9d698bb84 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -729,9 +729,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -741,7 +741,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 2f0c81a1111d0..03cf327b6259e 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -355,11 +355,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -370,7 +369,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION('l', "listen", "ADDR", "Listen for raw connections at ADDR"): - r = strv_extend(&arg_listen, arg); + r = strv_extend(&arg_listen, opts.arg); if (r < 0) return log_oom(); @@ -402,16 +401,16 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { 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); + r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.arg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg); break; OPTION_LONG("fdname", "NAME[:NAME...]", "Specify names for file descriptors"): { _cleanup_strv_free_ char **names = NULL; - names = strv_split(arg, ":"); + names = strv_split(opts.arg, ":"); if (!names) return log_oom(); @@ -443,7 +442,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; } - *remaining_args = option_parser_get_args(&state); + *remaining_args = option_parser_get_args(&opts); if (strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: command to execute is missing.", diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 82250a0b06ff1..ea68009b35802 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -420,11 +420,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -435,9 +434,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION('c', "connections-max", "NUMBER", "Set the maximum number of connections to be accepted"): - r = safe_atou(arg, &arg_connections_max); + r = safe_atou(opts.arg, &arg_connections_max); if (r < 0) - return log_error_errno(r, "Failed to parse --connections-max= argument: %s", arg); + return log_error_errno(r, "Failed to parse --connections-max= argument: %s", opts.arg); if (arg_connections_max < 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -447,13 +446,13 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("exit-idle-time", "TIME", "Exit when without a connection for this duration"): - r = parse_sec(arg, &arg_exit_idle_time); + r = parse_sec(opts.arg, &arg_exit_idle_time); if (r < 0) - return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", arg); + return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", opts.arg); break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); size_t n = strv_length(args); if (n < 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough parameters."); diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index f1000d60684a9..ee128b5e1811c 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -159,12 +159,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg, *verb = NULL; + OptionParser opts = { argc, argv }; + const char *verb = NULL; int r; - FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -175,18 +174,18 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("make-vsock", NULL, /* help= */ NULL): {} OPTION_LONG("rm-vsock", NULL, /* help= */ NULL): - verb = opt->long_code; + verb = opts.opt->long_code; break; OPTION_LONG("issue-path", "PATH", "Change path to /run/issue.d/50-ssh-vsock.issue"): - if (empty_or_dash(arg)) { + if (empty_or_dash(opts.arg)) { arg_issue_path = mfree(arg_issue_path); arg_issue_stdout = true; break; } - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_issue_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_issue_path); if (r < 0) return r; @@ -202,12 +201,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { char **args; if (verb) { - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 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)); + args = strv_copy(option_parser_get_args(&opts)); if (!args) return log_oom(); diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 42f365ad3ec18..4be5205d59894 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -46,11 +46,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -69,11 +68,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION('p', "bus-path", "PATH", "Path to the bus address (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")"): - arg_bus_path = arg; + arg_bus_path = opts.arg; break; OPTION_COMMON_MACHINE: - r = parse_machine_argument(arg, &arg_bus_path, &arg_transport); + r = parse_machine_argument(opts.arg, &arg_bus_path, &arg_transport); if (r < 0) return r; break; @@ -83,7 +82,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index 2b0ed742c1a16..5129887d795fe 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -77,11 +77,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -92,10 +91,10 @@ static int parse_argv(int argc, char *argv[]) { 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 (!filename_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", opts.arg); - if (free_and_strdup(&arg_nqn, arg) < 0) + if (free_and_strdup(&arg_nqn, opts.arg) < 0) return log_oom(); break; @@ -113,7 +112,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (arg_all > 0) { if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expects no further arguments if --all/-a is specified."); diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index 39041ea384502..6a9e33e6e6f7b 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -360,10 +360,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -384,7 +383,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_LONG("prefix", "PATH", "Only apply rules with the specified prefix"): { - _cleanup_free_ char *normalized = strdup(arg); + _cleanup_free_ char *normalized = strdup(opts.arg); if (!normalized) return log_oom(); sysctl_normalize(normalized); @@ -416,7 +415,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; } - *remaining_args = option_parser_get_args(&state); + *remaining_args = option_parser_get_args(&opts); if (arg_cat_flags != CAT_CONFIG_OFF && !strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index a4bf835108ab6..648dd093e6160 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1862,11 +1862,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -1880,18 +1879,18 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION('C', "component", "NAME", "Select component to update"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_component = mfree(arg_component); break; } - r = component_name_valid(arg); + r = component_name_valid(opts.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", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg); - r = free_and_strdup_warn(&arg_component, arg); + r = free_and_strdup_warn(&arg_component, opts.arg); if (r < 0) return r; @@ -1899,35 +1898,35 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_LONG("definitions", "DIR", "Find transfer definitions in specified directory"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_definitions); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions); if (r < 0) return r; break; OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("transfer-source", "PATH", "Specify the directory to transfer sources from"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_transfer_source); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source); if (r < 0) return r; @@ -1935,15 +1934,15 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION('m', "instances-max", "INT", "How many instances to maintain"): - r = safe_atou64(arg, &arg_instances_max); + r = safe_atou64(opts.arg, &arg_instances_max); if (r < 0) - return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg); break; OPTION_LONG("sync", "BOOL", "Controls whether to sync data to disk"): - r = parse_boolean_argument("--sync=", arg, &arg_sync); + r = parse_boolean_argument("--sync=", opts.arg, &arg_sync); if (r < 0) return r; break; @@ -1952,7 +1951,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { "Force signature verification on or off"): { bool b; - r = parse_boolean_argument("--verify=", arg, &b); + r = parse_boolean_argument("--verify=", opts.arg, &b); if (r < 0) return r; @@ -1979,7 +1978,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; @@ -1995,7 +1994,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { if (arg_definitions && arg_component) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined."); - *remaining_args = option_parser_get_args(&state); + *remaining_args = option_parser_get_args(&opts); return 1; } diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index c6e5c33fdfe48..65d2c7675ed45 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1687,10 +1687,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"): @@ -1707,7 +1706,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = arg; + arg_host = opts.arg; break; OPTION_COMMON_NO_PAGER: @@ -1727,7 +1726,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return version(); } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index ff391cb316578..38fe4f4515161 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -2101,10 +2101,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_CAT_CONFIG: @@ -2124,32 +2123,32 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_GROUP("Options"): {} OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): - if (!path_is_absolute(arg)) + if (!path_is_absolute(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(arg, ".conf")) + if (!endswith(opts.arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = arg; + arg_replace = opts.arg; break; OPTION_LONG("dry-run", NULL, "Just print what would be done"): @@ -2165,8 +2164,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state); - size_t n_args = option_parser_get_n_args(&state); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 0e760aeb89676..daa8713f48009 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -37,18 +37,16 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argv); assert(ret_args); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); OPTION_LONG("root", "PATH", "Operate below specified root directory"): - arg_root = arg; + arg_root = opts.arg; break; OPTION_LONG("open", NULL, "Open the resolved path"): @@ -64,11 +62,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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; + arg_flags |= opts.opt->data; break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); if (strv_isempty(*ret_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); diff --git a/src/test/test-options.c b/src/test/test-options.c index 49e1b3069fc91..6a0744e67363d 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -32,34 +32,32 @@ 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, mode }; - const Option *opt; - const char *arg; - for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { + OptionParser opts = { argc, argv, mode }; + for (int c; (c = option_parse(options, options + n_options, &opts)) != 0; ) { ASSERT_OK(c); - ASSERT_NOT_NULL(opt); + ASSERT_NOT_NULL(opts.opt); log_debug("%c %s: %s=%s", - opt->short_code != 0 ? opt->short_code : ' ', - opt->long_code ?: "", - strnull(opt->metavar), strnull(arg)); + opts.opt->short_code != 0 ? opts.opt->short_code : ' ', + opts.opt->long_code ?: "", + strnull(opts.opt->metavar), strnull(opts.arg)); ASSERT_LT(i, n_entries); if (entries[i].long_code) - ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + ASSERT_TRUE(streq_ptr(opts.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)); + ASSERT_EQ(opts.opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(opts.arg, entries[i].argument)); i++; } ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); - ASSERT_EQ(option_parser_get_n_args(&state), strv_length(remaining)); + ASSERT_EQ(option_parser_get_n_args(&opts), strv_length(remaining)); } static void test_option_invalid_one( @@ -78,11 +76,9 @@ static void test_option_invalid_one( for (const Option *o = options; o->short_code != 0 || o->long_code; o++) n_options++; - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; - int c = option_parse(options, options + n_options, &state, &opt, &arg); + int c = option_parse(options, options + n_options, &opts); ASSERT_ERROR(c, EINVAL); } @@ -736,7 +732,7 @@ TEST(option_optional_arg) { } /* 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. */ + * by using them in a FOREACH_OPTION switch, as they would be used in real code. */ static void test_macros_parse_one( char **argv, @@ -756,27 +752,25 @@ 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, mode }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv, mode }; - FOREACH_OPTION_FULL(&state, c, &opt, &arg, ASSERT_TRUE(false)) { + FOREACH_OPTION(c, &opts, assert_not_reached()) { log_debug("%c %s: %s=%s", - opt->short_code != 0 ? opt->short_code : ' ', - opt->long_code ?: "", - strnull(opt->metavar), strnull(arg)); + opts.opt->short_code != 0 ? opts.opt->short_code : ' ', + opts.opt->long_code ?: "", + strnull(opts.opt->metavar), strnull(opts.arg)); ASSERT_LT(i, n_entries); if (entries[i].long_code) - ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + ASSERT_TRUE(streq_ptr(opts.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)); + ASSERT_EQ(opts.opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(opts.arg, entries[i].argument)); if (streq_ptr(entries[i].long_code, "optional2")) - ASSERT_EQ(opt->data, 666u); + ASSERT_EQ(opts.opt->data, 666u); else - ASSERT_EQ(opt->data, 0u); + ASSERT_EQ(opts.opt->data, 0u); i++; @@ -828,9 +822,11 @@ static void test_macros_parse_one( ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); } TEST(option_macros) { @@ -1202,19 +1198,19 @@ TEST(option_optional_arg_consume) { char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); int argc = strv_length(argv); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; - 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_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_STREQ(option_parser_next_arg(&opts), "someuser"); + ASSERT_STREQ(option_parser_consume_next_arg(&opts), "someuser"); - ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); - ASSERT_TRUE(strv_equal(option_parser_get_args(&state), STRV_MAKE("pos1"))); + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), STRV_MAKE("pos1"))); } /* --user at end of args: no next arg, so scope mode */ @@ -1222,19 +1218,19 @@ TEST(option_optional_arg_consume) { char **argv = STRV_MAKE("arg0", "--user"); int argc = strv_length(argv); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; - 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_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_consume_next_arg(&opts)); - ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); - ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + ASSERT_TRUE(strv_isempty(option_parser_get_args(&opts))); } /* --user followed by -u (option): scope mode, -u gets its own processing */ @@ -1242,24 +1238,24 @@ TEST(option_optional_arg_consume) { char **argv = STRV_MAKE("arg0", "--user", "-u", "nobody"); int argc = strv_length(argv); - OptionParser state = { argc, argv }; - const Option *opt; - const char *arg; + OptionParser opts = { argc, argv }; - 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, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_STREQ(option_parser_next_arg(&opts), "-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_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "uid"); + ASSERT_STREQ(opts.arg, "nobody"); + ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_consume_next_arg(&opts)); - ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); - ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + ASSERT_TRUE(strv_isempty(option_parser_get_args(&opts))); } /* "Functional test": --user followed by -u (option): scope mode, -u gets its own processing, @@ -1268,20 +1264,20 @@ TEST(option_optional_arg_consume) { 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; + OptionParser opts = { argc, argv }; int scope_seen = 0; int nobody_seen = 0; - for (int c; (c = option_parse(options, options + 3, &state, &opt, &arg)) != 0; ) { + for (int c; (c = option_parse(options, options + 3, &opts)) != 0; ) { ASSERT_OK(c); - if (streq_ptr(opt->long_code, "user")) { + if (streq_ptr(opts.opt->long_code, "user")) { + const char *arg = opts.arg; + if (!arg) { - const char *t = option_parser_next_arg(&state); + const char *t = option_parser_next_arg(&opts); if (t && t[0] != '-') - arg = option_parser_consume_next_arg(&state); + arg = option_parser_consume_next_arg(&opts); } if (arg) { @@ -1290,15 +1286,17 @@ TEST(option_optional_arg_consume) { } else scope_seen ++; - } else if (streq_ptr(opt->long_code, "uid")) { - ASSERT_STREQ(arg, "nobody"); + } else if (streq_ptr(opts.opt->long_code, "uid")) { + ASSERT_STREQ(opts.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"))); + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), STRV_MAKE("nogroup"))); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); } } diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 7d6ca7450a6be..211f7d7a6d280 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -947,11 +947,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const Option *current; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -969,11 +967,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = arg; + arg_host = opts.arg; break; OPTION_COMMON_MACHINE: - r = parse_machine_argument(arg, &arg_host, &arg_transport); + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; @@ -988,14 +986,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { 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); + r = strv_extend(&arg_property, opts.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 (current->short_code == 'P') + if (opts.opt->short_code == 'P') SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; @@ -1008,7 +1006,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/tmpfiles/test-offline-passwd.c b/src/tmpfiles/test-offline-passwd.c index 9695ba9b63c2c..21b2697ceeab6 100644 --- a/src/tmpfiles/test-offline-passwd.c +++ b/src/tmpfiles/test-offline-passwd.c @@ -43,18 +43,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert_se(argc >= 0); assert_se(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION('r', "root", "PATH", "Operate on an alternate filesystem root"): - arg_root = arg; + arg_root = opts.arg; break; } - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 0; } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 0c133b08c84fe..0cc06ca8ec0aa 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -4187,10 +4187,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_LONG("create", NULL, "Create and adjust files and directories"): @@ -4240,12 +4239,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("prefix", "PATH", "Only apply rules with the specified prefix"): - if (strv_extend(&arg_include_prefixes, arg) < 0) + if (strv_extend(&arg_include_prefixes, opts.arg) < 0) return log_oom(); break; OPTION_LONG("exclude-prefix", "PATH", "Ignore rules with the specified prefix"): - if (strv_extend(&arg_exclude_prefixes, arg) < 0) + if (strv_extend(&arg_exclude_prefixes, opts.arg) < 0) return log_oom(); break; @@ -4256,13 +4255,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; @@ -4273,20 +4272,20 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): - r = parse_image_policy_argument(arg, &arg_image_policy); + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): - if (!path_is_absolute(arg)) + if (!path_is_absolute(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(arg, ".conf")) + if (!endswith(opts.arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = arg; + arg_replace = opts.arg; break; OPTION_LONG("dry-run", NULL, "Just print what would be done"): @@ -4302,8 +4301,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state); - size_t n_args = option_parser_get_n_args(&state); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c index e6a063f2a8d43..b65905c03dbcd 100644 --- a/src/tpm2-setup/tpm2-clear.c +++ b/src/tpm2-setup/tpm2-clear.c @@ -50,9 +50,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -66,7 +66,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) != 0) + if (option_parser_get_n_args(&opts) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no arguments."); return 1; diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index b779650b73384..b8e585225be2a 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -76,10 +76,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -89,17 +88,17 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): - if (streq(arg, "list")) + if (streq(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (free_and_strdup(&arg_tpm2_device, streq(arg, "auto") ? NULL : arg) < 0) + if (free_and_strdup(&arg_tpm2_device, streq(opts.arg, "auto") ? NULL : opts.arg) < 0) return log_oom(); break; OPTION_LONG("early", "BOOL", "Store SRK public key in /run/ rather than /var/lib/"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --early= argument: %s", arg); + return log_error_errno(r, "Failed to parse --early= argument: %s", opts.arg); arg_early = r; break; @@ -109,7 +108,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state) != 0) + if (option_parser_get_n_args(&opts) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no argument."); return 1; 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 b927871d211c3..cd49503156db7 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -472,11 +472,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -509,19 +508,19 @@ static int parse_argv(int argc, char *argv[]) { 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 (arg) { - if (isempty(arg)) + if (opts.arg) { + if (isempty(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty console device path is not allowed."); - r = free_and_strdup_warn(&arg_device, arg); + r = free_and_strdup_warn(&arg_device, opts.arg); if (r < 0) return r; } break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index 112456ffc2fd0..ea28ad027d313 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -377,10 +377,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -395,7 +394,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index 1a167475d20d8..b78096bde6362 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -918,10 +918,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -953,7 +952,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index df683d00729d0..269ea15252101 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -662,10 +662,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -676,11 +675,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION('F', "from-dump", "FILE", "Read DMI information from a binary file"): - arg_source_file = arg; + arg_source_file = opts.arg; break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index a301b9acdb087..6b31f49a48076 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -47,10 +47,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -60,7 +59,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument."); diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index 0767ccba09525..3b926fa4a24f2 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -84,10 +84,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -97,7 +96,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { return version(); } - *remaining_args = option_parser_get_args(&state); + *remaining_args = option_parser_get_args(&opts); return 1; } diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index 1cc769ad76aed..3e5f162343dbb 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -54,10 +54,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -67,7 +66,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 8f580a8cec6f5..41140db816281 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -225,11 +225,10 @@ static int set_options(int argc, char **argv, char *maj_min_dev) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -240,26 +239,26 @@ static int set_options(int argc, char **argv, char *maj_min_dev) { OPTION('d', "device", "PATH", "Device node for SG_IO commands"): dev_specified = true; - strscpy(maj_min_dev, MAX_PATH_LEN, arg); + strscpy(maj_min_dev, MAX_PATH_LEN, opts.arg); break; OPTION('f', "config", "PATH", "Location of config file"): - strscpy(config_file, MAX_PATH_LEN, arg); + strscpy(config_file, MAX_PATH_LEN, opts.arg); break; OPTION('p', "page", "0x80|0x83|pre-spc3-83", "SCSI page"): - r = parse_page_code(arg, &default_page_code); + r = parse_page_code(opts.arg, &default_page_code); if (r < 0) return r; break; OPTION('s', "sg-version", "3|4", "Use SGv3 or SGv4"): - r = safe_atoi(arg, &sg_version); + r = safe_atoi(opts.arg, &sg_version); if (r < 0) - return log_error_errno(r, "Invalid SG version '%s'", arg); + return log_error_errno(r, "Invalid SG version '%s'", opts.arg); if (!IN_SET(sg_version, 3, 4)) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "Unknown SG version '%s'", arg); + "Unknown SG version '%s'", opts.arg); break; OPTION('b', "denylisted", NULL, "Treat device as denylisted"): {} @@ -287,7 +286,7 @@ static int set_options(int argc, char **argv, char *maj_min_dev) { break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (!strv_isempty(args) && !dev_specified) { dev_specified = true; strscpy(maj_min_dev, MAX_PATH_LEN, args[0]); @@ -315,26 +314,24 @@ static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum if (newargc > INT_MAX) return log_oom(); /* Close enough :) */ - OptionParser state = { newargc, newargv }; - const Option *opt; - const char *arg; + OptionParser opts = { newargc, newargv }; /* We reuse the option parser, but only a subset of the options is supported here. * If any others are encountered, return an error. */ - FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) - if (opt->short_code == 'b') + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + if (opts.opt->short_code == 'b') *good_bad = 0; - else if (opt->short_code == 'g') + else if (opts.opt->short_code == 'g') *good_bad = 1; - else if (opt->short_code == 'p') { - r = parse_page_code(arg, page_code); + else if (opts.opt->short_code == 'p') { + r = parse_page_code(opts.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))); + strnull(option_get_synopsis("", opts.opt, "/", /* show_metavar=*/ false))); return 0; } diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index c5bfa935b2536..93ca2d3b997fe 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -41,10 +41,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -54,7 +53,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index bc678b8f41988..b55c9941a9de3 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -96,10 +96,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -108,13 +107,13 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("root", "PATH", "Operate on root directory PATH"): - r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; } - if (option_parser_get_n_args(&state) > 0) + if (option_parser_get_n_args(&opts) > 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 44a58387f05b1..58f8feb805dca 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -64,10 +64,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -76,21 +75,21 @@ 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")) + if (streq(opts.arg, "auto")) r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); else { - if (!path_is_absolute(arg)) + if (!path_is_absolute(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--root= argument must be 'auto' or absolute path, got: %s", arg); + "--root= argument must be 'auto' or absolute path, got: %s", opts.arg); - r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); } if (r < 0) return r; break; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index f2f8c271d4718..fbf4b217ff691 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -124,10 +124,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -165,7 +164,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_JSON: - r = parse_json_argument(arg, &arg_json_format_flags); + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; @@ -179,25 +178,25 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("graceful", "ERROR", "Treat specified Varlink error as success"): - r = varlink_idl_qualified_symbol_name_is_valid(arg); + r = varlink_idl_qualified_symbol_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", arg); + return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", opts.arg); - if (strv_extend(&arg_graceful, arg) < 0) + if (strv_extend(&arg_graceful, opts.arg) < 0) return log_oom(); break; OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_timeout = USEC_INFINITY; break; } - r = parse_sec(arg, &arg_timeout); + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", arg); + return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", opts.arg); if (arg_timeout == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timeout cannot be zero."); @@ -223,17 +222,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_oom(); _cleanup_close_ int add_fd = -EBADF; - if (STARTSWITH_SET(arg, "/", "./")) { + if (STARTSWITH_SET(opts.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(arg, O_CLOEXEC|O_RDONLY|O_NOCTTY); + add_fd = open(opts.arg, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (add_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", arg); + return log_error_errno(errno, "Failed to open '%s': %m", opts.arg); } else { - int parsed_fd = parse_fd(arg); + int parsed_fd = parse_fd(opts.arg); if (parsed_fd < 0) - return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", arg); + return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", opts.arg); /* Make a copy, so that the same fd could be used multiple times in a reasonable * way. This also validates the fd early */ @@ -253,7 +252,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { strv_sort_uniq(arg_graceful); - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1a349a1d634f3..20e8f878f6e24 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -325,11 +325,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - const Option *current; - const char *arg; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -361,7 +359,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Image"): {} OPTION('D', "directory", "PATH", "Root directory for the VM"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_directory); if (r < 0) return r; break; @@ -371,31 +369,31 @@ static int parse_argv(int argc, char *argv[]) { break; 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; OPTION_LONG("image-format", "FORMAT", "Specify disk image format (raw, qcow2; default: raw)"): - arg_image_format = image_format_from_string(arg); + arg_image_format = image_format_from_string(opts.arg); if (arg_image_format < 0) return log_error_errno(arg_image_format, - "Invalid image format: %s", arg); + "Invalid image format: %s", opts.arg); break; 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); + arg_image_disk_type = disk_type_from_string(opts.arg); if (arg_image_disk_type < 0) return log_error_errno(arg_image_disk_type, - "Invalid image disk type: %s", arg); + "Invalid image disk type: %s", opts.arg); break; OPTION_GROUP("Host Configuration"): {} 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); + r = free_and_strdup_warn(&arg_cpus, opts.arg); if (r < 0) return r; break; @@ -403,34 +401,34 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_ram(opts.arg); if (r < 0) return r; break; 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); + r = parse_tristate_argument_with_auto("--kvm=", opts.arg, &arg_kvm); if (r < 0) return r; break; 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); + r = parse_tristate_argument_with_auto("--vsock=", opts.arg, &arg_vsock); if (r < 0) return r; break; OPTION_LONG("vsock-cid", "CID", "Specify the CID to use for the guest's VSOCK support"): - if (isempty(arg)) + if (isempty(opts.arg)) arg_vsock_cid = VMADDR_CID_ANY; else { unsigned cid; - r = vsock_parse_cid(arg, &cid); + r = vsock_parse_cid(opts.arg, &cid); if (r < 0) - return log_error_errno(r, "Failed to parse --vsock-cid: %s", arg); + return log_error_errno(r, "Failed to parse --vsock-cid: %s", opts.arg); if (!VSOCK_CID_IS_REGULAR(cid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid); @@ -439,28 +437,28 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("tpm", "BOOL", "Enable use of a virtual TPM"): - r = parse_tristate_argument_with_auto("--tpm=", arg, &arg_tpm); + r = parse_tristate_argument_with_auto("--tpm=", opts.arg, &arg_tpm); if (r < 0) return r; break; OPTION_LONG("tpm-state", "off|auto|PATH", "Where to store TPM state"): - r = isempty(arg) ? false : - streq(arg, "auto") ? true : - parse_boolean(arg); + r = isempty(opts.arg) ? false : + streq(opts.arg, "auto") ? true : + parse_boolean(opts.arg); 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(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", arg); + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", opts.arg); - 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); + if (!path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", opts.arg); - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm_state_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm_state_path); if (r < 0) return r; @@ -468,31 +466,31 @@ static int parse_argv(int argc, char *argv[]) { 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, "./")) + if (!isempty(opts.arg) && !path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_template); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_efi_nvram_template); if (r < 0) return r; 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); + r = isempty(opts.arg) ? false : + streq(opts.arg, "auto") ? true : + parse_boolean(opts.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; } - if (!path_is_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", arg); + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", opts.arg); - 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); + if (!path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", opts.arg); - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_state_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_efi_nvram_state_path); if (r < 0) return r; @@ -500,14 +498,14 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_linux); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_linux); if (r < 0) return r; break; 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); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &initrd_path); if (r < 0) return r; @@ -528,7 +526,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { int b; - r = parse_tristate_argument_with_auto("--secure-boot=", arg, &b); + r = parse_tristate_argument_with_auto("--secure-boot=", opts.arg, &b); if (r < 0) return r; @@ -545,14 +543,14 @@ static int parse_argv(int argc, char *argv[]) { 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")) { + if (isempty(opts.arg) || streq(opts.arg, "auto")) { arg_firmware = mfree(arg_firmware); arg_firmware_type = _FIRMWARE_INVALID; arg_firmware_describe = false; break; } - if (streq(arg, "list")) { + if (streq(opts.arg, "list")) { _cleanup_strv_free_ char **l = NULL; r = list_ovmf_config(&l); @@ -567,7 +565,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - if (streq(arg, "describe")) { + if (streq(opts.arg, "describe")) { /* Handled after argument parsing so that --firmware-features= is * taken into account. */ arg_firmware = mfree(arg_firmware); @@ -577,7 +575,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - Firmware f = firmware_from_string(arg); + Firmware f = firmware_from_string(opts.arg); if (f >= 0) { arg_firmware = mfree(arg_firmware); arg_firmware_type = f; @@ -585,12 +583,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (!path_is_absolute(arg) && !startswith(arg, "./")) + if (!path_is_absolute(opts.arg) && !startswith(opts.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); + opts.arg); - r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_firmware); if (r < 0) return r; @@ -601,13 +599,13 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("firmware-features", "FEATURE,...|list", "Require/exclude specific firmware features"): { - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_firmware_features_include = set_free(arg_firmware_features_include); arg_firmware_features_exclude = set_free(arg_firmware_features_exclude); break; } - if (streq(arg, "list")) { + if (streq(opts.arg, "list")) { _cleanup_strv_free_ char **l = NULL; r = list_ovmf_firmware_features(&l); @@ -622,7 +620,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - _cleanup_strv_free_ char **features = strv_split(arg, ","); + _cleanup_strv_free_ char **features = strv_split(opts.arg, ","); if (!features) return log_oom(); @@ -636,39 +634,39 @@ static int parse_argv(int argc, char *argv[]) { } OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): - r = parse_boolean_argument("--discard-disk=", arg, &arg_discard_disk); + r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk); if (r < 0) return r; break; OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_grow_image = 0; break; } - r = parse_size(arg, 1024, &arg_grow_image); + r = parse_size(opts.arg, 1024, &arg_grow_image); if (r < 0) - return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg); + return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg); break; OPTION_GROUP("Execution"): {} OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): - if (isempty(arg)) { + if (isempty(opts.arg)) { arg_smbios11 = strv_free(arg_smbios11); break; } - 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 (!utf8_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", opts.arg); - if (strv_extend(&arg_smbios11, arg) < 0) + if (strv_extend(&arg_smbios11, opts.arg) < 0) return log_oom(); break; OPTION_LONG("notify-ready", "BOOL", "Wait for ready notification from the VM"): - r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); + r = parse_boolean_argument("--notify-ready=", opts.arg, &arg_notify_ready); if (r < 0) return r; break; @@ -676,25 +674,25 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("System Identity"): {} OPTION('M', "machine", "NAME", "Set the machine name for the VM"): - if (isempty(arg)) + if (isempty(opts.arg)) arg_machine = mfree(arg_machine); else { - if (!hostname_is_valid(arg, 0)) + if (!hostname_is_valid(opts.arg, 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", arg); + "Invalid machine name: %s", opts.arg); - r = free_and_strdup(&arg_machine, arg); + r = free_and_strdup(&arg_machine, opts.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); + r = id128_from_string_nonzero(opts.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); + return log_error_errno(r, "Invalid UUID: %s", opts.arg); break; OPTION_GROUP("Properties"): {} @@ -702,21 +700,21 @@ static int parse_argv(int argc, char *argv[]) { OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): { _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle_with_suffix(arg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); + r = unit_name_mangle_with_suffix(opts.arg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) - return log_error_errno(r, "Failed to turn '%s' into unit name: %m", arg); + return log_error_errno(r, "Failed to turn '%s' into unit name: %m", opts.arg); free_and_replace(arg_slice, mangled); break; } OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): - if (strv_extend(&arg_property, arg) < 0) + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); break; OPTION_LONG("register", "BOOLEAN", "Register VM as machine"): - r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); + r = parse_tristate_argument_with_auto("--register=", opts.arg, &arg_register); if (r < 0) return r; break; @@ -730,7 +728,7 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = parse_userns_uid_range(opts.arg, &arg_uid_shift, &arg_uid_range); if (r < 0) return r; break; @@ -739,10 +737,11 @@ static int parse_argv(int argc, char *argv[]) { 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); + bool read_only = streq(opts.opt->long_code, "bind-ro"); + r = runtime_mount_parse(&arg_runtime_mounts, opts.arg, read_only); if (r < 0) - return log_error_errno(r, "Failed to parse --%s= argument %s: %m", current->long_code, arg); + return log_error_errno(r, "Failed to parse --%s= argument %s: %m", + opts.opt->long_code, opts.arg); break; } @@ -751,7 +750,7 @@ static int parse_argv(int argc, char *argv[]) { DiskType extra_disk_type = _DISK_TYPE_INVALID; _cleanup_free_ char *drive_path = NULL; - r = parse_disk_spec(arg, &format, &extra_disk_type, &drive_path); + r = parse_disk_spec(opts.arg, &format, &extra_disk_type, &drive_path); if (r < 0) return r; @@ -767,10 +766,10 @@ static int parse_argv(int argc, char *argv[]) { } 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 (!valid_user_group_name(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg); - if (strv_extend(&arg_bind_user, arg) < 0) + if (strv_extend(&arg_bind_user, opts.arg) < 0) return log_oom(); break; @@ -778,11 +777,11 @@ static int parse_argv(int argc, char *argv[]) { "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(arg, &sh, ©); + r = parse_user_shell(opts.arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", arg); + return log_error_errno(r, "Invalid user shell to bind: %s", opts.arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; @@ -790,61 +789,61 @@ static int parse_argv(int argc, char *argv[]) { } 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 (!valid_user_group_name(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", opts.arg); - if (strv_extend(&arg_bind_user_groups, arg) < 0) + if (strv_extend(&arg_bind_user_groups, opts.arg) < 0) return log_oom(); 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); + r = parse_path_argument(opts.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); + r = parse_size(opts.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); + r = parse_size(opts.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); + r = parse_size(opts.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); + r = safe_atou64(opts.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); + r = parse_boolean_argument("--pass-ssh-key=", opts.arg, &arg_pass_ssh_key); if (r < 0) return r; break; OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"): - if (isempty(arg)) { + if (isempty(opts.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); + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", opts.arg); - r = free_and_strdup_warn(&arg_ssh_key_type, arg); + r = free_and_strdup_warn(&arg_ssh_key_type, opts.arg); if (r < 0) return r; break; @@ -853,15 +852,15 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("console", "MODE", "Console mode (interactive, native, gui, read-only or headless)"): - arg_console_mode = console_mode_from_string(arg); + arg_console_mode = console_mode_from_string(opts.arg); if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", arg); + return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", opts.arg); break; OPTION_LONG("console-transport", "TRANSPORT", "Console transport (virtio or serial)"): - arg_console_transport = console_transport_from_string(arg); + arg_console_transport = console_transport_from_string(opts.arg); if (arg_console_transport < 0) - return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", arg); + return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", opts.arg); break; OPTION_LONG("qemu-gui", NULL, /* help= */ NULL): /* Compat alias */ @@ -869,7 +868,7 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("background", "COLOR", "Set ANSI color for background"): - r = parse_background_argument(arg, &arg_background); + r = parse_background_argument(opts.arg, &arg_background); if (r < 0) return r; break; @@ -877,14 +876,14 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = machine_credential_set(&arg_credentials, opts.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); + r = machine_credential_load(&arg_credentials, opts.arg); if (r < 0) return r; break; @@ -931,7 +930,7 @@ static int parse_argv(int argc, char *argv[]) { arg_uid_range = 0x10000; } - char **args = option_parser_get_args(&state); + char **args = option_parser_get_args(&opts); if (!strv_isempty(args)) { arg_kernel_cmdline_extra = strv_copy(args); if (!arg_kernel_cmdline_extra) diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index 595ecae68869d..f18edb263f8e9 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -99,71 +99,70 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv }; - const char *arg; + OptionParser opts = { argc, argv }; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { 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); + if (!filename_part_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_basename, arg); + r = free_and_strdup_warn(&arg_filter_basename, opts.arg); if (r < 0) return r; break; 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); + if (!version_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_version, arg); + r = free_and_strdup_warn(&arg_filter_version, opts.arg); if (r < 0) return r; break; OPTION_SHORT('A', "ARCH", "Look for specified architecture"): - if (streq(arg, "native")) + if (streq(opts.arg, "native")) arg_filter_architecture = native_architecture(); - else if (streq(arg, "secondary")) { + else if (streq(opts.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(arg, "uname")) + } else if (streq(opts.arg, "uname")) arg_filter_architecture = uname_architecture(); - else if (streq(arg, "auto")) + else if (streq(opts.arg, "auto")) arg_filter_architecture = _ARCHITECTURE_INVALID; else { - arg_filter_architecture = architecture_from_string(arg); + arg_filter_architecture = architecture_from_string(opts.arg); if (arg_filter_architecture < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", arg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", opts.arg); } break; 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); + if (!filename_part_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_suffix, arg); + r = free_and_strdup_warn(&arg_filter_suffix, opts.arg); if (r < 0) return r; break; OPTION('t', "type", "TYPE", "Look for specified inode type"): - if (isempty(arg)) + if (isempty(opts.arg)) arg_filter_type_mask = 0; else { mode_t m; - m = inode_type_from_string(arg); + m = inode_type_from_string(opts.arg); if (m == MODE_INVALID) - return log_error_errno(m, "Unknown inode type: %s", arg); + return log_error_errno(m, "Unknown inode type: %s", opts.arg); arg_filter_type_mask |= UINT32_C(1) << IFTODT(m); } @@ -193,17 +192,17 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "all", "... print all of the above"): - if (streq(arg, "arch")) /* accept abbreviation too */ + if (streq(opts.arg, "arch")) /* accept abbreviation too */ arg_print = PRINT_ARCHITECTURE; else - arg_print = print_from_string(arg); + arg_print = print_from_string(opts.arg); if (arg_print < 0) - return log_error_errno(arg_print, "Unknown --print= argument: %s", arg); + return log_error_errno(arg_print, "Unknown --print= argument: %s", opts.arg); break; OPTION_LONG("resolve", "BOOL", "Canonicalize the result path"): - r = parse_boolean(arg); + r = parse_boolean(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse --resolve= value: %m"); @@ -214,7 +213,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_print < 0) arg_print = PRINT_PATH; - *ret_args = option_parser_get_args(&state); + *ret_args = option_parser_get_args(&opts); return 1; } From dbe3602abe23b306b3f08aa71957e2737b894108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 27 Apr 2026 09:22:43 +0200 Subject: [PATCH 1307/2155] nspawn,vmspawn: fix state optarg uses --- src/nspawn/nspawn.c | 12 ++++++------ src/vmspawn/vmspawn.c | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 0a99c14e45141..e4e0359ce6d79 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1137,9 +1137,9 @@ static int parse_argv(int argc, char *argv[]) { } 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); + r = parse_address_families(opts.arg, &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); + return log_error_errno(r, "Failed to parse --restrict-address-families= argument: %s", opts.arg); arg_settings_mask |= SETTING_RESTRICT_ADDRESS_FAMILIES; break; @@ -1266,25 +1266,25 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", opts.arg); break; OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", opts.arg); break; OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", opts.arg); break; OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): r = safe_atou64(opts.arg, &arg_forward_journal_max_files); if (r < 0) - return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", opts.arg); break; OPTION_GROUP("Mounts"): {} diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 20e8f878f6e24..17149677763c9 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -807,25 +807,25 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", opts.arg); break; OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", opts.arg); break; OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): r = parse_size(opts.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); + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", opts.arg); break; OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): r = safe_atou64(opts.arg, &arg_forward_journal_max_files); if (r < 0) - return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", opts.arg); break; OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): From a6e694025a26be24676c7c883e478d2461e00f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 20:04:35 +0200 Subject: [PATCH 1308/2155] shared/options: add whitespace to OPTION_COMMON definitions Initially I thought that there'd be just a handful, but the list has grown over time and all bunched up together they are hard to read. --- src/shared/options.h | 73 ++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/shared/options.h b/src/shared/options.h index 10878c746208e..3d08390b33e90 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -59,52 +59,71 @@ typedef struct Option { #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 \ +#define OPTION_COMMON_HELP \ OPTION('h', "help", NULL, "Show this help") -#define OPTION_COMMON_VERSION \ + +#define OPTION_COMMON_VERSION \ OPTION_LONG("version", NULL, "Show package version") -#define OPTION_COMMON_NO_PAGER \ + +#define OPTION_COMMON_NO_PAGER \ OPTION_LONG("no-pager", NULL, "Do not start a pager") -#define OPTION_COMMON_NO_LEGEND \ + +#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", \ + +#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", \ + +#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 \ + +#define OPTION_COMMON_LOG_COLOR \ OPTION_LONG("log-color", "BOOL", "Highlight important messages") -#define OPTION_COMMON_LOG_LOCATION \ + +#define OPTION_COMMON_LOG_LOCATION \ OPTION_LONG("log-location", "BOOL", "Include code location in messages") -#define OPTION_COMMON_LOG_TIME \ + +#define OPTION_COMMON_LOG_TIME \ OPTION_LONG("log-time", "BOOL", "Prefix messages with current time") -#define OPTION_COMMON_CAT_CONFIG \ + +#define OPTION_COMMON_CAT_CONFIG \ OPTION_LONG("cat-config", NULL, "Show configuration files") -#define OPTION_COMMON_TLDR \ + +#define OPTION_COMMON_TLDR \ OPTION_LONG("tldr", NULL, "Show non-comment parts of configuration") -#define OPTION_COMMON_NO_ASK_PASSWORD \ + +#define OPTION_COMMON_NO_ASK_PASSWORD \ OPTION_LONG("no-ask-password", NULL, "Do not prompt for password") -#define OPTION_COMMON_HOST \ + +#define OPTION_COMMON_HOST \ OPTION('H', "host", "[USER@]HOST", "Operate on remote host") -#define OPTION_COMMON_MACHINE \ + +#define OPTION_COMMON_MACHINE \ OPTION('M', "machine", "CONTAINER", "Operate on local container") -#define OPTION_COMMON_JSON \ + +#define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") -#define OPTION_COMMON_LOWERCASE_J \ - OPTION_SHORT('j', NULL, \ + +#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) \ + +#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 " \ + +#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 \ + +#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", \ + +#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)") From 9c39db197b6b5bc0e3ed1706eb32fbf34ee4d246 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 21 Apr 2026 09:10:39 +0200 Subject: [PATCH 1309/2155] varlink: add new io.systemd.Job interface This commit creates a new varlink-io.systemd.Job.c file and puts the job related varlink types into this file. Those will be used by the upcoming io.systemd.Unit.StartTransient. Note that the property names follow the D-Bus naming to make a future "systemctl show" transition from D-Bus -> varlink easier. Thanks to @ikruglov for suggesting this preparation commit and to Lennart for suggesting the D-Bus compatibility considerations. --- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Job.c | 65 +++++++++++++++++++++++++++++ src/shared/varlink-io.systemd.Job.h | 10 +++++ src/test/test-varlink-idl.c | 2 + 4 files changed, 78 insertions(+) create mode 100644 src/shared/varlink-io.systemd.Job.c create mode 100644 src/shared/varlink-io.systemd.Job.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 84acaf698b9c4..c28fe040b6b2b 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -223,6 +223,7 @@ shared_sources = files( 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.InstanceMetadata.c', + 'varlink-io.systemd.Job.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.JournalAccess.c', 'varlink-io.systemd.Login.c', diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c new file mode 100644 index 0000000000000..af15f8f45bb2d --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Job.h" + +/* Keep in sync with job_type_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobType, + SD_VARLINK_DEFINE_ENUM_VALUE(start), + SD_VARLINK_DEFINE_ENUM_VALUE(verify_active), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(reload), + SD_VARLINK_DEFINE_ENUM_VALUE(reload_or_start), + SD_VARLINK_DEFINE_ENUM_VALUE(restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(nop)); + +/* Keep in sync with job_state_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobState, + SD_VARLINK_DEFINE_ENUM_VALUE(waiting), + SD_VARLINK_DEFINE_ENUM_VALUE(running), + SD_VARLINK_DEFINE_ENUM_VALUE(finished)); + +/* Keep in sync with job_result_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobResult, + SD_VARLINK_DEFINE_ENUM_VALUE(done), + SD_VARLINK_DEFINE_ENUM_VALUE(canceled), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(failed), + SD_VARLINK_DEFINE_ENUM_VALUE(dependency), + SD_VARLINK_DEFINE_ENUM_VALUE(skipped), + SD_VARLINK_DEFINE_ENUM_VALUE(invalid), + SD_VARLINK_DEFINE_ENUM_VALUE(assert), + SD_VARLINK_DEFINE_ENUM_VALUE(unsupported), + SD_VARLINK_DEFINE_ENUM_VALUE(collected), + SD_VARLINK_DEFINE_ENUM_VALUE(once), + SD_VARLINK_DEFINE_ENUM_VALUE(frozen), + SD_VARLINK_DEFINE_ENUM_VALUE(concurrency)); + +/* Field names match the D-Bus Job properties (Id, JobType, State) */ +SD_VARLINK_DEFINE_STRUCT_TYPE( + Job, + SD_VARLINK_FIELD_COMMENT("The numeric job ID"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The job type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0), + SD_VARLINK_FIELD_COMMENT("Current job state, set in intermediate streaming notifications"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Final job result, set in the final streaming reply"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Job, + "io.systemd.Job", + SD_VARLINK_INTERFACE_COMMENT("Job-related types for the systemd service manager."), + SD_VARLINK_SYMBOL_COMMENT("Job type"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object"), + &vl_type_Job); diff --git a/src/shared/varlink-io.systemd.Job.h b/src/shared/varlink-io.systemd.Job.h new file mode 100644 index 0000000000000..029d56aa2122a --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_symbol vl_type_JobType; +extern const sd_varlink_symbol vl_type_JobState; +extern const sd_varlink_symbol vl_type_JobResult; +extern const sd_varlink_symbol vl_type_Job; +extern const sd_varlink_interface vl_interface_io_systemd_Job; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index d0f3b914471d2..a645d4d9d360c 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -25,6 +25,7 @@ #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.InstanceMetadata.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Login.h" @@ -192,6 +193,7 @@ TEST(parse_format) { &vl_interface_io_systemd_Hostname, &vl_interface_io_systemd_Import, &vl_interface_io_systemd_InstanceMetadata, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Journal, &vl_interface_io_systemd_JournalAccess, &vl_interface_io_systemd_Login, From 8edd9b2b66aacbf4d8bd061b76ba734fd39046b4 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 16:24:07 +0200 Subject: [PATCH 1310/2155] core: extract manager_setup_transient_unit() helper Extract the transient unit setup logic from transient_unit_from_message() in dbus-manager.c into a shared helper. This prepares for reuse by the varlink StartTransient implementation. --- src/core/dbus-manager.c | 23 +---------------------- src/core/unit.c | 36 ++++++++++++++++++++++++++++++++++++ src/core/unit.h | 1 + 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 1bc73e7b434c9..f48d04a4cb483 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1028,7 +1028,6 @@ static int transient_unit_from_message( Unit **ret_unit, sd_bus_error *reterr_error) { - UnitType t; Unit *u; int r; @@ -1036,27 +1035,7 @@ static int transient_unit_from_message( assert(message); assert(name); - t = unit_name_to_type(name); - if (t < 0) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid unit name or type: %s", name); - - if (!unit_vtable[t]->can_transient) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Unit type %s does not support transient units.", - unit_type_to_string(t)); - - r = manager_load_unit(m, name, NULL, reterr_error, &u); - if (r < 0) - return r; - - if (!unit_is_pristine(u)) - return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, - "Unit %s was already loaded or has a fragment file.", name); - - /* OK, the unit failed to load and is unreferenced, now let's - * fill in the transient data instead */ - r = unit_make_transient(u); + r = manager_setup_transient_unit(m, name, &u, reterr_error); if (r < 0) return r; diff --git a/src/core/unit.c b/src/core/unit.c index 3404d7f8d3a76..de0276a813a08 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -4893,6 +4893,42 @@ int unit_make_transient(Unit *u) { return 0; } +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error) { + Unit *u; + int r; + + assert(m); + assert(name); + assert(ret); + + UnitType t = unit_name_to_type(name); + if (t < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid unit name or type: %s", name); + + if (!unit_vtable[t]->can_transient) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Unit type %s does not support transient units.", + unit_type_to_string(t)); + + r = manager_load_unit(m, name, /* path= */ NULL, reterr_error, &u); + if (r < 0) + return r; + + if (!unit_is_pristine(u)) + return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, + "Unit %s was already loaded or has a fragment file.", name); + + /* OK, the unit failed to load and is unreferenced, now let's + * fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return r; + + *ret = u; + return 0; +} + static bool ignore_leftover_process(const char *comm) { return comm && comm[0] == '('; /* Most likely our own helper process (PAM?), ignore */ } diff --git a/src/core/unit.h b/src/core/unit.h index e503c2e344af5..54794e8be7ad9 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -973,6 +973,7 @@ int unit_write_settingf(Unit *u, UnitWriteFlags flags, const char *name, const c int unit_kill_context(Unit *u, KillOperation k); int unit_make_transient(Unit *u); +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error); int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type); From 913b81735e740c9d700647754c466bc8b75943d7 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 17:04:15 +0200 Subject: [PATCH 1311/2155] core: add io.systemd.Unit.StartTransient() to the varlink API This commit adds a simple version of io.systemd.Unit.StartTransient for varlink. It is similar to the dbus version, but there is a key difference: 1. Instead of building the unit from key/value properties it takes a json object in the "service" parameter. It is also only implementing a minimal set of what can be done with a service (for now) 2. No aux units (for now) 3. When called with --more the varlink socket can notify about unit job and state changes controlled via a bool on the varlink call inputs: notify{Job,Unit}Changes We use the new io.systemd.Job interface when outputing the io.systemd.Unit.StartTransient result as it makes the output nice and mirrors the input. Note that the property names follow the D-Bus naming to make a future "systemctl show" transition from D-Bus -> varlink easier. Because UnitContext is now also used for the inputs we need to make a bunch of fields `SD_VARLINK_NULLABLE` so that the input is even accepted. This does not affect the output, it is still fully populated, just the schema. The ID of UnitContext is still required. Thanks to ikruglov and Lennart for their excellent feedback on this. --- src/core/job.c | 7 +- src/core/job.h | 5 + src/core/manager.c | 3 + src/core/unit.c | 2 + src/core/unit.h | 4 + src/core/varlink-common.c | 10 + src/core/varlink-common.h | 1 + src/core/varlink-manager.c | 1 + src/core/varlink-unit.c | 442 +++++++++++++++++++++++++++ src/core/varlink-unit.h | 10 + src/core/varlink.c | 28 +- src/shared/varlink-io.systemd.Job.c | 4 +- src/shared/varlink-io.systemd.Unit.c | 107 ++++++- 13 files changed, 601 insertions(+), 23 deletions(-) diff --git a/src/core/job.c b/src/core/job.c index b4578a946c6a3..44652d5f3e202 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -2,6 +2,7 @@ #include "sd-bus.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ansi-color.h" @@ -24,6 +25,7 @@ #include "string-util.h" #include "strv.h" #include "unit.h" +#include "varlink-unit.h" #include "virt.h" Job* job_new_raw(Unit *unit) { @@ -124,6 +126,7 @@ Job* job_free(Job *j) { job_unlink(j); sd_bus_track_unref(j->bus_track); + sd_varlink_unref(j->varlink); strv_free(j->deserialized_clients); activation_details_unref(j->activation_details); @@ -173,8 +176,10 @@ void job_uninstall(Job *j) { /* Detach from next 'bigger' objects */ /* daemon-reload should be transparent to job observers */ - if (!MANAGER_IS_RELOADING(j->manager)) + if (!MANAGER_IS_RELOADING(j->manager)) { bus_job_send_removed_signal(j); + varlink_job_send_removed_signal(j); + } *pj = NULL; diff --git a/src/core/job.h b/src/core/job.h index a8eca99505d51..45dccdaaaecd4 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -125,6 +125,9 @@ typedef struct Job { sd_bus_track *bus_track; char **deserialized_clients; + /* If non-NULL, a varlink connection streaming updates. */ + sd_varlink *varlink; + /* If the job had a specific trigger that needs to be advertised (eg: a path unit), store it. */ ActivationDetails *activation_details; @@ -142,6 +145,8 @@ typedef struct Job { bool ref_by_private_bus:1; bool in_gc_queue:1; + + bool varlink_notify_job_changes:1; } Job; Job* job_new(Unit *unit, JobType type); diff --git a/src/core/manager.c b/src/core/manager.c index 56ee66e5bd292..17908d4db864e 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -93,6 +93,7 @@ #include "umask-util.h" #include "unit-name.h" #include "user-util.h" +#include "varlink-unit.h" #include "varlink.h" #include "virt.h" #include "watchdog.h" @@ -2636,6 +2637,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(u->in_dbus_queue); bus_unit_send_change_signal(u); + varlink_unit_send_change_signal(u); n++; if (budget != UINT_MAX) @@ -2646,6 +2648,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(j->in_dbus_queue); bus_job_send_change_signal(j); + varlink_job_send_change_signal(j); n++; if (budget != UINT_MAX) diff --git a/src/core/unit.c b/src/core/unit.c index de0276a813a08..0edb7e25aaa1d 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -7,6 +7,7 @@ #include "sd-bus.h" #include "sd-id128.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "all-units.h" #include "alloc-util.h" @@ -791,6 +792,7 @@ Unit* unit_free(Unit *u) { u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); u->bus_track = sd_bus_track_unref(u->bus_track); + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); u->deserialized_refs = strv_free(u->deserialized_refs); u->pending_freezer_invocation = sd_bus_message_unref(u->pending_freezer_invocation); diff --git a/src/core/unit.h b/src/core/unit.h index 54794e8be7ad9..d79bb7a98a57d 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -289,6 +289,10 @@ typedef struct Unit { /* References to this unit from clients */ sd_bus_track *bus_track; + + /* If non-NULL, a varlink connection streaming unit state change notifications */ + sd_varlink *varlink_unit_change; + char **deserialized_refs; /* References to this */ diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index bdef4d5a9471d..6f876db85b04f 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-bus.h" +#include "sd-varlink.h" #include "bus-common-errors.h" #include "cpu-set-util.h" @@ -18,6 +19,8 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { { BUS_ERROR_NO_SUCH_UNIT, VARLINK_ERROR_UNIT_NO_SUCH_UNIT }, { BUS_ERROR_ONLY_BY_DEPENDENCY, VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY }, { BUS_ERROR_SHUTTING_DOWN, VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN }, + { BUS_ERROR_UNIT_EXISTS, VARLINK_ERROR_UNIT_UNIT_EXISTS }, + { BUS_ERROR_BAD_UNIT_SETTING, VARLINK_ERROR_UNIT_BAD_SETTING }, }; if (!sd_bus_error_is_set(e)) @@ -30,6 +33,13 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { return NULL; } +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e) { + const char *error_id = varlink_error_id_from_bus_error(e); + if (error_id) + return sd_varlink_error(link, error_id, NULL); + return sd_varlink_error_errno(link, r); +} + int rlimit_build_json(sd_json_variant **ret, const char *name, void *userdata) { const struct rlimit *rl = userdata; struct rlimit buf = {}; diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index fc919dcf36c74..aa9f26145c1d9 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -8,3 +8,4 @@ int rlimit_table_build_json(sd_json_variant **ret, const char *name, void *userd 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); +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 96287dc32f05e..0bef5cbe9848d 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -381,6 +381,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par JOB_FAIL, /* reload_if_possible= */ !BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART), &job_id, + /* ret_job= */ NULL, &bus_error); if (ERRNO_IS_NEG_RESOURCE(r)) return r; diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 789b21947c83b..7111baf7391e6 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-bus.h" #include "sd-json.h" #include "bitfield.h" @@ -10,16 +11,21 @@ #include "execute.h" #include "format-util.h" #include "install.h" +#include "job.h" #include "json-util.h" +#include "locale-util.h" #include "manager.h" #include "path-util.h" #include "pidref.h" #include "selinux-access.h" +#include "service.h" #include "set.h" #include "strv.h" +#include "unit-name.h" #include "unit.h" #include "varlink-automount.h" #include "varlink-cgroup.h" +#include "varlink-common.h" #include "varlink-execute.h" #include "varlink-kill.h" #include "varlink-mount.h" @@ -108,6 +114,43 @@ static int unit_conditions_build_json(sd_json_variant **ret, const char *name, v return 0; } +static int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_build_json(&entry, /* name= */ NULL, c); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&v, entry); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +/* TODO: This covers only a small subset of a service object's properties. Extend to make more available to + * consumers like Unit.StartTransient */ +static int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Service *s = ASSERT_PTR(SERVICE(u)); + assert(ret); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit)); +} + static int unit_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { Unit *u = ASSERT_PTR(userdata); @@ -119,6 +162,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void 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, + [UNIT_SERVICE] = service_context_build_json, }; return sd_json_buildo( @@ -548,6 +592,7 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error) { int r; @@ -568,6 +613,8 @@ int varlink_unit_queue_job_one( if (ret_job_id) *ret_job_id = j->id; + if (ret_job) + *ret_job = j; return 0; } @@ -579,6 +626,401 @@ int varlink_error_no_such_unit(sd_varlink *v, const char *name) { JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); } +void varlink_unit_send_change_signal(Unit *u) { + assert(u); + + if (!u->varlink_unit_change) + return; + + (void) sd_varlink_notifybo( + u->varlink_unit_change, + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u)); +} + +static int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Job *j = ASSERT_PTR(userdata); + + /* We cannot just "blindly" use the j->state as it will get reset to WAITING in + * core/job.c:job_uninstall() before varlink_job_send_removed_signal(). This happens from + * unit.c:unit_free(). It is probably something that should be fixed but its a subtle part of + * systemd so for now we just deal with it here. */ + JobState state = j->result >= 0 ? JOB_FINISHED : j->state; + + /* Note that "Result" is suppressed until the job reaches JOB_FINISHED. */ + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), + JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(state)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("Result", job_result_to_string(j->result))); +} + +void varlink_job_send_change_signal(Job *j) { + assert(j); + + if (!j->varlink || !j->varlink_notify_job_changes) + return; + + (void) sd_varlink_notifybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); +} + +void varlink_job_send_removed_signal(Job *j) { + assert(j); + + if (!j->varlink) + return; + + /* Send the final reply, which completes the method call */ + (void) sd_varlink_replybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + j->varlink = sd_varlink_unref(j->varlink); + j->unit->varlink_unit_change = sd_varlink_unref(j->unit->varlink_unit_change); +} + +typedef struct TransientExecCommandItem { + const char *path; + char **arguments; +} TransientExecCommandItem; + +static void transient_exec_command_item_done(TransientExecCommandItem *i) { + assert(i); + strv_free(i->arguments); +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_type_from_string); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string); + +typedef struct TransientServiceParameters { + ServiceType type; + TransientExecCommandItem *exec_start; + size_t n_exec_start; + int remain_after_exit; +} TransientServiceParameters; + +static void transient_service_parameters_done(TransientServiceParameters *p) { + assert(p); + FOREACH_ARRAY(i, p->exec_start, p->n_exec_start) + transient_exec_command_item_done(i); + free(p->exec_start); +} + +static int dispatch_transient_exec_command(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field exec_command_dispatch[] = { + { "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY }, + { "arguments", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecCommandItem, arguments), 0 }, + {} + }; + + TransientServiceParameters *p = ASSERT_PTR(userdata); + size_t n; + int r; + + if (!sd_json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Expected JSON array for ExecStart."); + + n = sd_json_variant_elements(variant); + if (n == 0) + return 0; + + p->exec_start = new0(TransientExecCommandItem, n); + if (!p->exec_start) + return -ENOMEM; + p->n_exec_start = n; + + for (size_t i = 0; i < n; i++) { + sd_json_variant *element = sd_json_variant_by_index(variant, i); + + r = sd_json_dispatch(element, exec_command_dispatch, /* flags= */ 0, &p->exec_start[i]); + if (r < 0) + return r; + } + return 0; +} + +typedef struct StartTransientContextParameters { + const char *id; + const char *description; + TransientServiceParameters service; +} StartTransientContextParameters; + +static void start_transient_context_parameters_done(StartTransientContextParameters *p) { + assert(p); + transient_service_parameters_done(&p->service); +} + +typedef struct StartTransientParameters { + StartTransientContextParameters context; + JobMode mode; + int notify_job_changes; + int notify_unit_changes; + const char *unsupported_property; /* For error reporting on unknown context fields */ +} StartTransientParameters; + +static void start_transient_parameters_done(StartTransientParameters *p) { + assert(p); + start_transient_context_parameters_done(&p->context); +} + +static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field service_dispatch[] = { + { "Type", SD_JSON_VARIANT_STRING, dispatch_service_type, offsetof(TransientServiceParameters, type), 0 }, + { "ExecStart", SD_JSON_VARIANT_ARRAY, dispatch_transient_exec_command, 0, 0 }, + { "RemainAfterExit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TransientServiceParameters, remain_after_exit), 0 }, + {} + }; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + return sd_json_dispatch(variant, service_dispatch, /* flags= */ 0, &p->service); +} + +static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field context_dispatch[] = { + { "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY }, + { "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 }, + { "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 }, + {} + }; + + StartTransientParameters *p = ASSERT_PTR(userdata); + const char *bad_field = NULL; + int r; + + /* Don't propagate the caller's flags (in particular SD_JSON_MANDATORY from the outer 'context' + * field) into the nested dispatch, otherwise every inner field becomes mandatory. */ + r = sd_json_dispatch_full(variant, context_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->context, &bad_field); + if (r == -EADDRNOTAVAIL && !isempty(bad_field)) + /* A UnitContext field that exists in the schema but is not settable at creation time: stash + * the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. */ + p->unsupported_property = bad_field; + return r; +} + +static int transient_service_apply_properties(Unit *u, TransientServiceParameters *sp) { + int r; + + Service *s = ASSERT_PTR(SERVICE(u)); + assert(sp); + + if (sp->type >= 0) { + s->type = sp->type; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Type", "Type=%s", service_type_to_string(sp->type)); + } + + if (sp->remain_after_exit >= 0) { + s->remain_after_exit = sp->remain_after_exit; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "RemainAfterExit", "RemainAfterExit=%s", yes_no(sp->remain_after_exit)); + } + + FOREACH_ARRAY(item, sp->exec_start, sp->n_exec_start) { + _cleanup_(exec_command_freep) ExecCommand *c = NULL; + _cleanup_strv_free_ char **argv = NULL; + + if (!filename_or_absolute_path_is_valid(item->path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path); + + if (!strv_isempty(item->arguments)) { + argv = strv_copy(item->arguments); + if (!argv) + return -ENOMEM; + } + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + r = path_simplify_alloc(item->path, &c->path); + if (r < 0) + return r; + + /* If no arguments were provided, default argv[0] to the executable path. + * Otherwise the caller is expected to include argv[0] in the arguments array. */ + if (strv_isempty(argv)) { + r = strv_extend(&argv, c->path); + if (r < 0) + return r; + } + + c->argv = TAKE_PTR(argv); + + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], TAKE_PTR(c)); + } + + /* Write ExecStart= lines to the transient file */ + if (sp->n_exec_start > 0) { + UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX_ENV; + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a = NULL; + + a = unit_concat_strv(c->argv, esc_flags); + if (!a) + return -ENOMEM; + + /* streq() instead path_equal() as argv[0] can be arbitrary and may not be a path */ + if (streq(c->path, c->argv[0])) + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=%s", a); + else { + _cleanup_free_ char *t = NULL; + const char *p; + + p = unit_escape_setting(c->path, esc_flags, &t); + if (!p) + return -ENOMEM; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=@%s %s", p, a); + } + } + } + + return 0; +} + +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "context", SD_JSON_VARIANT_OBJECT, dispatch_transient_context, 0, SD_JSON_MANDATORY }, + { "mode", SD_JSON_VARIANT_STRING, dispatch_job_mode, offsetof(StartTransientParameters, mode), 0 }, + { "notifyJobChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_job_changes), 0 }, + { "notifyUnitChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_unit_changes), 0 }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL; + _cleanup_(start_transient_parameters_done) StartTransientParameters p = { + .mode = JOB_REPLACE, + .notify_job_changes = -1, + .notify_unit_changes = -1, + .context.service.type = _SERVICE_TYPE_INVALID, + .context.service.remain_after_exit = -1, + }; + Manager *manager = ASSERT_PTR(userdata); + const char *bad_field = NULL; + Unit *u; + int r; + + assert(link); + assert(parameters); + + r = mac_selinux_access_check_varlink(link, "start"); + if (r < 0) + return r; + + r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, &p, &bad_field); + if (r < 0) { + /* An unknown field in 'context' maps to PropertyNotSupported (the field is defined in the + * UnitContext schema but cannot be set at creation time). Anything else is a bad parameter. */ + if (streq_ptr(bad_field, "context") && r == -EADDRNOTAVAIL && p.unsupported_property) + return sd_varlink_errorbo( + link, + "io.systemd.Unit.PropertyNotSupported", + SD_JSON_BUILD_PAIR_STRING("property", p.unsupported_property)); + if (bad_field) + return sd_varlink_error_invalid_parameter_name(link, bad_field); + return r; + } + + /* Pre-check unit type early and return targeted varlink error as manager_setup_transient_unit() the + * too generic SD_BUS_ERROR_INVALID_ARGS. */ + UnitType t = unit_name_to_type(p.context.id); + if (t < 0) + return sd_varlink_error_invalid_parameter_name(link, "context"); + if (!unit_vtable[t]->can_transient) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", p.context.id, + "verb", "start", + "polkit.message", N_("Authentication is required to start transient unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + r = manager_setup_transient_unit(manager, p.context.id, &u, &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + /* Apply unit-level properties from context */ + if (p.context.description) { + r = unit_set_description(u, p.context.description); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p.context.description); + } + + /* Apply service-specific properties from context.Service */ + if (p.context.service.type >= 0 || p.context.service.n_exec_start > 0 || p.context.service.remain_after_exit >= 0) { + if (t != UNIT_SERVICE) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + r = transient_service_apply_properties(u, &p.context.service); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + } + + unit_add_to_load_queue(u); + manager_dispatch_load_queue(manager); + + if (u->load_state == UNIT_BAD_SETTING) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + if (!UNIT_IS_LOAD_COMPLETE(u->load_state)) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT, NULL); + + Job *j; + r = varlink_unit_queue_job_one( + u, + JOB_START, + p.mode, + /* reload_if_possible= */ false, + /* ret_job_id= */ NULL, + &j, + &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + bool notify_job = p.notify_job_changes > 0; + bool notify_unit = p.notify_unit_changes > 0; + + /* Non-streaming, or fire-and-forget (no notification flags set): return full unit context + * and runtime, plus the job object so the caller can correlate with later state. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE) || (!notify_job && !notify_unit)) + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + /* Streaming: always attach to the job for the final reply, and optionally to the unit for state + * change notifications. j->varlink owns the stream lifetime, u->varlink_unit_change is just a flag + * to also send unit state notifications along the way. */ + assert(!j->varlink); + j->varlink = sd_varlink_ref(link); + j->varlink_notify_job_changes = notify_job; + if (notify_unit) { + assert(!u->varlink_unit_change); + u->varlink_unit_change = sd_varlink_ref(link); + } + + /* Send initial job state notification if requested. Unit state change notifications are not sent + * here; they will arrive via varlink_unit_send_change_signal() when the unit actually transitions, + * matching D-Bus PropertiesChanged behavior. */ + if (notify_job) + return sd_varlink_notifybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + return 0; +} + typedef struct UnitSetPropertiesParameters { const char *unsupported_property; /* For error reporting */ const char *name; diff --git a/src/core/varlink-unit.h b/src/core/varlink-unit.h index 8f58d7ce10378..9460245ec0a77 100644 --- a/src/core/varlink-unit.h +++ b/src/core/varlink-unit.h @@ -6,6 +6,9 @@ #define VARLINK_ERROR_UNIT_NO_SUCH_UNIT "io.systemd.Unit.NoSuchUnit" #define VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY "io.systemd.Unit.OnlyByDependency" #define VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN "io.systemd.Unit.DBusShuttingDown" +#define VARLINK_ERROR_UNIT_UNIT_EXISTS "io.systemd.Unit.UnitExists" +#define VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED "io.systemd.Unit.UnitTypeNotSupported" +#define VARLINK_ERROR_UNIT_BAD_SETTING "io.systemd.Unit.BadUnitSetting" int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); @@ -15,8 +18,15 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error); int vl_method_set_unit_properties(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +void varlink_unit_send_change_signal(Unit *u); +void varlink_job_send_change_signal(Job *j); +void varlink_job_send_removed_signal(Job *j); + int varlink_error_no_such_unit(sd_varlink *v, const char *name); diff --git a/src/core/varlink.c b/src/core/varlink.c index 7bfc3789a39ba..09817b6dce2c4 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -4,6 +4,7 @@ #include "constants.h" #include "errno-util.h" +#include "job.h" #include "json-util.h" #include "manager.h" #include "metrics.h" @@ -356,6 +357,24 @@ static void vl_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata if (link == m->managed_oom_varlink) m->managed_oom_varlink = sd_varlink_unref(link); + + /* Drop any job varlink references for the disconnecting client. + * A varlink link can stream at most one job, so stop after the first match. */ + Job *j; + HASHMAP_FOREACH(j, m->jobs) + if (j->varlink == link) { + j->varlink = sd_varlink_unref(j->varlink); + break; + } + + /* Also drop any unit-change varlink reference streaming to this link. + * A varlink link attaches to at most one unit, so stop after the first match. */ + Unit *u; + HASHMAP_FOREACH(u, m->units) + if (u->varlink_unit_change == link) { + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); + break; + } } int manager_setup_varlink_server(Manager *m) { @@ -398,6 +417,7 @@ int manager_setup_varlink_server(Manager *m) { "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.Unit.StartTransient", vl_method_start_transient_unit, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) @@ -419,12 +439,12 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); if (r < 0) return log_debug_errno(r, "Failed to register varlink methods: %m"); - - r = sd_varlink_server_bind_disconnect(s, vl_disconnect); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); } + r = sd_varlink_server_bind_disconnect(s, vl_disconnect); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); + r = sd_varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index af15f8f45bb2d..8dfc403c7be81 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -46,9 +46,9 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("The job type"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0), - SD_VARLINK_FIELD_COMMENT("Current job state, set in intermediate streaming notifications"), + SD_VARLINK_FIELD_COMMENT("Current job state. 'finished' indicates the job has completed; in that case Result is also set."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Final job result, set in the final streaming reply"), + SD_VARLINK_FIELD_COMMENT("Job result. Only set once the job has reached the 'finished' state."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE)); SD_VARLINK_DEFINE_INTERFACE( diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 422aeb6952b30..2b1f0f2b1058b 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.Job.h" #include "varlink-io.systemd.Unit.h" SD_VARLINK_DEFINE_ENUM_TYPE( @@ -972,6 +973,30 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Remount command"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecRemount, ExecCommand, SD_VARLINK_NULLABLE)); +/* Service-specific types */ + +/* Keep in sync with service_type_table[] in src/core/service.c */ +static SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceType, + SD_VARLINK_DEFINE_ENUM_VALUE(simple), + SD_VARLINK_DEFINE_ENUM_VALUE(exec), + SD_VARLINK_DEFINE_ENUM_VALUE(forking), + SD_VARLINK_DEFINE_ENUM_VALUE(oneshot), + SD_VARLINK_DEFINE_ENUM_VALUE(dbus), + SD_VARLINK_DEFINE_ENUM_VALUE(notify), + SD_VARLINK_FIELD_COMMENT("Like notify, but also implements a reload protocol via SIGHUP."), + SD_VARLINK_DEFINE_ENUM_VALUE(notify_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(idle)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Type="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, ServiceType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStart="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStart, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RemainAfterExit="), + SD_VARLINK_DEFINE_FIELD(RemainAfterExit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( Condition, @@ -984,10 +1009,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The parameter passed to the condition"), SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +/* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, + * Description, Service) and as output from List/StartTransient (full unit configuration). Fields that + * are not settable at creation time are rejected with PropertyNotSupported when supplied as input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), - SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The unit ID"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The aliases of this unit"), @@ -1054,25 +1082,25 @@ 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_BY_TYPE(OnSuccessJobMode, JobMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnSuccessJobMode, JobMode, 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_BY_TYPE(OnFailureJobMode, JobMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StopWhenUnneeded="), - SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#AllowIsolate="), - SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#DefaultDependencies="), - SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#CollectMode="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, 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_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="), @@ -1119,11 +1147,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The unit file preset for this unit"), SD_VARLINK_DEFINE_FIELD(UnitFilePreset, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is transient"), - SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is perpetual"), - SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("When true, logs about this unit will be at debug level regardless of other log level settings"), - SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), /* Other contexts */ SD_VARLINK_FIELD_COMMENT("The cgroup context of the unit"), @@ -1132,6 +1160,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( 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_FIELD_COMMENT("The service context of the unit (only for .service units)"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Service, ServiceContext, 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_FIELD_COMMENT("The mount context of the unit"), @@ -1339,6 +1369,28 @@ static SD_VARLINK_DEFINE_ERROR( PropertyNotSupported, SD_VARLINK_DEFINE_FIELD(property, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ERROR(UnitExists); +static SD_VARLINK_DEFINE_ERROR(UnitTypeNotSupported); +static SD_VARLINK_DEFINE_ERROR(BadUnitSetting); + +static SD_VARLINK_DEFINE_METHOD_FULL( + StartTransient, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Unit context. Must include ID (the unit name). Only the subset of fields settable at creation time is accepted; supplying any other field returns PropertyNotSupported."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(context, UnitContext, 0), + SD_VARLINK_FIELD_COMMENT("Job mode. Defaults to replace."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mode, JobMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream job state change notifications. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyJobChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream unit runtime notifications on state changes. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyUnitChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit context. Set in the final reply."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, UnitContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit runtime state. Set in the final reply and in intermediate streaming notifications when notifyUnitChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, UnitRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The job that was enqueued. Always set in the final streaming reply; also included in intermediate streaming notifications when notifyJobChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(job, Job, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_METHOD( SetProperties, SD_VARLINK_FIELD_COMMENT("The name of the unit to operate on."), @@ -1355,6 +1407,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_List, SD_VARLINK_SYMBOL_COMMENT("Set unit properties"), &vl_method_SetProperties, + SD_VARLINK_SYMBOL_COMMENT("Create a transient unit and start it"), + &vl_method_StartTransient, &vl_type_RateLimit, SD_VARLINK_SYMBOL_COMMENT("An object to represent a unit's conditions"), &vl_type_Condition, @@ -1451,7 +1505,22 @@ SD_VARLINK_DEFINE_INTERFACE( /* UnitContext enums */ &vl_type_CollectMode, &vl_type_EmergencyAction, + + /* Shared types (used by both StartTransient and Unit.List) */ + SD_VARLINK_SYMBOL_COMMENT("Service type"), + &vl_type_ServiceType, + SD_VARLINK_SYMBOL_COMMENT("Job mode"), &vl_type_JobMode, + SD_VARLINK_SYMBOL_COMMENT("Job type (defined in io.systemd.Job)"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state (defined in io.systemd.Job)"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result (defined in io.systemd.Job)"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object (defined in io.systemd.Job)"), + &vl_type_Job, + SD_VARLINK_SYMBOL_COMMENT("Service-specific context"), + &vl_type_ServiceContext, /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), @@ -1460,9 +1529,15 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_UnitMasked, SD_VARLINK_SYMBOL_COMMENT("Unit is in a fatal error state"), &vl_error_UnitError, - SD_VARLINK_SYMBOL_COMMENT("Changing this property via SetProperties() is not supported"), + SD_VARLINK_SYMBOL_COMMENT("The named property cannot be set (via SetProperties() or at creation time via StartTransient())"), &vl_error_PropertyNotSupported, SD_VARLINK_SYMBOL_COMMENT("Job for the unit may only be enqueued by dependencies"), &vl_error_OnlyByDependency, SD_VARLINK_SYMBOL_COMMENT("A unit that requires D-Bus cannot be started as D-Bus is shutting down"), - &vl_error_DBusShuttingDown); + &vl_error_DBusShuttingDown, + SD_VARLINK_SYMBOL_COMMENT("A unit with this name already exists"), + &vl_error_UnitExists, + SD_VARLINK_SYMBOL_COMMENT("This unit type does not support transient units"), + &vl_error_UnitTypeNotSupported, + SD_VARLINK_SYMBOL_COMMENT("The unit file content contains invalid settings"), + &vl_error_BadUnitSetting); From 1cde1cc3bab595fe7b4e2befbfa08a01a172db0f Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 17:04:31 +0200 Subject: [PATCH 1312/2155] test: add varlink io.systemd.Unit.StartTransient() test This commit adds a bunch of integration tests for the new varlink based io.systemd.Unit.StartTransient(). --- test/units/TEST-26-SYSTEMCTL.sh | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 75feedd3b097e..32842c1e90e64 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -519,6 +519,123 @@ systemctl show -P Markers "$UNIT_NAME" | grep needs-stop (! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload) varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}" +# Test io.systemd.Unit.StartTransient +MANAGER_SOCKET="/run/systemd/io.systemd.Manager" + +TRANSIENT_UNITS=() +defer_transient_cleanup() { + TRANSIENT_UNITS+=("$1") +} +transient_cleanup() { + for u in "${TRANSIENT_UNITS[@]}"; do + systemctl stop "$u" 2>/dev/null || true + systemctl reset-failed "$u" 2>/dev/null || true + done +} +trap transient_cleanup EXIT + +# Basic oneshot transient service +defer_transient_cleanup varlink-transient-test.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | grep '"context"' >/dev/null +echo "$result" | grep '"runtime"' >/dev/null + +# Wait for completion +timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done' +systemctl show -P Result varlink-transient-test.service | grep success >/dev/null + +# With explicit mode +defer_transient_cleanup varlink-transient-test2.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}') +echo "$result" | grep '"context"' >/dev/null + +# Streaming with notifyJobChanges: should get intermediate state updates and a final result +defer_transient_cleanup varlink-transient-test3.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.State == "waiting")' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# Fire-and-forget: --more without notify flags should return immediately with context+runtime +defer_transient_cleanup varlink-transient-fireforget.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +printf '%s' "$result" | jq --seq -e 'select(.context)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.runtime)' >/dev/null + +# Streaming with notifyUnitChanges: should get unit state change notifications +defer_transient_cleanup varlink-transient-unitnotify.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null + +# Streaming with both notifyJobChanges and notifyUnitChanges +defer_transient_cleanup varlink-transient-both.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.State)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# prepare for the error case below: create a long-running service, then try to create it again while it's active +defer_transient_cleanup varlink-transient-exists.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' +timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done' + +# Multiple ExecStart commands (oneshot allows multiple) +defer_transient_cleanup varlink-transient-multi.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# Transient service with Description and RemainAfterExit +defer_transient_cleanup varlink-transient-desc.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Description == "Test description property"' +echo "$result" | jq -e '.context.Service.Type == "oneshot"' +echo "$result" | jq -e '.context.Service.RemainAfterExit == true' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"' +echo "$result" | jq -e '.runtime' + +# Transient service with explicit arguments +defer_transient_cleanup varlink-transient-args.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}') +echo "$result" | jq -e '.context' +echo "$result" | jq -e '.runtime' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"' +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done' + +# Verify that omitting arguments defaults argv[0] to the path +defer_transient_cleanup varlink-transient-noargs.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' + +# Error cases: verify specific varlink error types +set +o pipefail +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +defer_transient_cleanup varlink-transient-bad.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() +defer_transient_cleanup varlink-transient-badpath.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +set -o pipefail + +transient_cleanup +trap - EXIT + # --dry-run with destructive verbs # kexec is skipped intentionally, as it requires a bit more involved setup VERBS=( From 0d6d2c9ebda27a7da2da249458eb44610968bc97 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 27 Apr 2026 12:34:27 +0200 Subject: [PATCH 1313/2155] 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 4dbcc319cae75233cc334556ded595636115ed47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Sat, 25 Apr 2026 00:18:56 +0900 Subject: [PATCH 1314/2155] fork-notify: Use callback instead of argv NULL code path with return In 012d87c1fc/cc8f398202 it was made possible for fork_notify() to return in the child but at that point all FDs were closed and the _cleanup path from the return causes assertion failures due to invalid FDs in notify_event_source/event, leading to a vmspawn failing to start with a SIGABRT logged in coredump. Instead of TAKE_PTR on a bunch of things which is fragile, rather avoid the return and instead add an explicit callback handler and guarantee to exit directly after it. A userdata argument is also added but not used yet I think it's quite normal to have for a callback. --- src/shared/fork-notify.c | 44 ++++++++++++++++------------------------ src/shared/fork-notify.h | 4 +++- src/vmspawn/vmspawn.c | 2 +- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index 066ee29115faa..0a34c491b7aa5 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -92,9 +92,10 @@ static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } -int fork_notify(char * const *argv, PidRef *ret_pidref) { +int fork_notify(char * const *argv, fork_notify_handler_t child_handler, void *child_userdata, PidRef *ret_pidref) { int r; + assert(argv); assert(ret_pidref); if (!is_main_thread()) @@ -123,7 +124,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { if (r < 0) return r; - if (DEBUG_LOGGING && argv) { + if (DEBUG_LOGGING) { _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY); log_debug("Invoking '%s' as child.", strnull(l)); } @@ -145,10 +146,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 */ - } + /* After fork and before exec one can execute custom code in this function + * but since all open FDs were closed only limited actions are safe (e.g., setenv), + * akin to how in a signal handler only certain things are safe. */ + if (child_handler) + child_handler(child_userdata); r = invoke_callout_binary(argv[0], argv); log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); @@ -173,7 +175,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { *ret_pidref = TAKE_PIDREF(child); - return 1; /* In the parent */ + return 0; } static void fork_notify_terminate_internal(PidRef *pidref) { @@ -241,7 +243,14 @@ int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, Pid if (strv_extendf(&argv, "--output=%s", output_mode_to_string(output)) < 0) return log_oom_debug(); - return fork_notify(argv, ret_pidref); + return fork_notify(argv, /* child_handler= */ NULL, /* child_userdata= */ NULL, ret_pidref); +} + +static void set_journal_remote_config(void *userdata) { + 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); + } } int fork_journal_remote( @@ -313,22 +322,5 @@ int fork_journal_remote( 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; + return fork_notify(argv, set_journal_remote_config, /* child_userdata= */ NULL, ret_pidref); } diff --git a/src/shared/fork-notify.h b/src/shared/fork-notify.h index cc241beff9335..cf22f48ba2ace 100644 --- a/src/shared/fork-notify.h +++ b/src/shared/fork-notify.h @@ -4,7 +4,9 @@ #include "output-mode.h" #include "shared-forward.h" -int fork_notify(char * const *argv, PidRef *ret_pidref); +typedef void (*fork_notify_handler_t)(void *userdata); + +int fork_notify(char * const *argv, fork_notify_handler_t child_handler, void *child_userdata, PidRef *ret_pidref); void fork_notify_terminate(PidRef *pidref); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1a349a1d634f3..d7784e1e9a9d9 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1565,7 +1565,7 @@ static int start_tpm( if (r < 0) return log_oom(); - r = fork_notify(argv, ret_pidref); + r = fork_notify(argv, /* child_handler= */ NULL, /* child_userdata= */ NULL, ret_pidref); if (r < 0) return r; From 41cf6891213dd78c5af3c2b8ff05194c52efa62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Sat, 25 Apr 2026 01:42:36 +0900 Subject: [PATCH 1315/2155] nsresource: fix buffer overrun reported by ASAN This came up when running systemd-vmspawn with ASAN to fix another bug and thus I had to fix this overrun here first: The dispatch tables were missing the terminator, add it. --- src/shared/nsresource.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c index 7a1c33446b811..b783cb80b78ae 100644 --- a/src/shared/nsresource.c +++ b/src/shared/nsresource.c @@ -372,6 +372,7 @@ int nsresource_add_netif_veth( static const sd_json_dispatch_field dispatch_table[] = { { "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY }, { "namespaceInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, namespace_interface_name), SD_JSON_MANDATORY }, + {} }; _cleanup_(interface_params_done) InterfaceParams p = {}; @@ -437,6 +438,7 @@ int nsresource_add_netif_tap( static const sd_json_dispatch_field dispatch_table[] = { { "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY }, { "interfaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(InterfaceParams, interface_fd_index), SD_JSON_MANDATORY }, + {} }; _cleanup_(interface_params_done) InterfaceParams p = {}; From 70d75c22d700098dea39151b12bba9729bed0f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Sat, 25 Apr 2026 01:49:09 +0900 Subject: [PATCH 1316/2155] machine-register: don't unref borrowed varlink reply ASAN showed a use-after-free error for systemd-vmspawn's machine_register call because the reply got accessed and freed again through _cleanup. The same problem exists in two verb_machine_control_one/unregister_machine. Fix these call sites to not set up _cleanup. --- src/machine/machinectl.c | 2 +- src/shared/machine-register.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index e8a30a89592a3..ae5473aa99d50 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1242,7 +1242,7 @@ static int verb_machine_control_one(const char *machine_name, const char *method 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; + sd_json_variant *reply = NULL; const char *error_id = NULL; r = sd_varlink_call(vl, method, /* parameters= */ NULL, &reply, &error_id); if (r < 0) diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c index a161d1b0508d3..9637793fe549f 100644 --- a/src/shared/machine-register.c +++ b/src/shared/machine-register.c @@ -170,8 +170,7 @@ int register_machine( } 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; + sd_json_variant *reply = NULL; const char *error_id = NULL; r = sd_varlink_callbo( vl, @@ -306,7 +305,7 @@ int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope if (r >= 0) r = sd_varlink_connect_address(&vl, p); if (r >= 0) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *reply = NULL; const char *error_id = NULL; r = sd_varlink_callbo( vl, From 81d23b58e8d4fe0560d70ae4c6009e8f9f25eca2 Mon Sep 17 00:00:00 2001 From: Christian Goeschel Ndjomouo Date: Sat, 4 Apr 2026 00:16:41 -0400 Subject: [PATCH 1317/2155] shared/pager: add support for more(1) pager in secure mode The more(1) pager (part of util-linux) now supports secure mode, which can be enabled with the PAGERSECURE environment variable. Adding support for more(1) in secure mode serves as an alternative for systems that do not have less installed or for users who prefer it. Commit d2fce960f9cac740 introduced secure mode in more(1) and the new feature is available in version 2.42 of util-linux. Signed-off-by: Christian Goeschel Ndjomouo --- src/shared/pager.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shared/pager.c b/src/shared/pager.c index 3c89aacab48d0..61718aeaa6807 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -206,6 +206,13 @@ void pager_open(PagerFlags flags) { _exit(EXIT_FAILURE); } + /* Some pager implementations support the PAGERSECURE environment variable, e.g. more(1) */ + r = set_unset_env("PAGERSECURE", use_secure_mode ? "1" : NULL, true); + if (r < 0) { + log_error_errno(r, "Failed to adjust environment variable PAGERSECURE: %m"); + _exit(EXIT_FAILURE); + } + if (trust_pager && pager_args) { /* The pager config might be set globally, and we cannot * know if the user adjusted it to be appropriate for the * secure mode. Thus, start the pager specified through @@ -228,8 +235,8 @@ void pager_open(PagerFlags flags) { static const char* pagers[] = { "pager", "less", "more", "(built-in)" }; for (unsigned i = 0; i < ELEMENTSOF(pagers); i++) { - /* Only less (and our trivial fallback) implement secure mode right now. */ - if (use_secure_mode && !STR_IN_SET(pagers[i], "less", "(built-in)")) + /* Only less, more (and our trivial fallback) implement secure mode right now. */ + if (use_secure_mode && !STR_IN_SET(pagers[i], "less", "more", "(built-in)")) continue; r = loop_write(exe_name_pipe[1], pagers[i], strlen(pagers[i]) + 1); From 84e53b8cfcdd0d3fe5810279bdf261e6cd5910f9 Mon Sep 17 00:00:00 2001 From: Christian Goeschel Ndjomouo Date: Sat, 4 Apr 2026 00:29:02 -0400 Subject: [PATCH 1318/2155] man: document secure mode support for the more(1) pager Signed-off-by: Christian Goeschel Ndjomouo --- man/common-variables.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/man/common-variables.xml b/man/common-variables.xml index 0f807b2cde1d2..74463c23d6083 100644 --- a/man/common-variables.xml +++ b/man/common-variables.xml @@ -190,11 +190,14 @@ allowing untrusted users to execute commands with elevated privileges. This option takes a boolean argument. When set to true, the "secure mode" of the pager is - enabled. In "secure mode", will be set when invoking the pager, which - instructs the pager to disable commands that open or create new files or start new subprocesses. + enabled. In "secure mode", and will be set + when invoking the pager, which instructs the pager to disable commands that open or create new files or + start new subprocesses. Currently only less1 is known - to understand this variable and implement "secure mode". + project='man-pages'>less1 and + more1 are known + to understand these variables, respectively, and implement "secure mode". When set to false, no limitation is placed on the pager. Setting SYSTEMD_PAGERSECURE=0 or not removing it from the inherited environment may allow From df87d750380425384464a991594706137a528a75 Mon Sep 17 00:00:00 2001 From: Christian Goeschel Ndjomouo Date: Mon, 20 Apr 2026 23:41:12 -0400 Subject: [PATCH 1319/2155] doc: bump util-linux baseline to v2.42 This baseline bump is mainly to support the secure mode feature in more(1) that has been made available since util-linux v2.42. Signed-off-by: Christian Goeschel Ndjomouo --- README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index ddcb863f4ada2..51a78bd2a1d00 100644 --- a/README +++ b/README @@ -264,9 +264,9 @@ REQUIREMENTS: During runtime, you need the following additional dependencies: - util-linux >= v2.27.1 required (including but not limited to: mount, - umount, swapon, swapoff, sulogin, - agetty, fsck) + util-linux >= v2.42 required (including but not limited to: mount, + umount, swapon, swapoff, sulogin, + agetty, fsck, more) 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 b10d3631f36e4c45f6815f6461e4ab6e44553e03 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 10:32:52 +0200 Subject: [PATCH 1320/2155] options: drop redundant enum value declaration --- src/shared/options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/options.h b/src/shared/options.h index 3d08390b33e90..889867bf6a9b0 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -140,7 +140,7 @@ 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, + OPTION_PARSER_NORMAL, /* Same as "+…" for getopt_long — only parse options before the first positional argument. */ OPTION_PARSER_STOP_AT_FIRST_NONOPTION, From bd2d2fec80f059eff279c76275f564323259ca37 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 10:37:48 +0200 Subject: [PATCH 1321/2155] options: tighten validation of argc/argv If argc/argv do no match a lot of our assumptions are invalid, hence let's check this explicitly once, instead of ignoring the issue. --- 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 644c7ae27c3db..0c3188bf78681 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -76,9 +76,11 @@ int option_parse( if (state->optind == 0) { assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); - if (state->argc < 1 || strv_isempty(state->argv)) + if (state->argc < 1) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */ + state->optind = state->positional_offset = 1; } From ce6a5f416432a86c702912a0edf18c2ca34a1314 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 10:33:46 +0200 Subject: [PATCH 1322/2155] options: add a proper state machine field Let's track the state of the option parser in an explicit 'state' field. Benefits: 1. We can make sure that a parser that got into a failure state will be invalidated for good (i.e. further operations are guaranteed to fail too). 2. As a side effect this cleans up the option_parse() return parameter handling: we'll now always initialize ret_option/ret_arg when returning >= 0, as per coding style. --- src/shared/options.c | 122 +++++++++++++++++++++++++++++-------------- src/shared/options.h | 14 ++++- 2 files changed, 95 insertions(+), 41 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 0c3188bf78681..faefbe659eae6 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -72,23 +72,46 @@ int option_parse( const Option options_end[], OptionParser *state) { + /* We define this one early, since we use goto below, and need to guarantee its initialization */ + _cleanup_free_ char *_optname = NULL; /* allocated option name */ + int r; + + assert(state); + /* Check and initialize */ - if (state->optind == 0) { + switch (state->state) { + + case OPTION_PARSER_INIT: assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); - if (state->argc < 1) - return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + if (state->argc < 1) { + r = log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + goto fail; + } assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */ - state->optind = state->positional_offset = 1; + state->state = OPTION_PARSER_RUNNING; + break; + + case OPTION_PARSER_RUNNING: + case OPTION_PARSER_STOPPING: + break; + + case OPTION_PARSER_DONE: + goto done; + + case OPTION_PARSER_FAILED: + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Option parser failed before, refusing."); + + default: + assert_not_reached(); } /* Look for the next 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; bool handling_positional_arg = false; @@ -96,27 +119,27 @@ int option_parse( /* Handle non-option parameters */ for (;;) { if (state->optind == state->argc) - goto finished; + goto done; if (streq(state->argv[state->optind], "--")) { /* No more options. Move "--" before positional args so that * the list of positional args is clean. */ shift_arg(state->argv, state->positional_offset++, state->optind++); - state->parsing_stopped = true; + goto done; } - if (state->parsing_stopped) - goto finished; + /* If we are in OPTION_PARSER_STOPPING state we only wanted to read one more "--" if + * there is one, nothing else, hence it's time to say goodbye now. */ + if (state->state == OPTION_PARSER_STOPPING) + goto done; if (state->argv[state->optind][0] == '-' && state->argv[state->optind][1] != '\0') /* Looks like we found an option parameter */ break; - if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) { - state->parsing_stopped = true; - goto finished; - } + if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) + goto done; if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) { handling_positional_arg = true; @@ -147,8 +170,10 @@ int option_parse( char *eq = strchr(state->argv[state->optind], '='); if (eq) { optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]); - if (!_optname) - return log_oom(); + if (!_optname) { + r = log_oom(); + goto fail; + } /* joined argument */ optval = eq + 1; @@ -161,12 +186,16 @@ int option_parse( 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); + if (n_partial_matches == 0) { + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + goto fail; + } + if (n_partial_matches > 1) { + r = partial_match_error(options, options_end, optname, n_partial_matches); + goto fail; + } /* just one partial — good */ option = last_partial; @@ -195,15 +224,19 @@ int option_parse( if (state->short_option_offset > 0) { char optchar = state->argv[state->optind][state->short_option_offset]; - if (asprintf(&_optname, "-%c", optchar) < 0) - return log_oom(); + if (asprintf(&_optname, "-%c", optchar) < 0) { + r = log_oom(); + goto fail; + } 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 >= options_end) { + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + goto fail; + } if (option_is_metadata(option) || optchar != option->short_code) continue; @@ -225,15 +258,19 @@ int option_parse( assert(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 (!handling_positional_arg && optval && !option_takes_arg(option)) { + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' doesn't allow an argument", + program_invocation_short_name, optname); + goto fail; + } 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", - program_invocation_short_name, optname); + if (!state->argv[state->optind + 1]) { + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' requires an argument", + program_invocation_short_name, optname); + goto fail; + } optval = state->argv[state->optind + 1]; separate_optval = true; } @@ -252,16 +289,23 @@ int option_parse( } if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING)) - state->parsing_stopped = true; + state->state = OPTION_PARSER_STOPPING; state->opt = option; state->arg = optval; return option->id; - finished: + done: + state->state = OPTION_PARSER_DONE; state->opt = NULL; state->arg = NULL; return 0; + + fail: + /* Invalidate the object for good on the first error */ + assert(r < 0); + state->state = OPTION_PARSER_FAILED; + return r; } char* option_parser_next_arg(const OptionParser *state) { @@ -294,7 +338,7 @@ char** option_parser_get_args(const OptionParser *state) { * original argv array, so it must not be freed or modified. */ assert(state->optind > 0); - assert(state->optind == state->argc || state->parsing_stopped); + assert(state->state == OPTION_PARSER_DONE); assert(state->positional_offset <= state->argc); return state->argv + state->positional_offset; @@ -302,7 +346,7 @@ char** option_parser_get_args(const OptionParser *state) { size_t option_parser_get_n_args(const OptionParser *state) { assert(state->optind > 0); - assert(state->optind == state->argc || state->parsing_stopped); + assert(state->state == OPTION_PARSER_DONE); assert(state->positional_offset <= state->argc); return state->argc - state->positional_offset; diff --git a/src/shared/options.h b/src/shared/options.h index 889867bf6a9b0..81b92d2da5ff1 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -152,14 +152,24 @@ typedef enum OptionParserMode { _OPTION_PARSER_MODE_MAX, } OptionParserMode; +typedef enum OptionParserState { + OPTION_PARSER_INIT, + OPTION_PARSER_RUNNING, + OPTION_PARSER_STOPPING, /* We processed an option with OPTION_STOPS_PARSING, and will eat up one + * more "--", but nothing else. */ + OPTION_PARSER_DONE, /* Option parsing completed (could be because we reached the end, or because + * "--" was fully processed, or because we hit a terminating option). */ + OPTION_PARSER_FAILED, /* We encountered a parse error, and terminated option parsing. */ + _OPTION_PARSER_MAX, +} OptionParserState; + typedef struct OptionParser { /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } * or { argc, argv, mode }. */ int argc; /* The original argc. */ char **argv; /* The argv array, possibly reordered. */ OptionParserMode mode; - - bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ + OptionParserState state; 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. From 181a9f65a7cf9059da0f2a44e2152d7636446b33 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Apr 2026 08:44:44 +0000 Subject: [PATCH 1323/2155] udev: don't assert on worker cap after killing a broken idle worker manager_can_process_event() considers an event processable if either there is room below children_max to spawn, or an idle worker exists. When only the latter holds, event_run() picks the idle worker and tries device_monitor_send(). If that send fails, event_run() SIGKILLs the worker, marks it WORKER_KILLED and continues the loop. With no other idle worker available, it falls through to worker_spawn(), guarded by: assert(hashmap_size(manager->workers) < manager->config.children_max); The just-killed worker is still in manager->workers until its SIGCHLD is reaped by on_worker_exit(), so at the cap this assertion trips and udevd aborts: Assertion 'hashmap_size(manager->workers) < manager->config.children_max' failed at src/udev/udev-manager.c:635, function event_run(). Aborting. Instead of asserting, bail out when we are already at the worker limit. The event remains in EVENT_QUEUED; once the killed worker's SIGCHLD arrives and frees it from the hashmap, on_post() re-runs event_queue_start() and the event is retried. --- src/udev/udev-manager.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 46c8a85d98608..7c2530f17fec8 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -631,8 +631,13 @@ static int event_run(Event *event) { return 0; } + /* No idle worker could accept the event. If we already reached the worker limit, e.g. because + * we just killed the only idle worker above, leave the event queued and wait for SIGCHLD of an + * exiting worker to free up a slot. on_post() will retry processing the queue. */ + if (hashmap_size(manager->workers) >= manager->config.children_max) + return 0; + /* start new worker and pass initial device */ - assert(hashmap_size(manager->workers) < manager->config.children_max); r = worker_spawn(manager, event); if (r < 0) return r; From 328fd964a91e120c4ea7edde6d1c5c9688123175 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 27 Apr 2026 13:12:30 +0200 Subject: [PATCH 1324/2155] core: transition job to JOB_FINISHED on uninstall When a job reaches the job_uninstall() stage we used to set the state to JOB_WAITING. However now that we have a JOB_FINISHED state [1] we should use that instead. This is more accurate so when varlink_job_send_removed_signal() is called the job is in the expected state and that is what the user will see. Note that this does not change the D-Bus API because there bus_job_send_removed_signal() doesn't send the state, it only sends the result. [1] https://github.com/systemd/systemd/pull/41811 --- src/core/job.c | 2 +- src/core/varlink-unit.c | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/core/job.c b/src/core/job.c index 44652d5f3e202..2388d1de95f64 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -168,7 +168,7 @@ void job_uninstall(Job *j) { assert(j); assert(j->installed); - job_set_state(j, JOB_WAITING); + job_set_state(j, JOB_FINISHED); pj = j->type == JOB_NOP ? &j->unit->nop_job : &j->unit->job; assert(*pj == j); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 7111baf7391e6..5b1dc77aae0b4 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -640,18 +640,12 @@ void varlink_unit_send_change_signal(Unit *u) { static int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { Job *j = ASSERT_PTR(userdata); - /* We cannot just "blindly" use the j->state as it will get reset to WAITING in - * core/job.c:job_uninstall() before varlink_job_send_removed_signal(). This happens from - * unit.c:unit_free(). It is probably something that should be fixed but its a subtle part of - * systemd so for now we just deal with it here. */ - JobState state = j->result >= 0 ? JOB_FINISHED : j->state; - /* Note that "Result" is suppressed until the job reaches JOB_FINISHED. */ return sd_json_buildo( ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), - JSON_BUILD_PAIR_ENUM("State", job_state_to_string(state)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(j->state)), JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("Result", job_result_to_string(j->result))); } From a192a5cc4da38760cf919a9df204d35a0937fb78 Mon Sep 17 00:00:00 2001 From: Dark Cronyx Date: Tue, 28 Apr 2026 08:59:17 +0000 Subject: [PATCH 1325/2155] po: Translated using Weblate (German) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Dark Cronyx Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/de/ Translation: systemd/main --- po/de.po | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/po/de.po b/po/de.po index 27ddf8e743edc..6f358593cdef1 100644 --- a/po/de.po +++ b/po/de.po @@ -12,12 +12,13 @@ # Weblate Translation Memory , 2024, 2025. # Anselm Schueler , 2024. # Marcel Leismann , 2025, 2026. +# Dark Cronyx , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-10 15:58+0000\n" -"Last-Translator: Marcel Leismann \n" +"PO-Revision-Date: 2026-04-28 08:59+0000\n" +"Last-Translator: Dark Cronyx \n" "Language-Team: German \n" "Language: de\n" @@ -25,7 +26,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.2\n" +"X-Generator: Weblate 5.17\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1146,7 +1147,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Netzwerkverbindungen verwalten" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." From e596867ed6e32a57a282c87bbeac7660011a1ecb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Apr 2026 12:03:45 +0200 Subject: [PATCH 1326/2155] meson: rerun 'update-man-rules' --- man/rules/meson.build | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/man/rules/meson.build b/man/rules/meson.build index 525c56b1d3b34..4aae561512991 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -2,7 +2,15 @@ # Update with: # ninja -C build update-man-rules manpages = [ -['binfmt.d', '5', [], 'ENABLE_BINFMT'], +['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'], + ''], + ['binfmt.d', '5', [], 'ENABLE_BINFMT'], ['bootctl', '1', [], ''], ['bootup', '7', [], ''], ['busctl', '1', [], ''], @@ -155,14 +163,6 @@ 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', @@ -1249,7 +1249,7 @@ manpages = [ ['systemd-tpm2-swtpm.service', '8', ['systemd-tpm2-swtpm'], - 'ENABLE_BOOTLOADER'], + 'ENABLE_BOOTLOADER HAVE_TPM2'], ['systemd-tty-ask-password-agent', '1', [], ''], ['systemd-udev-settle.service', '8', [], ''], ['systemd-udevd.service', From 37c272228dbdbcb4f60609d273d1352ccac061b7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Apr 2026 12:05:28 +0200 Subject: [PATCH 1327/2155] core: ensure all types from execute.h start with Exec Until very recently all types defined by execute.h started with "Exec" in the name. I think that was useful, since it made clear that the types are associated with the ExecContext infrastructure. Let's hence restore this. (If we every move these types out of execute.h we should drop the "Exec" prefix again. But today is not that day.) No real code changes, just dumb renaming. --- src/core/dbus-execute.c | 12 ++++++------ src/core/exec-invoke.c | 18 +++++++++--------- src/core/execute-serialize.c | 4 ++-- src/core/execute.c | 14 +++++++------- src/core/execute.h | 26 +++++++++++++------------- src/core/load-fragment-gperf.gperf.in | 2 +- src/core/load-fragment.c | 4 ++-- src/core/load-fragment.h | 2 +- src/core/varlink-execute.c | 4 ++-- src/test/test-varlink-idl-unit.c | 2 +- 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 9843836eaf0df..aea9fbb304e22 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -58,7 +58,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_private_bpf, private_bpf, Priva static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_home, protect_home, ProtectHome); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_system, protect_system, ProtectSystem); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_personality, personality, unsigned long); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_memory_thp, memory_thp, MemoryTHP); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_memory_thp, exec_memory_thp, ExecMemoryTHP); static BUS_DEFINE_PROPERTY_GET(property_get_ioprio, "i", ExecContext, exec_context_get_effective_ioprio); static BUS_DEFINE_PROPERTY_GET(property_get_mount_apivfs, "b", ExecContext, exec_context_get_effective_mount_apivfs); static BUS_DEFINE_PROPERTY_GET(property_get_bind_log_sockets, "b", ExecContext, exec_context_get_effective_bind_log_sockets); @@ -1002,7 +1002,7 @@ static int property_get_exec_quota(sd_bus *bus, void *userdata, sd_bus_error *reterr_error) { - QuotaLimit *q = ASSERT_PTR(userdata); + ExecQuotaLimit *q = ASSERT_PTR(userdata); assert(bus); assert(reply); @@ -1399,7 +1399,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("BPFDelegatePrograms", "s", property_get_bpf_delegate_programs, offsetof(ExecContext, bpf_delegate_programs), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("BPFDelegateAttachments", "s", property_get_bpf_delegate_attachments, offsetof(ExecContext, bpf_delegate_attachments), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MemoryTHP", "s", property_get_memory_thp, offsetof(ExecContext, memory_thp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MemoryTHP", "s", property_get_exec_memory_thp, offsetof(ExecContext, memory_thp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UserNamespacePath", "s", NULL, offsetof(ExecContext, user_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1444,7 +1444,7 @@ static int property_get_quota_usage( else assert_not_reached(); - const QuotaLimit *q; + const ExecQuotaLimit *q; q = &c->directories[dt].exec_quota; if (q->quota_enforce || q->quota_accounting) { @@ -1842,7 +1842,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_commands, uint64_t, bpf_d static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_maps, uint64_t, bpf_delegate_maps_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_programs, uint64_t, bpf_delegate_programs_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_attachments, uint64_t, bpf_delegate_attachments_from_string); -static BUS_DEFINE_SET_TRANSIENT_PARSE(memory_thp, MemoryTHP, memory_thp_from_string); +static BUS_DEFINE_SET_TRANSIENT_PARSE(exec_memory_thp, ExecMemoryTHP, exec_memory_thp_from_string); BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check); @@ -2341,7 +2341,7 @@ int bus_exec_context_set_transient_property( return bus_set_transient_tristate(u, name, &c->memory_ksm, message, flags, reterr_error); if (streq(name, "MemoryTHP")) - return bus_set_transient_memory_thp(u, name, &c->memory_thp, message, flags, reterr_error); + return bus_set_transient_exec_memory_thp(u, name, &c->memory_thp, message, flags, reterr_error); if (streq(name, "UtmpIdentifier")) return bus_set_transient_string(u, name, &c->utmp_id, message, flags, reterr_error); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 8c8502ffab396..260e7a36afb08 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2762,7 +2762,7 @@ static int create_many_symlinks(const char *root, const char *source, char **sym return 0; } -static int set_exec_storage_quota(int fd, uint32_t proj_id, const QuotaLimit *ql) { +static int set_exec_storage_quota(int fd, uint32_t proj_id, const ExecQuotaLimit *ql) { int r; uint64_t block_limit = 0, inode_limit = 0; @@ -2852,7 +2852,7 @@ static int apply_exec_quotas( const char *target_dir, const char *cgroup_path, ExecDirectoryType type, - const QuotaLimit *ql, + const ExecQuotaLimit *ql, uint32_t *exec_dt_proj_id, /* in/out */ bool *already_enforced) { /* in/out */ @@ -4872,23 +4872,23 @@ static int setup_delegated_namespaces( return 0; } -static int set_memory_thp(MemoryTHP thp) { +static int set_memory_thp(ExecMemoryTHP thp) { int r; switch (thp) { - case MEMORY_THP_INHERIT: + case EXEC_MEMORY_THP_INHERIT: return 0; - case MEMORY_THP_DISABLE: + case EXEC_MEMORY_THP_DISABLE: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)); break; - case MEMORY_THP_MADVISE: + case EXEC_MEMORY_THP_MADVISE: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0)); break; - case MEMORY_THP_SYSTEM: + case EXEC_MEMORY_THP_SYSTEM: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 0, 0, 0, 0)); break; @@ -5711,11 +5711,11 @@ int exec_invoke( r = set_memory_thp(context->memory_thp); if (r == -EOPNOTSUPP) log_debug_errno(r, "Setting MemoryTHP=%s is not supported, ignoring.", - memory_thp_to_string(context->memory_thp)); + exec_memory_thp_to_string(context->memory_thp)); else if (r < 0) { *exit_status = EXIT_MEMORY_THP; return log_error_errno(r, "Failed to set MemoryTHP=%s: %m", - memory_thp_to_string(context->memory_thp)); + exec_memory_thp_to_string(context->memory_thp)); } #if ENABLE_UTMP diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 143cfe6286b91..5d0ebfa37c0bb 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -1698,7 +1698,7 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; - r = serialize_item(f, "exec-context-memory-thp", memory_thp_to_string(c->memory_thp)); + r = serialize_item(f, "exec-context-memory-thp", exec_memory_thp_to_string(c->memory_thp)); if (r < 0) return r; @@ -2643,7 +2643,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-memory-thp="))) { - c->memory_thp = memory_thp_from_string(val); + c->memory_thp = exec_memory_thp_from_string(val); if (c->memory_thp < 0) return c->memory_thp; } else if ((val = startswith(l, "exec-context-private-tmp="))) { diff --git a/src/core/execute.c b/src/core/execute.c index cfd63fe3038fa..987093511fc57 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1143,7 +1143,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { prefix, protect_hostname_to_string(c->protect_hostname), c->private_hostname ? ":" : "", strempty(c->private_hostname), prefix, protect_proc_to_string(c->protect_proc), prefix, proc_subset_to_string(c->proc_subset), - prefix, memory_thp_to_string(c->memory_thp), + prefix, exec_memory_thp_to_string(c->memory_thp), prefix, private_bpf_to_string(c->private_bpf)); if (c->private_bpf == PRIVATE_BPF_YES) { @@ -3146,11 +3146,11 @@ static const char* const exec_keyring_mode_table[_EXEC_KEYRING_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(exec_keyring_mode, ExecKeyringMode); -static const char* const memory_thp_table[_MEMORY_THP_MAX] = { - [MEMORY_THP_INHERIT] = "inherit", - [MEMORY_THP_DISABLE] = "disable", - [MEMORY_THP_MADVISE] = "madvise", - [MEMORY_THP_SYSTEM] = "system", +static const char* const exec_memory_thp_table[_EXEC_MEMORY_THP_MAX] = { + [EXEC_MEMORY_THP_INHERIT] = "inherit", + [EXEC_MEMORY_THP_DISABLE] = "disable", + [EXEC_MEMORY_THP_MADVISE] = "madvise", + [EXEC_MEMORY_THP_SYSTEM] = "system", }; -DEFINE_STRING_TABLE_LOOKUP(memory_thp, MemoryTHP); +DEFINE_STRING_TABLE_LOOKUP(exec_memory_thp, ExecMemoryTHP); diff --git a/src/core/execute.h b/src/core/execute.h index 3d5e92cccf0fe..66bb40a675010 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -73,23 +73,23 @@ typedef enum ExecKeyringMode { _EXEC_KEYRING_MODE_INVALID = -EINVAL, } ExecKeyringMode; -typedef enum MemoryTHP { +typedef enum ExecMemoryTHP { /* * Inherit default from process that starts systemd, i.e. do not make * any PR_SET_THP_DISABLE call. */ - MEMORY_THP_INHERIT, - MEMORY_THP_DISABLE, /* Disable THPs completely for the process */ - MEMORY_THP_MADVISE, /* Disable THPs for the process except when madvised */ + EXEC_MEMORY_THP_INHERIT, + EXEC_MEMORY_THP_DISABLE, /* Disable THPs completely for the process */ + EXEC_MEMORY_THP_MADVISE, /* Disable THPs for the process except when madvised */ /* * Use system default THP setting. this can be used when the process that * starts systemd has already disabled THPs via PR_SET_THP_DISABLE, and we * want to restore the system default THP setting at process invocation time. */ - MEMORY_THP_SYSTEM, - _MEMORY_THP_MAX, - _MEMORY_THP_INVALID = -EINVAL, -} MemoryTHP; + EXEC_MEMORY_THP_SYSTEM, + _EXEC_MEMORY_THP_MAX, + _EXEC_MEMORY_THP_INVALID = -EINVAL, +} ExecMemoryTHP; /* Contains start and exit information about an executed command. */ typedef struct ExecStatus { @@ -154,12 +154,12 @@ static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) { return t >= 0 && t < _EXEC_DIRECTORY_TYPE_MAX && t != EXEC_DIRECTORY_CONFIGURATION; } -typedef struct QuotaLimit { +typedef struct ExecQuotaLimit { uint64_t quota_absolute; /* absolute quota in bytes; if UINT64_MAX relative quota configured, see below */ uint32_t quota_scale; /* relative quota to backend size, scaled to 0…UINT32_MAX */ bool quota_enforce; bool quota_accounting; -} QuotaLimit; +} ExecQuotaLimit; typedef struct ExecDirectoryItem { char *path; @@ -172,7 +172,7 @@ typedef struct ExecDirectory { mode_t mode; size_t n_items; ExecDirectoryItem *items; - QuotaLimit exec_quota; + ExecQuotaLimit exec_quota; } ExecDirectory; typedef enum ExecCleanMask { @@ -332,7 +332,7 @@ typedef struct ExecContext { int mount_apivfs; int bind_log_sockets; int memory_ksm; - MemoryTHP memory_thp; + ExecMemoryTHP memory_thp; PrivateTmp private_tmp; /* Those are not independent parameters, but are calculated from */ PrivateTmp private_var_tmp; /* other parameters in unit_patch_contexts(). */ @@ -636,7 +636,7 @@ DECLARE_STRING_TABLE_LOOKUP(exec_directory_type_mode, ExecDirectoryType); DECLARE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType); -DECLARE_STRING_TABLE_LOOKUP(memory_thp, MemoryTHP); +DECLARE_STRING_TABLE_LOOKUP(exec_memory_thp, ExecMemoryTHP); bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params); bool exec_needs_network_namespace(const ExecContext *context); diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 17ac9c5138b26..79551adad8b1a 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -196,7 +196,7 @@ {% endif %} {{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context) {{type}}.MemoryKSM, config_parse_tristate, 0, offsetof({{type}}, exec_context.memory_ksm) -{{type}}.MemoryTHP, config_parse_memory_thp, 0, offsetof({{type}}, exec_context.memory_thp) +{{type}}.MemoryTHP, config_parse_exec_memory_thp, 0, offsetof({{type}}, exec_context.memory_thp) {%- endmacro -%} {%- macro KILL_CONTEXT_CONFIG_ITEMS(type) -%} diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 1fae521333f79..d08e71f9c9782 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -166,7 +166,7 @@ DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_commands, bpf_delegate_command DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_maps, bpf_delegate_maps_from_string, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_programs, bpf_delegate_programs_from_string, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_attachments, bpf_delegate_attachments_from_string, uint64_t); -DEFINE_CONFIG_PARSE_ENUM(config_parse_memory_thp, memory_thp, MemoryTHP); +DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_memory_thp, exec_memory_thp, ExecMemoryTHP); bool contains_instance_specifier_superset(const char *s) { const char *p, *q; @@ -4532,7 +4532,7 @@ int config_parse_exec_quota( void *data, void *userdata) { - QuotaLimit *quota_limit = ASSERT_PTR(data); + ExecQuotaLimit *quota_limit = ASSERT_PTR(data); uint64_t quota_absolute = UINT64_MAX; uint32_t quota_scale = UINT32_MAX; int r; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index a5b7595dbfdb9..ed8060b0428ac 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -169,7 +169,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max); CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface); -CONFIG_PARSER_PROTOTYPE(config_parse_memory_thp); +CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index 21d735183fc4a..7056439a220ae 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -352,7 +352,7 @@ static int ioprio_class_build_json(sd_json_variant **ret, const char *name, void static int exec_dir_build_json(sd_json_variant **ret, const char *name, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; ExecDirectory *exec_dir = ASSERT_PTR(userdata); - const QuotaLimit *quota = &exec_dir->exec_quota; + const ExecQuotaLimit *quota = &exec_dir->exec_quota; int r; assert(ret); @@ -881,7 +881,7 @@ 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), - JSON_BUILD_PAIR_ENUM("MemoryTHP", memory_thp_to_string(c->memory_thp)), + JSON_BUILD_PAIR_ENUM("MemoryTHP", exec_memory_thp_to_string(c->memory_thp)), /* Sandboxing */ JSON_BUILD_PAIR_ENUM("ProtectSystem", protect_system_to_string(c->protect_system)), diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 2f4bb1c9e9156..28b49a0659258 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -20,7 +20,7 @@ TEST(unit_enums_idl) { 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(ExecMemoryTHP, exec_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); From d0f726ed8f9f62c0fef14723aab241ff7b23e9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:12:43 +0200 Subject: [PATCH 1328/2155] shared/options: fix --help indentation for long options In 4339197f5d4f712bc900d8e09c892015d48b19bb the helper to format -o/--opt= was split out, but the indentation was for --long-options was messed up. We'd print: Options: -h --help Show this help --version Show package version --no-ask-password Do not prompt for password ... But we want -h --help Show this help --version Show package version --no-ask-password Do not prompt for password ... The prefix argument was arguably ugly, even if it allowed one alloc to be avoided. Let's get rid of it and let the handler prefix the string as appropriate. This makes other callers nicer too. Fixup for 4339197f5d4f712bc900d8e09c892015d48b19bb. --- src/shared/options.c | 32 +++++++++--------- src/shared/options.h | 2 +- src/test/test-options.c | 66 +++++++++++++++++++------------------- src/udev/scsi_id/scsi_id.c | 2 +- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 644c7ae27c3db..74f697edbe921 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -306,18 +306,15 @@ 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) { +char* option_get_synopsis(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)); + return strdup(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, @@ -338,16 +335,14 @@ char* option_get_synopsis(const char *prefix, const Option *opt, const char *joi bool need_eq = option_takes_arg(opt) && opt->long_code; if (!show_metavar) - return strjoin(prefix, - sc, + return strjoin(sc, joiner, opt->long_code ? "--" : "", strempty(opt->long_code), need_eq ? "=" : ""); bool need_quote = opt->metavar && strchr(opt->metavar, ' '); - return strjoin(prefix, - sc, + return strjoin(sc, joiner, opt->long_code ? "--" : "", strempty(opt->long_code), @@ -389,22 +384,27 @@ int _option_parser_get_help_table( /* No help string — we do not show the option */ continue; + _cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true); + if (!s) + return log_oom(); + /* 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) + const char *prefix = opt->short_code != 0 ? " " : " "; + _cleanup_free_ char *t = strjoin(prefix, s); + if (!t) return log_oom(); - r = table_add_many(table, TABLE_STRING, s); + r = table_add_many(table, TABLE_STRING, t); if (r < 0) return table_log_add_error(r); - _cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL); - if (!t) + _cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL); + if (!split) return log_oom(); - r = table_add_many(table, TABLE_STRV_WRAPPED, t); + r = table_add_many(table, TABLE_STRV_WRAPPED, split); if (r < 0) return table_log_add_error(r); } diff --git a/src/shared/options.h b/src/shared/options.h index 3d08390b33e90..f9913951f4251 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -191,7 +191,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); +char* option_get_synopsis(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 6a0744e67363d..4bdec73d401ae 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -1306,47 +1306,47 @@ static void test_option_get_synopsis_one( bool show_metavar, const char *expected) { log_debug("%s", expected); - _cleanup_free_ char *s = option_get_synopsis(". ", opt, joiner, show_metavar); + _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]"); + 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, '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']"); + '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)"); + 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); diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 41140db816281..f272648c420c9 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -331,7 +331,7 @@ static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option %s not supported in the config file.", - strnull(option_get_synopsis("", opts.opt, "/", /* show_metavar=*/ false))); + strnull(option_get_synopsis(opts.opt, "/", /* show_metavar=*/ false))); return 0; } From 47d408163b0b71e5f8fed6b2e520c053cefc5780 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Mon, 27 Apr 2026 19:38:26 -0700 Subject: [PATCH 1329/2155] loop-util: don't reuse partition fd when partscan needed Some devices (e.g. android phones running pmOS) cannot have their OEM partition table altered without breaking the firmware, so the distros's partitions live inside a nested GPT carved into one of the OEM partitions. Exposing these subpartitions requires wrapping the outer partition in a loop device with partscan enabled, since the kernel does not go into nested partition tables. systemd already detects this case in udev-builtin-blkid (ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP) and acts on with systemd-loop@.service, but this fails towards the end. loop_device_make_internal has an optimization where if the input is already a block device with a matching sector size, it skips creating a loop and just hands back the original fd. That's fine for whole disks but wrong for partitions, which don't support partscan, so this causes dissect_image to fail with EPROTONOSUPPORT. This patch changes the behavior to only take the shortcut when the input is a whole disk, or when partscan was not requested. Co-Authored-By: Clayton Craft --- src/shared/loop-util.c | 47 +++++++++++++++++++++++++++++++++------ src/test/test-loop-util.c | 33 +++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index e6fe4dbb49d7a..3437afcda49f6 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -445,6 +445,42 @@ static int probe_sector_size_harder(int fd, uint32_t *ret) { return probe_sector_size(probe_fd, ret); } +static int loop_device_can_shortcut( + int fd, + uint64_t offset, + uint64_t size, + uint32_t sector_size, + uint32_t device_ssz, + uint32_t loop_flags) { + + int r; + + /* Returns whether we can hand back the original block device fd instead of allocating a real + * loopback device for it: it must cover the whole device, the requested sector size must match the + * device's sector size, and if partscan was requested it must already be enabled on the device + * (otherwise e.g. partition block devices or loop devices created without LO_FLAGS_PARTSCAN would + * be reused even though they cannot expose nested partitions). */ + + assert(fd >= 0); + + if (offset != 0) + return false; + if (!IN_SET(size, 0, UINT64_MAX)) + return false; + if (sector_size != device_ssz) + return false; + + if (FLAGS_SET(loop_flags, LO_FLAGS_PARTSCAN)) { + r = blockdev_partscan_enabled_fd(fd); + if (r < 0) + return r; + if (r == 0) + return false; + } + + return true; +} + static int loop_device_make_internal( const char *path, int fd, @@ -510,13 +546,10 @@ static int loop_device_make_internal( 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. 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. */ + r = loop_device_can_shortcut(fd, offset, size, sector_size, device_ssz, loop_flags); + if (r < 0) + return r; + if (r > 0) return loop_device_open_from_fd(fd, open_flags, lock_op, ret); } else { r = stat_verify_regular(&st); diff --git a/src/test/test-loop-util.c b/src/test/test-loop-util.c index f90bf0e1998fb..fca125564a18b 100644 --- a/src/test/test-loop-util.c +++ b/src/test/test-loop-util.c @@ -538,4 +538,37 @@ TEST(sector_size_mismatch) { loop = loop_device_unref(loop); } +TEST(partscan_required) { + _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)); + + /* Set up a backing loop device without LO_FLAGS_PARTSCAN. */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &block_loop)); + ASSERT_TRUE(block_loop->created); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + /* Without LO_FLAGS_PARTSCAN: shortcut should be taken (reuse existing loop). */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + loop = loop_device_unref(loop); + + /* With LO_FLAGS_PARTSCAN: backing loop has partscan disabled, so a new loop device with + * partscan must be created. */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + loop = loop_device_unref(loop); +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); From 1600978f5fc7ad0402d69e7d65c890fc5b10f9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 27 Apr 2026 22:56:50 +0200 Subject: [PATCH 1330/2155] shared/options: introduce "namespaces" for options This allows multiple option parsers to be defined in a single compilation unit. We put the OPTION_NAMESPACE("name") to split up the options. The basic implementation is similar to groups, except that groups only matter for help display, while namespaces matter for both help display and actual option parsing. When parsing, we locate the appropriate range between the beginning of options and the next namespace marker or between two namespace markers and only look at that range. --- src/shared/options.c | 74 +++++++++-- src/shared/options.h | 37 ++++-- src/test/test-options.c | 286 +++++++++++++++++++++++++++++----------- 3 files changed, 300 insertions(+), 97 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index faefbe659eae6..364b57792c877 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 ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + return ASSERT_PTR(opt)->flags & (OPTION_NAMESPACE_MARKER | + OPTION_GROUP_MARKER | OPTION_POSITIONAL_ENTRY | OPTION_HELP_ENTRY | OPTION_HELP_ENTRY_VERBATIM); @@ -81,7 +82,7 @@ int option_parse( /* Check and initialize */ switch (state->state) { - case OPTION_PARSER_INIT: + case OPTION_PARSER_INIT: { assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); if (state->argc < 1) { @@ -90,9 +91,34 @@ int option_parse( } assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */ + + /* Figure out the right range of options */ + bool in_ns = state->namespace == NULL; /* Are we currently in the section of the array that + * forms namespace ? The first part is the + * default unnamed namespace, so if the namespace was + * not specified, we are in it. */ + if (in_ns) + state->namespace_start = options; + + const Option *opt; + for (opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(state->namespace, opt->long_code); + if (in_ns) + state->namespace_start = opt + 1; + continue; + } + if (ns_marker) + break; /* End of namespace */ + } + assert(state->namespace_start); + state->namespace_end = opt; + state->optind = state->positional_offset = 1; state->state = OPTION_PARSER_RUNNING; break; + } case OPTION_PARSER_RUNNING: case OPTION_PARSER_STOPPING: @@ -156,10 +182,10 @@ int option_parse( if (handling_positional_arg) /* We are supposed to return the positional arg to be handled. */ - for (option = options;; option++) { + for (option = state->namespace_start;; option++) { /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, * OPTION_POSITIONAL must be used. */ - assert(option < options_end); + assert(option < state->namespace_end); if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) break; @@ -184,8 +210,8 @@ int option_parse( 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) { + for (option = state->namespace_start;; option++) { + if (option >= state->namespace_end) { if (n_partial_matches == 0) { r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: unrecognized option '%s'", @@ -193,7 +219,11 @@ int option_parse( goto fail; } if (n_partial_matches > 1) { - r = partial_match_error(options, options_end, optname, n_partial_matches); + r = partial_match_error( + state->namespace_start, + state->namespace_end, + optname, + n_partial_matches); goto fail; } @@ -230,8 +260,8 @@ int option_parse( } optname = _optname; - for (option = options;; option++) { - if (option >= options_end) { + for (option = state->namespace_start;; option++) { + if (option >= state->namespace_end) { r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: unrecognized option '%s'", program_invocation_short_name, optname); @@ -354,7 +384,8 @@ 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) { assert(opt); - assert(!FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)); /* A group marker should not be displayed */ + assert(!(opt->flags & (OPTION_NAMESPACE_MARKER | + OPTION_GROUP_MARKER))); /* The markers should not be displayed */ if (!prefix) prefix = ""; @@ -405,9 +436,10 @@ char* option_get_synopsis(const char *prefix, const Option *opt, const char *joi option_arg_optional(opt) ? "]" : ""); } -int _option_parser_get_help_table( +int _option_parser_get_help_table_full( const Option options[], const Option options_end[], + const char *namespace, const char *group, Table **ret) { int r; @@ -418,11 +450,23 @@ int _option_parser_get_help_table( 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. */ + bool in_ns = namespace == NULL; /* Are we currently in the section of the array that forms namespace + * ? The first part is the default unnamed namespace, so + * if the namespace was not specified, we are in it. */ + + bool in_group = group == NULL; /* Are we currently in the section of the array that forms group + * ? The first part is the default group, so if the group was + * not specified, we are in it. */ for (const Option *opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(namespace, opt->long_code); + continue; + } + if (ns_marker) + break; /* End of namespace */ + bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); if (!in_group) { in_group = group_marker && streq(group, opt->long_code); @@ -455,6 +499,8 @@ int _option_parser_get_help_table( return table_log_add_error(r); } + assert(!table_isempty(table)); /* The namespace or group were not found. Something is off. */ + table_set_header(table, false); *ret = TAKE_PTR(table); return 0; diff --git a/src/shared/options.h b/src/shared/options.h index 81b92d2da5ff1..7d8507d0e5444 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -4,13 +4,21 @@ #include "memory-util.h" #include "shared-forward.h" +/* Option namespace/group explanation: + * the list of options is split into namespaces, and a namespace is split into groups. + * By default, options defined in a single program are all placed in a single (unnamed) namespace + * and in a single (unnamed) group. OPTION_NAMESPACE() marks the beginning of a named namespace. + * OPTION_GROUP() marks the beginning of a named group. + */ + typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ 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 */ + OPTION_NAMESPACE_MARKER = 1U << 3, /* Fake option entry to separate namespaces */ + OPTION_GROUP_MARKER = 1U << 4, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 5, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 6, /* Same, but use the long_code in the first column as written */ } OptionFlags; typedef struct Option { @@ -41,9 +49,13 @@ typedef struct Option { }; \ case (0x100 + counter) +/* Magic entry in the table (which will not be returned) that designates the start of the namespace . + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ +#define OPTION_NAMESPACE(ns) \ + _OPTION(__COUNTER__, OPTION_NAMESPACE_MARKER, /* sc= */ 0, /* lc= */ ns, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) + /* 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. - */ + * 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, /* d= */ 0u, /* h= */ NULL) @@ -164,11 +176,15 @@ typedef enum OptionParserState { } OptionParserState; typedef struct OptionParser { - /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } - * or { argc, argv, mode }. */ + /* Those four should stay first so that it's possible to initialize the struct as { argc, argv } + * or { argc, argv, mode } or { argc, argv, mode, namespace }. */ int argc; /* The original argc. */ char **argv; /* The argv array, possibly reordered. */ OptionParserMode mode; + const char *namespace; /* The namespace, may be NULL. */ + + const Option *namespace_start, *namespace_end; /* The range of options that are part of our namespace. */ + OptionParserState state; int optind; /* Position of the parameter being handled. * 0 → option parsing hasn't been started yet. */ @@ -203,12 +219,15 @@ 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( +int _option_parser_get_help_table_full( const Option options[], const Option options_end[], + const char *namespace, const char *group, Table **ret); +#define option_parser_get_help_table_full(namespace, group, ret) \ + _option_parser_get_help_table_full(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, namespace, group, 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) + option_parser_get_help_table_full(/* namespace= */ NULL, group, ret) #define option_parser_get_help_table(ret) \ option_parser_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/test/test-options.c b/src/test/test-options.c index 6a0744e67363d..a77964df4e1fa 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -15,7 +15,8 @@ static void test_option_parse_one( const Option options[], const Entry *entries, char **remaining, - OptionParserMode mode) { + OptionParserMode mode, + const char *namespace) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -32,7 +33,7 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser opts = { argc, argv, mode }; + OptionParser opts = { argc, argv, mode, namespace }; for (int c; (c = option_parse(options, options + n_options, &opts)) != 0; ) { ASSERT_OK(c); ASSERT_NOT_NULL(opts.opt); @@ -90,6 +91,12 @@ TEST(option_parse) { { 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 }, + { 7, .long_code = "NS2", .flags = OPTION_NAMESPACE_MARKER }, + { 8, .short_code = 'h', .long_code = "help2" }, + { 9, .long_code = "version2" }, + { 10, .long_code = "NS3", .flags = OPTION_NAMESPACE_MARKER }, + { 11, .short_code = 'h', .long_code = "help3" }, + { 12, .long_code = "version3" }, {} }; @@ -97,7 +104,8 @@ TEST(option_parse) { options, NULL, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -110,7 +118,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--", @@ -124,7 +133,8 @@ TEST(option_parse) { "--help", "-h", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -138,7 +148,8 @@ TEST(option_parse) { "string2", "--", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -152,7 +163,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help"), @@ -162,7 +174,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -175,7 +188,8 @@ TEST(option_parse) { }, STRV_MAKE("string1", "--help"), - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-h"), @@ -185,7 +199,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -202,7 +217,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-h", @@ -219,7 +235,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -236,7 +253,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -253,7 +271,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -270,7 +289,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -287,7 +307,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--required1", "reqarg1"), @@ -297,7 +318,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-r", "reqarg1"), @@ -307,7 +329,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -320,7 +343,8 @@ TEST(option_parse) { }, STRV_MAKE("string1", "string2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -331,7 +355,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "-r", "reqarg1"), - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--optional1=optarg1"), @@ -341,7 +366,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--optional1", "string1"), @@ -351,7 +377,8 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-ooptarg1"), @@ -361,7 +388,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-o", "string1"), @@ -371,7 +399,8 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -441,7 +470,42 @@ TEST(option_parse) { "--help", "--required1", "--optional1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); + + /* Check that we can access options from NS2 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help2", + "--version2", + "string1"), + options, + (Entry[]) { + { "help2" }, + { "help2" }, + { "version2" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS2"); + + /* Check that we can access options from NS3 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help3", + "--version3", + "string1"), + options, + (Entry[]) { + { "help3" }, + { "help3" }, + { "version3" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS3"); } TEST(option_stops_parsing) { @@ -465,7 +529,8 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--help", "foo"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Options before --exec are still parsed */ test_option_parse_one(STRV_MAKE("arg0", @@ -481,7 +546,8 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--version", "bar"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --exec with no trailing args */ test_option_parse_one(STRV_MAKE("arg0", @@ -492,7 +558,8 @@ TEST(option_stops_parsing) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --exec after positional args */ test_option_parse_one(STRV_MAKE("arg0", @@ -509,7 +576,8 @@ TEST(option_stops_parsing) { "--help", "--required", "val"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* "--" 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 @@ -525,7 +593,8 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* "--" before --exec: "--" terminates first, --exec is positional */ test_option_parse_one(STRV_MAKE("arg0", @@ -536,7 +605,8 @@ TEST(option_stops_parsing) { NULL, STRV_MAKE("--exec", "--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Multiple options then --exec then more option-like args */ test_option_parse_one(STRV_MAKE("arg0", @@ -555,7 +625,8 @@ TEST(option_stops_parsing) { STRV_MAKE("-h", "--required", "val2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); } TEST(option_group_marker) { @@ -580,7 +651,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Check that group marker name is ignored */ test_option_parse_one(STRV_MAKE("arg0", @@ -593,7 +665,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Verify that the group marker is not mistaken for an option */ test_option_invalid_one(STRV_MAKE("arg0", @@ -621,7 +694,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Partial match with multiple candidates */ test_option_invalid_one(STRV_MAKE("arg0", @@ -645,7 +719,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Long option without = does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -656,7 +731,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option with inline arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -667,7 +743,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option without inline arg does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -678,7 +755,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Optional arg option at end of argv */ test_option_parse_one(STRV_MAKE("arg0", @@ -689,7 +767,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Mixed: optional arg with other options */ test_option_parse_one(STRV_MAKE("arg0", @@ -704,7 +783,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short combo: -ho (h then o with no arg) */ test_option_parse_one(STRV_MAKE("arg0", @@ -716,7 +796,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short combo: -hobar (h then o with inline arg "bar") */ test_option_parse_one(STRV_MAKE("arg0", @@ -728,7 +809,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); } /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros @@ -738,7 +820,8 @@ static void test_macros_parse_one( char **argv, const Entry *entries, char **remaining, - OptionParserMode mode) { + OptionParserMode mode, + const char *namespace) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -752,7 +835,7 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser opts = { argc, argv, mode }; + OptionParser opts = { argc, argv, mode, namespace }; FOREACH_OPTION(c, &opts, assert_not_reached()) { log_debug("%c %s: %s=%s", @@ -814,9 +897,14 @@ static void test_macros_parse_one( OPTION_POSITIONAL: break; + OPTION_NAMESPACE("namespaced options"): {} + + OPTION('r', "required2", "ARG", "Required arg option"): + break; + default: log_error("Unexpected option id: %d", c); - ASSERT_TRUE(false); + assert_not_reached(); } } @@ -838,7 +926,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION: short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -848,7 +937,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_LONG: only accessible via long form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -858,7 +948,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_SHORT: only accessible via short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -868,7 +959,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: long --required=ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -878,7 +970,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: long --required ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -888,7 +981,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: short -r ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -898,7 +992,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: short -rARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -908,7 +1003,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ test_macros_parse_one(STRV_MAKE("arg0", @@ -918,7 +1014,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ test_macros_parse_one(STRV_MAKE("arg0", @@ -928,7 +1025,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -938,7 +1036,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -948,7 +1047,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ test_macros_parse_one(STRV_MAKE("arg0", @@ -961,7 +1061,8 @@ TEST(option_macros) { }, STRV_MAKE("--help", "--version"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING: options before are still parsed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -976,7 +1077,8 @@ TEST(option_macros) { }, STRV_MAKE("-h", "--debug"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -988,7 +1090,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1000,7 +1103,8 @@ TEST(option_macros) { }, STRV_MAKE("--exec", "--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1010,7 +1114,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Mixed: all macro types together */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1039,7 +1144,8 @@ TEST(option_macros) { }, STRV_MAKE("pos1", "pos2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combos with macros: -hv (help + verbose) */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1050,7 +1156,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combo with required arg: -hrval (help + required with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1061,7 +1168,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1072,7 +1180,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1088,7 +1197,8 @@ TEST(option_macros) { }, STRV_MAKE("--version", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1105,7 +1215,8 @@ TEST(option_macros) { STRV_MAKE("--version", "--", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1123,7 +1234,8 @@ TEST(option_macros) { STRV_MAKE("--", "--version", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Basic OPTION_POSITIONAL use */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1139,7 +1251,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_RETURN_POSITIONAL_ARGS); + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + NULL); /* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1154,7 +1267,30 @@ TEST(option_macros) { {} }, STRV_MAKE("arg2"), - OPTION_PARSER_RETURN_POSITIONAL_ARGS); + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + NULL); + + /* Second namespace, OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required2=arg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); + + /* Second namespace, OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rarg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); } /* Test the pattern used by nspawn's --user: an optional-arg option that also @@ -1176,7 +1312,8 @@ TEST(option_optional_arg_consume) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --user without arg: next arg is an option, so no consumption */ test_option_parse_one(STRV_MAKE("arg0", @@ -1189,7 +1326,8 @@ TEST(option_optional_arg_consume) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --user without arg: next arg is positional (doesn't start with -). * The option parser returns NULL for the arg. The caller would then From ff902c7ccda2165f7c33231a0174384f0d1331b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 27 Apr 2026 23:58:24 +0200 Subject: [PATCH 1331/2155] run: reorder switch cases to match help() output order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both parse_argv() and parse_argv_sudo_mode() handled options in an order that no longer matched the help text. Reorder the case statements so the source order mirrors what the user sees in --help. In parse_argv_sudo_mode(), drop the case 'i' → ARG_VIA_SHELL fall-through so the cases can be sequenced independently; 'i' now sets arg_via_shell directly. Co-developed-by: Claude Opus 4.7 --- src/run/run.c | 252 +++++++++++++++++++++++++------------------------- 1 file changed, 127 insertions(+), 125 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index 596e12826753c..60eaeb12d5b9c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -433,6 +433,17 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + r = parse_machine_argument(optarg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + case 'C': r = capsule_name_is_valid(optarg); if (r < 0) @@ -453,6 +464,12 @@ static int parse_argv(int argc, char *argv[]) { arg_unit = optarg; break; + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + case ARG_DESCRIPTION: r = free_and_strdup_warn(&arg_description, optarg); if (r < 0) @@ -475,23 +492,20 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_SEND_SIGHUP: - arg_send_sighup = true; + case ARG_NO_BLOCK: + arg_no_block = true; break; case 'r': arg_remain_after_exit = true; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + case ARG_WAIT: + arg_wait = true; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + case ARG_SEND_SIGHUP: + arg_send_sighup = true; break; case ARG_SERVICE_TYPE: @@ -516,21 +530,53 @@ static int parse_argv(int argc, char *argv[]) { arg_nice_set = true; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + case ARG_WORKING_DIRECTORY: + r = parse_path_argument(optarg, true, &arg_working_directory); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return r; + same_dir = false; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); + case 'd': { + _cleanup_free_ char *p = NULL; + + r = safe_getcwd(&p); + if (r < 0) + return log_error_errno(r, "Failed to get current working directory: %m"); + if (empty_or_root(p)) + arg_working_directory = mfree(arg_working_directory); + else + free_and_replace(arg_working_directory, p); + + same_dir = true; break; + } + case ARG_ROOT_DIRECTORY: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root_directory); + if (r < 0) + return r; + + break; + + case 'R': + r = free_and_strdup_warn(&arg_root_directory, "/"); + if (r < 0) + return r; + + break; + + case 'E': + r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + + break; + + case 't': /* --pty (and --tty deprecated alias) */ case 'T': /* --pty-late */ - case 't': /* --pty */ arg_stdio |= ARG_STDIO_PTY; arg_pty_late = c == 'T'; break; @@ -556,6 +602,59 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + case 'G': + arg_aggressive_gc = true; + break; + + case 'S': + arg_shell = true; + break; + + case ARG_JOB_MODE: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); + + r = job_mode_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Invalid job mode: %s", optarg); + + arg_job_mode = r; + break; + + case ARG_IGNORE_FAILURE: + arg_ignore_failure = true; + break; + + case ARG_BACKGROUND: + r = parse_background_argument(optarg, &arg_background); + if (r < 0) + return r; + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_PATH_PROPERTY: + + if (strv_extend(&arg_path_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_SOCKET_PROPERTY: + + if (strv_extend(&arg_socket_property, optarg) < 0) + return log_oom(); + + break; + case ARG_ON_ACTIVE: r = add_timer_property("OnActiveSec", optarg); if (r < 0) @@ -653,105 +752,6 @@ static int parse_argv(int argc, char *argv[]) { "OnCalendar="); break; - case ARG_PATH_PROPERTY: - - if (strv_extend(&arg_path_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_SOCKET_PROPERTY: - - if (strv_extend(&arg_socket_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case ARG_WAIT: - arg_wait = true; - break; - - case ARG_WORKING_DIRECTORY: - r = parse_path_argument(optarg, true, &arg_working_directory); - if (r < 0) - return r; - - same_dir = false; - break; - - case 'd': { - _cleanup_free_ char *p = NULL; - - r = safe_getcwd(&p); - if (r < 0) - return log_error_errno(r, "Failed to get current working directory: %m"); - - if (empty_or_root(p)) - arg_working_directory = mfree(arg_working_directory); - else - free_and_replace(arg_working_directory, p); - - same_dir = true; - break; - } - - case ARG_ROOT_DIRECTORY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root_directory); - if (r < 0) - return r; - - break; - - case 'R': - r = free_and_strdup_warn(&arg_root_directory, "/"); - if (r < 0) - return r; - - break; - - case 'G': - arg_aggressive_gc = true; - break; - - case 'S': - arg_shell = true; - break; - - case ARG_JOB_MODE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); - - r = job_mode_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Invalid job mode: %s", optarg); - - arg_job_mode = r; - break; - - case ARG_IGNORE_FAILURE: - arg_ignore_failure = true; - break; - - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); - if (r < 0) - return r; - break; - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - break; - case '?': return -EINVAL; @@ -1066,6 +1066,18 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { break; + case ARG_VIA_SHELL: + arg_via_shell = true; + break; + + case 'i': + r = free_and_strdup_warn(&arg_working_directory, "~"); + if (r < 0) + return r; + + arg_via_shell = true; + break; + case ARG_SETENV: r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); if (r < 0) @@ -1113,16 +1125,6 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { break; - case 'i': - r = free_and_strdup_warn(&arg_working_directory, "~"); - if (r < 0) - return r; - - _fallthrough_; - case ARG_VIA_SHELL: - arg_via_shell = true; - break; - case ARG_EMPOWER: arg_empower = true; break; From 4c0e12b3b922f99a9be09e3be9df6a7a44b5db04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 00:04:50 +0200 Subject: [PATCH 1332/2155] run: convert parse_argv() to OPTION macros --system is now documented (fixup for 66b1e746055b9c56fd72c0451a4cfb2b06cf3f20). --capsule is now documented (fixup for 759b3c082d463a488235592df45cbebefbe1ad5c). --help output is generally the same, except for the formatting changes related to use of the new output helpers and common option macros. Co-developed-by: Claude Opus 4.7 --- src/run/run.c | 419 +++++++++++++++++--------------------------------- 1 file changed, 143 insertions(+), 276 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index 60eaeb12d5b9c..36ebe32f2d474 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -40,9 +40,11 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "help-util.h" #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" @@ -139,80 +141,43 @@ STATIC_DESTRUCTOR_REGISTER(arg_shell_prompt_prefix, freep); STATIC_DESTRUCTOR_REGISTER(arg_area, freep); static int help(void) { - _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-run", "1", &link); - if (r < 0) - return log_oom(); + static const char* const groups[] = { + NULL, + "Path options", + "Socket options", + "Timer options", + }; - printf("%1$s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%5$sRun the specified command in a transient scope or service.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --scope Run this as scope rather than service\n" - " -u --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set service or scope unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice from the caller\n" - " --expand-environment=BOOL Control expansion of environment variables\n" - " --no-block Do not wait until operation finished\n" - " -r --remain-after-exit Leave service around until explicitly stopped\n" - " --wait Wait until service stopped again\n" - " --send-sighup Send SIGHUP when terminating\n" - " --service-type=TYPE Service type\n" - " --uid=USER Run as system user\n" - " --gid=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " --working-directory=PATH Set working directory\n" - " -d --same-dir Inherit working directory from caller\n" - " --root-directory=PATH Set root directory\n" - " -R --same-root-dir Inherit root directory from caller\n" - " -E --setenv=NAME[=VALUE] Set environment variable\n" - " -t --pty Run service on pseudo TTY as STDIN/STDOUT/\n" - " STDERR\n" - " -T --pty-late Just like --pty, but leave TTY access to\n" - " agents until unit is started up\n" - " -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" - " --job-mode=MODE Specify how to deal with already queued jobs,\n" - " when queueing a new job\n" - " --ignore-failure Ignore the exit status of the invoked process\n" - " --background=COLOR Set ANSI color for background\n" - " --no-pager Do not pipe output into a pager\n" - "\n%3$sPath options:%4$s\n" - " --path-property=NAME=VALUE Set path unit property\n" - "\n%3$sSocket options:%4$s\n" - " --socket-property=NAME=VALUE Set socket unit property\n" - "\n%3$sTimer options:%4$s\n" - " --on-active=SECONDS Run after SECONDS delay\n" - " --on-boot=SECONDS Run SECONDS after machine was booted up\n" - " --on-startup=SECONDS Run SECONDS after systemd activation\n" - " --on-unit-active=SECONDS Run SECONDS after the last activation\n" - " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" - " --on-calendar=SPEC Realtime timer\n" - " --on-timezone-change Run when the timezone changes\n" - " --on-clock-change Run when the realtime clock jumps\n" - " --timer-property=NAME=VALUE Set timer unit property\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), ansi_normal(), - ansi_highlight(), ansi_normal()); + _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]); + + help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); + help_abstract("Run the specified command in a transient scope or service."); + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + _cleanup_free_ char *title = strjoin(groups[i] ?: "Options", ":"); + if (!title) + return log_oom(); + + help_section(title); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("systemd-run", "1"); return 0; } @@ -306,239 +271,142 @@ static char** make_login_shell_cmdline(const char *shell) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - ARG_SCOPE, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SLICE_INHERIT, - ARG_EXPAND_ENVIRONMENT, - ARG_SEND_SIGHUP, - ARG_SERVICE_TYPE, - ARG_EXEC_USER, - ARG_EXEC_GROUP, - ARG_NICE, - ARG_OUTPUT, - ARG_ON_ACTIVE, - ARG_ON_BOOT, - ARG_ON_STARTUP, - ARG_ON_UNIT_ACTIVE, - ARG_ON_UNIT_INACTIVE, - ARG_ON_CALENDAR, - ARG_ON_TIMEZONE_CHANGE, - ARG_ON_CLOCK_CHANGE, - ARG_TIMER_PROPERTY, - ARG_PATH_PROPERTY, - ARG_SOCKET_PROPERTY, - ARG_NO_BLOCK, - ARG_NO_ASK_PASSWORD, - ARG_WAIT, - ARG_WORKING_DIRECTORY, - ARG_ROOT_DIRECTORY, - ARG_JOB_MODE, - ARG_IGNORE_FAILURE, - ARG_BACKGROUND, - ARG_NO_PAGER, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "capsule", required_argument, NULL, 'C' }, - { "scope", no_argument, NULL, ARG_SCOPE }, - { "unit", required_argument, NULL, 'u' }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, - { "remain-after-exit", no_argument, NULL, 'r' }, - { "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT }, - { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, - { "wait", no_argument, NULL, ARG_WAIT }, - { "uid", required_argument, NULL, ARG_EXEC_USER }, - { "gid", required_argument, NULL, ARG_EXEC_GROUP }, - { "nice", required_argument, NULL, ARG_NICE }, - { "setenv", required_argument, NULL, 'E' }, - { "property", required_argument, NULL, 'p' }, - { "tty", no_argument, NULL, 't' }, /* deprecated alias */ - { "pty", no_argument, NULL, 't' }, - { "pty-late", no_argument, NULL, 'T' }, - { "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 }, - { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, - { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, - { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, - { "on-timezone-change", no_argument, NULL, ARG_ON_TIMEZONE_CHANGE }, - { "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE }, - { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, - { "path-property", required_argument, NULL, ARG_PATH_PROPERTY }, - { "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "collect", no_argument, NULL, 'G' }, - { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY }, - { "same-dir", no_argument, NULL, 'd' }, - { "root-directory", required_argument, NULL, ARG_ROOT_DIRECTORY }, - { "same-root-dir", no_argument, NULL, 'R' }, - { "shell", no_argument, NULL, 'S' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - {}, - }; - bool with_trigger = false, same_dir = false; - int r, c; + int r; 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, "+hrC:H:M:E:p:tTPqvGdSu:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Run as user unit"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Talk to the service manager (implied default)"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION('C', "capsule", "NAME", "Operate on specified capsule"): + r = capsule_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SCOPE: + OPTION_LONG("scope", NULL, "Run this as scope rather than service"): arg_scope = true; break; - case 'u': - arg_unit = optarg; + OPTION('u', "unit", "UNIT", "Run under the specified unit name"): + arg_unit = opts.arg; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) + OPTION('p', "property", "NAME=VALUE", "Set service or scope unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case ARG_SLICE: - r = free_and_strdup_warn(&arg_slice, optarg); + OPTION_LONG("slice", "SLICE", "Run in the specified slice"): + r = free_and_strdup_warn(&arg_slice, opts.arg); if (r < 0) return r; break; - case ARG_SLICE_INHERIT: + OPTION_LONG("slice-inherit", NULL, "Inherit the slice from the caller"): arg_slice_inherit = true; break; - case ARG_EXPAND_ENVIRONMENT: - r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment); + OPTION_LONG("expand-environment", "BOOL", + "Control expansion of environment variables"): + r = parse_boolean_argument("--expand-environment=", opts.arg, &arg_expand_environment); if (r < 0) return r; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - case 'r': + OPTION('r', "remain-after-exit", NULL, + "Leave service around until explicitly stopped"): arg_remain_after_exit = true; break; - case ARG_WAIT: + OPTION_LONG("wait", NULL, "Wait until service stopped again"): arg_wait = true; break; - case ARG_SEND_SIGHUP: + OPTION_LONG("send-sighup", NULL, "Send SIGHUP when terminating"): arg_send_sighup = true; break; - case ARG_SERVICE_TYPE: - arg_service_type = optarg; + OPTION_LONG("service-type", "TYPE", "Service type"): + arg_service_type = opts.arg; break; - case ARG_EXEC_USER: - r = free_and_strdup_warn(&arg_exec_user, optarg); + OPTION_LONG("uid", "USER", "Run as system user"): + r = free_and_strdup_warn(&arg_exec_user, opts.arg); if (r < 0) return r; break; - case ARG_EXEC_GROUP: - arg_exec_group = optarg; + OPTION_LONG("gid", "GROUP", "Run as system group"): + arg_exec_group = opts.arg; break; - case ARG_NICE: - r = parse_nice(optarg, &arg_nice); + OPTION_LONG("nice", "NICE", "Nice level"): + r = parse_nice(opts.arg, &arg_nice); if (r < 0) - return log_error_errno(r, "Failed to parse nice value: %s", optarg); + return log_error_errno(r, "Failed to parse nice value: %s", opts.arg); arg_nice_set = true; break; - case ARG_WORKING_DIRECTORY: - r = parse_path_argument(optarg, true, &arg_working_directory); + OPTION_LONG("working-directory", "PATH", "Set working directory"): + r = parse_path_argument(opts.arg, true, &arg_working_directory); if (r < 0) return r; same_dir = false; break; - case 'd': { + OPTION('d', "same-dir", NULL, "Inherit working directory from caller"): { _cleanup_free_ char *p = NULL; r = safe_getcwd(&p); @@ -554,151 +422,156 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_ROOT_DIRECTORY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root_directory); + OPTION_LONG("root-directory", "PATH", "Set root directory"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root_directory); if (r < 0) return r; - break; - case 'R': + OPTION('R', "same-root-dir", NULL, "Inherit root directory from caller"): r = free_and_strdup_warn(&arg_root_directory, "/"); if (r < 0) return r; - break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + OPTION('E', "setenv", "NAME[=VALUE]", "Set environment variable"): + r = strv_env_replace_strdup_passthrough(&arg_environment, opts.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", opts.arg); break; - case 't': /* --pty (and --tty deprecated alias) */ - case 'T': /* --pty-late */ + OPTION_LONG("tty", NULL, NULL): {} /* deprecated alias for --pty */ + OPTION('t', "pty", NULL, + "Run service on pseudo TTY as STDIN/STDOUT/STDERR"): {} + OPTION('T', "pty-late", NULL, + "Just like --pty, but leave TTY access to agents until unit is started up"): arg_stdio |= ARG_STDIO_PTY; - arg_pty_late = c == 'T'; + arg_pty_late = opts.opt->short_code == 'T'; break; - case 'P': /* --pipe */ + OPTION('P', "pipe", NULL, "Pass STDIN/STDOUT/STDERR directly to service"): arg_stdio |= ARG_STDIO_DIRECT; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case 'v': + OPTION('v', "verbose", NULL, "Show unit logs while executing operation"): arg_verbose = true; break; - case ARG_OUTPUT: - if (streq(optarg, "help")) + OPTION_LONG("output", "MODE", + "Controls formatting of verbose logs, see journalctl for valid values"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(opts.arg); if (arg_output < 0) - return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); + return log_error_errno(arg_output, "Unknown output format '%s'.", opts.arg); break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case 'G': + OPTION('G', "collect", NULL, "Unload unit after it ran, even when failed"): arg_aggressive_gc = true; break; - case 'S': + OPTION('S', "shell", NULL, "Invoke a $SHELL interactively"): arg_shell = true; break; - case ARG_JOB_MODE: - if (streq(optarg, "help")) + OPTION_LONG("job-mode", "MODE", + "Specify how to deal with already queued jobs, when queueing a new job"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); - r = job_mode_from_string(optarg); + r = job_mode_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Invalid job mode: %s", optarg); + return log_error_errno(r, "Invalid job mode: %s", opts.arg); arg_job_mode = r; break; - case ARG_IGNORE_FAILURE: + OPTION_LONG("ignore-failure", NULL, "Ignore the exit status of the invoked process"): arg_ignore_failure = 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(opts.arg, &arg_background); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_PATH_PROPERTY: + OPTION_GROUP("Path options"): {} - if (strv_extend(&arg_path_property, optarg) < 0) + OPTION_LONG("path-property", "NAME=VALUE", "Set path unit property"): + if (strv_extend(&arg_path_property, opts.arg) < 0) return log_oom(); - break; - case ARG_SOCKET_PROPERTY: + OPTION_GROUP("Socket options"): {} - if (strv_extend(&arg_socket_property, optarg) < 0) + OPTION_LONG("socket-property", "NAME=VALUE", "Set socket unit property"): + if (strv_extend(&arg_socket_property, opts.arg) < 0) return log_oom(); - break; - case ARG_ON_ACTIVE: - r = add_timer_property("OnActiveSec", optarg); + OPTION_GROUP("Timer options"): {} + + OPTION_LONG("on-active", "SECONDS", "Run after SECONDS delay"): + r = add_timer_property("OnActiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_BOOT: - r = add_timer_property("OnBootSec", optarg); + OPTION_LONG("on-boot", "SECONDS", "Run SECONDS after machine was booted up"): + r = add_timer_property("OnBootSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_STARTUP: - r = add_timer_property("OnStartupSec", optarg); + OPTION_LONG("on-startup", "SECONDS", "Run SECONDS after systemd activation"): + r = add_timer_property("OnStartupSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_UNIT_ACTIVE: - r = add_timer_property("OnUnitActiveSec", optarg); + OPTION_LONG("on-unit-active", "SECONDS", "Run SECONDS after the last activation"): + r = add_timer_property("OnUnitActiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_UNIT_INACTIVE: - r = add_timer_property("OnUnitInactiveSec", optarg); + OPTION_LONG("on-unit-inactive", "SECONDS", + "Run SECONDS after the last deactivation"): + r = add_timer_property("OnUnitInactiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_CALENDAR: { + OPTION_LONG("on-calendar", "SPEC", "Realtime timer"): { _cleanup_(calendar_spec_freep) CalendarSpec *cs = NULL; - r = calendar_spec_from_string(optarg, &cs); + r = calendar_spec_from_string(opts.arg, &cs); if (r < 0) return log_error_errno(r, "Failed to parse calendar event specification: %m"); @@ -713,7 +586,7 @@ static int parse_argv(int argc, char *argv[]) { else if (r < 0) return log_error_errno(r, "Failed to calculate next time calendar expression elapses: %m"); - r = add_timer_property("OnCalendar", optarg); + r = add_timer_property("OnCalendar", opts.arg); if (r < 0) return r; @@ -721,7 +594,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_ON_TIMEZONE_CHANGE: + OPTION_LONG("on-timezone-change", NULL, "Run when the timezone changes"): r = add_timer_property("OnTimezoneChange", "yes"); if (r < 0) return r; @@ -729,7 +602,7 @@ static int parse_argv(int argc, char *argv[]) { arg_with_timer = true; break; - case ARG_ON_CLOCK_CHANGE: + OPTION_LONG("on-clock-change", NULL, "Run when the realtime clock jumps"): r = add_timer_property("OnClockChange", "yes"); if (r < 0) return r; @@ -737,13 +610,12 @@ static int parse_argv(int argc, char *argv[]) { arg_with_timer = true; break; - case ARG_TIMER_PROPERTY: - - if (strv_extend(&arg_timer_property, optarg) < 0) + OPTION_LONG("timer-property", "NAME=VALUE", "Set timer unit property"): + if (strv_extend(&arg_timer_property, opts.arg) < 0) return log_oom(); arg_with_timer = arg_with_timer || - STARTSWITH_SET(optarg, + STARTSWITH_SET(opts.arg, "OnActiveSec=", "OnBootSec=", "OnStartupSec=", @@ -751,12 +623,6 @@ static int parse_argv(int argc, char *argv[]) { "OnUnitInactiveSec=", "OnCalendar="); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* If we are talking to the per-user instance PolicyKit isn't going to help */ @@ -805,13 +671,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_pty_late < 0) arg_pty_late = false; /* For systemd-run this defaults to false, for compat reasons */ - if (argc > optind) { + char **args = option_parser_get_args(&opts); + if (!strv_isempty(args)) { char **l; if (arg_shell) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --shell is used, no command line is expected."); - l = strv_copy(argv + optind); + l = strv_copy(args); if (!l) return log_oom(); From 03a58fe2517a8015ba83eb9db87e69d60f9a0438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 00:30:08 +0200 Subject: [PATCH 1333/2155] run: convert run0 option parser to macros This exercises the new option namespace code. --help output is generally the same, except for the formatting changes related to use of the new output helpers and the common option macros. --same-root-dir is now documented (followup for 475729b80532dfbbce98705dade6570ce5cc29f0). Co-developed-by: Claude Opus 4.7 --- src/run/run.c | 227 ++++++++++++++++---------------------------------- 1 file changed, 71 insertions(+), 156 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index 36ebe32f2d474..885ad23b15f9c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -182,51 +181,27 @@ static int help(void) { } static int help_sudo_mode(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *opts_table = NULL; int r; - r = terminal_urlify_man("run0", "1", &link); - if (r < 0) - return log_oom(); - /* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with * sudo's short switches, hence please do not introduce new short switches unless they have a roughly * equivalent purpose on sudo. Use long options for everything private to run0. */ - printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%sElevate privileges interactively.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --machine=CONTAINER Operate on local container\n" - " --unit=UNIT Run under the specified unit name\n" - " --property=NAME=VALUE Set service or scope unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice\n" - " -u --user=USER Run as system user\n" - " -g --group=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " -D --chdir=PATH Set working directory\n" - " --via-shell Invoke command via target user's login shell\n" - " -i Shortcut for --via-shell --chdir='~'\n" - " --setenv=NAME[=VALUE] Set environment variable\n" - " --background=COLOR Set ANSI color for background\n" - " --pty Request allocation of a pseudo TTY for stdio\n" - " --pty-late Just like --pty, but leave TTY access to agents\n" - " until unit is started up\n" - " --pipe Request direct pipe for stdio\n" - " --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n" - " --lightweight=BOOLEAN Control whether to register a session with service manager\n" - " or without\n" - " --area=AREA Home area to log into\n" - " --empower Give privileges to selected or current user\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table_full("run0", /* group= */ NULL, &opts_table); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); + help_abstract("Elevate privileges interactively."); + help_section("Options:"); + + r = table_print_or_warn(opts_table); + if (r < 0) + return r; + + help_man_page_reference("run0", "1"); return 0; } @@ -795,149 +770,96 @@ static Glyph pty_window_glyph(void) { } static int parse_argv_sudo_mode(int argc, char *argv[]) { - - enum { - ARG_NO_ASK_PASSWORD = 0x100, - ARG_MACHINE, - ARG_UNIT, - ARG_PROPERTY, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SLICE_INHERIT, - ARG_NICE, - ARG_SETENV, - ARG_BACKGROUND, - ARG_PTY, - ARG_PTY_LATE, - ARG_PIPE, - ARG_SHELL_PROMPT_PREFIX, - ARG_LIGHTWEIGHT, - ARG_AREA, - ARG_VIA_SHELL, - ARG_EMPOWER, - ARG_SAME_ROOT_DIR, - }; + int r; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions * though (but limit the extension to long options). */ - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "machine", required_argument, NULL, ARG_MACHINE }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, - { "user", required_argument, NULL, 'u' }, - { "group", required_argument, NULL, 'g' }, - { "nice", required_argument, NULL, ARG_NICE }, - { "chdir", required_argument, NULL, 'D' }, - { "via-shell", no_argument, NULL, ARG_VIA_SHELL }, - { "login", no_argument, NULL, 'i' }, /* compat with sudo, --via-shell + --chdir='~' */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "pty", no_argument, NULL, ARG_PTY }, - { "pty-late", no_argument, NULL, ARG_PTY_LATE }, - { "pipe", no_argument, NULL, ARG_PIPE }, - { "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX }, - { "lightweight", required_argument, NULL, ARG_LIGHTWEIGHT }, - { "area", required_argument, NULL, ARG_AREA }, - { "empower", no_argument, NULL, ARG_EMPOWER }, - { "same-root-dir", no_argument, NULL, ARG_SAME_ROOT_DIR }, - {}, - }; - - int r, c; - 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, "+hVu:g:D:i", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "run0" }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_NAMESPACE("run0"): {} + + OPTION_COMMON_HELP: return help_sudo_mode(); - case 'V': + OPTION('V', "version", NULL, "Show package version"): return version(); - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_MACHINE: - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_LONG("machine", "CONTAINER", "Operate on local container"): + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_UNIT: - arg_unit = optarg; + OPTION_LONG("unit", "UNIT", "Run under the specified unit name"): + arg_unit = opts.arg; break; - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) + OPTION_LONG("property", "NAME=VALUE", "Set service or scope unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case ARG_SLICE: - r = free_and_strdup_warn(&arg_slice, optarg); + OPTION_LONG("slice", "SLICE", "Run in the specified slice"): + r = free_and_strdup_warn(&arg_slice, opts.arg); if (r < 0) return r; break; - case ARG_SLICE_INHERIT: + OPTION_LONG("slice-inherit", NULL, "Inherit the slice"): arg_slice_inherit = true; break; - case 'u': - r = free_and_strdup_warn(&arg_exec_user, optarg); + OPTION('u', "user", "USER", "Run as system user"): + r = free_and_strdup_warn(&arg_exec_user, opts.arg); if (r < 0) return r; break; - case 'g': - arg_exec_group = optarg; + OPTION('g', "group", "GROUP", "Run as system group"): + arg_exec_group = opts.arg; break; - case ARG_NICE: - r = parse_nice(optarg, &arg_nice); + OPTION_LONG("nice", "NICE", "Nice level"): + r = parse_nice(opts.arg, &arg_nice); if (r < 0) - return log_error_errno(r, "Failed to parse nice value: %s", optarg); + return log_error_errno(r, "Failed to parse nice value: %s", opts.arg); arg_nice_set = true; break; - case 'D': - if (streq(optarg, "~")) - r = free_and_strdup_warn(&arg_working_directory, optarg); + OPTION('D', "chdir", "PATH", "Set working directory"): + if (streq(opts.arg, "~")) + r = free_and_strdup_warn(&arg_working_directory, opts.arg); else /* Root will be manually suppressed later. */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_working_directory); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_working_directory); if (r < 0) return r; - break; - case ARG_VIA_SHELL: + OPTION_LONG("via-shell", NULL, "Invoke command via target user's login shell"): arg_via_shell = true; break; - case 'i': + OPTION_LONG("login", NULL, NULL): {} /* hidden compat alias for -i */ + OPTION_SHORT('i', NULL, "Shortcut for --via-shell --chdir='~'"): r = free_and_strdup_warn(&arg_working_directory, "~"); if (r < 0) return r; @@ -945,69 +867,61 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_via_shell = true; break; - case ARG_SETENV: - r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + OPTION_LONG("setenv", "NAME[=VALUE]", "Set environment variable"): + r = strv_env_replace_strdup_passthrough(&arg_environment, opts.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", opts.arg); break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(opts.arg, &arg_background); if (r < 0) return r; - break; - case ARG_PTY: - case ARG_PTY_LATE: + OPTION_LONG("pty", NULL, "Request allocation of a pseudo TTY for stdio"): {} + OPTION_LONG("pty-late", NULL, + "Just like --pty, but leave TTY access to agents until unit is started up"): arg_stdio |= ARG_STDIO_PTY; - arg_pty_late = c == ARG_PTY_LATE; + arg_pty_late = streq(opts.opt->long_code, "pty-late"); break; - case ARG_PIPE: + OPTION_LONG("pipe", NULL, "Request direct pipe for stdio"): arg_stdio |= ARG_STDIO_DIRECT; break; - case ARG_SHELL_PROMPT_PREFIX: - r = free_and_strdup_warn(&arg_shell_prompt_prefix, optarg); + OPTION_LONG("shell-prompt-prefix", "PREFIX", "Set $SHELL_PROMPT_PREFIX"): + r = free_and_strdup_warn(&arg_shell_prompt_prefix, opts.arg); if (r < 0) return r; break; - case ARG_LIGHTWEIGHT: - r = parse_tristate_argument_with_auto("--lightweight=", optarg, &arg_lightweight); + OPTION_LONG("lightweight", "BOOLEAN", + "Control whether to register a session with service manager or without"): + r = parse_tristate_argument_with_auto("--lightweight=", opts.arg, &arg_lightweight); if (r < 0) return r; break; - case ARG_AREA: + OPTION_LONG("area", "AREA", "Home area to log into"): /* We allow an empty --area= specification to allow logging into the primary home directory */ - if (!isempty(optarg) && !filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", optarg); + if (!isempty(opts.arg) && !filename_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", opts.arg); - r = free_and_strdup_warn(&arg_area, optarg); + r = free_and_strdup_warn(&arg_area, opts.arg); if (r < 0) return r; - break; - case ARG_EMPOWER: + OPTION_LONG("empower", NULL, "Give privileges to selected or current user"): arg_empower = true; break; - case ARG_SAME_ROOT_DIR: + OPTION_LONG("same-root-dir", NULL, NULL): /* hidden */ r = free_and_strdup_warn(&arg_root_directory, "/"); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_working_directory) { @@ -1055,8 +969,9 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_send_sighup = true; _cleanup_strv_free_ char **l = NULL; - if (argc > optind) { - l = strv_copy(argv + optind); + char **args = option_parser_get_args(&opts); + if (!strv_isempty(args)) { + l = strv_copy(args); if (!l) return log_oom(); } else if (!arg_via_shell) { From 0a85be9ccf1bdd0fb19caf1178958f243c6fc9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 08:11:40 +0200 Subject: [PATCH 1334/2155] run: use a "named namespace" also for the main option parser It seems that clang reorders the entries in the options array that originate from different functions, but not within a function. Using "named namespaces" exclusively should sidestep the issue. (A bigger hammer would be to sort the array. We *can* do this, since the options have the increasing .id field. But that'd require duplicating the memory or making it writable. Let's avoid this until we know for sure that it's needed.) --- src/run/run.c | 6 ++++-- src/shared/options.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index 885ad23b15f9c..ce35b48fba4cb 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -154,7 +154,7 @@ static int help(void) { _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]); + r = option_parser_get_help_table_full("systemd-run", groups[i], &tables[i]); if (r < 0) return r; } @@ -252,11 +252,13 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "systemd-run" }; FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { + OPTION_NAMESPACE("systemd-run"): {} + OPTION_COMMON_HELP: return help(); diff --git a/src/shared/options.h b/src/shared/options.h index 7d8507d0e5444..8b8fc9ed4a819 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -9,6 +9,8 @@ * By default, options defined in a single program are all placed in a single (unnamed) namespace * and in a single (unnamed) group. OPTION_NAMESPACE() marks the beginning of a named namespace. * OPTION_GROUP() marks the beginning of a named group. + * + * Note: if multiple namespaces are used, they should all be named. */ typedef enum OptionFlags { From 4c640beba6faf717d7f145036af4fa5ba430e6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 08:45:03 +0200 Subject: [PATCH 1335/2155] shared/options: add an assert loop to verify ordering If things are misordered, we need to catch this. Use assert_se to make the check also in custom builds that otherwise disable assertions. --- src/shared/options.c | 6 ++++++ src/shared/options.h | 9 ++++++++- src/test/test-options.c | 18 +++++++++--------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 364b57792c877..227ba8ffa3522 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -101,6 +101,12 @@ int option_parse( state->namespace_start = options; const Option *opt; + + /* Verify that the option array didn't get mangled within a namespace. */ + for (opt = options; opt < options_end; opt++) + if (opt + 1 < options_end && !FLAGS_SET((opt + 1)->flags, OPTION_NAMESPACE_MARKER)) + assert_se(opt->id < (opt + 1)->id); + for (opt = options; opt < options_end; opt++) { bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); if (!in_ns) { diff --git a/src/shared/options.h b/src/shared/options.h index 8b8fc9ed4a819..7bb73dd5811c2 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -10,7 +10,14 @@ * and in a single (unnamed) group. OPTION_NAMESPACE() marks the beginning of a named namespace. * OPTION_GROUP() marks the beginning of a named group. * - * Note: if multiple namespaces are used, they should all be named. + * Note: if multiple namespaces are used, they should all be named, i.e. each separate parse_argv + * instance should have OPTION_NAMESPACE first, and then its set of OPTION()s. (This is because + * clang reorders OPTIONs coming from different functions. So an unnamed group could end up being + * merged with one of the earlier groups. It seems that reordering within a single function does + * not happen.) + * + * When groups are used, the first group may be named (with OPTION_GROUP appearing before any + * options), or it may be unnamed. Both variants should work fine. */ typedef enum OptionFlags { diff --git a/src/test/test-options.c b/src/test/test-options.c index a77964df4e1fa..150c2b2a5664a 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -631,12 +631,12 @@ TEST(option_stops_parsing) { 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" }, + { __COUNTER__, .short_code = 'h', .long_code = "help" }, + { __COUNTER__, .long_code = "version" }, + { __COUNTER__, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER }, + { __COUNTER__, .long_code = "debug" }, + { __COUNTER__, .long_code = "Advance" }, /* prefix match with the group */ + { __COUNTER__, .long_code = "defilbrilate" }, {} }; @@ -1297,9 +1297,9 @@ TEST(option_macros) { * 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" }, + { __COUNTER__, .short_code = 'h', .long_code = "help" }, + { __COUNTER__, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG }, + { __COUNTER__, .short_code = 'u', .long_code = "uid", .metavar = "USER" }, {} }; From 46bd1d1e88cda94e548bbf775a59cbcdef651c00 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Apr 2026 12:04:20 +0200 Subject: [PATCH 1336/2155] update TODO --- TODO.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/TODO.md b/TODO.md index d77225c5720ca..e51f6734f065e 100644 --- a/TODO.md +++ b/TODO.md @@ -135,6 +135,16 @@ SPDX-License-Identifier: LGPL-2.1-or-later 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 a report backend that simply exposes a bunch of static files that are + symlinked to some dir {/run,/etc/,/var/lib/}systemd/report-files/ or so as + facts. Use that for exposing SSH keys and suchlike. + +- report generators for: + - ip addresses + - imds address + - tpm event log + - open IP ports + - a way for container managers to turn off getty starting via $container_headless= or so... - add "conditions" for bls type 1 and type 2 profiles that allow suppressing @@ -167,6 +177,20 @@ SPDX-License-Identifier: LGPL-2.1-or-later - add --vacuum-xyz options to coredumpctl, matching those journalctl already has. +- sysupdate: in .transfer files have a 2nd url that is used if we + auto-rollbacked the OS before. + +- sysupdate: optionally enrich URL with countme=1 once a week + +- sysupdate: have an explicit concept of update policies: i.e. a choice of at least + - download list + report updates in motd – but do not auto update + - download list + download new version – but do not apply it + - download list + download new version + apply it – but do not reboot + - download list + donwload new version + apply it + reboot + Other things the policy shoudl contain is when to place the reboot. + This would all decouple the updating of the package list from the application + of it. Which is great for "countme" style stuff. + - 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. From f6b15dc63d482397de995552d0e00e67aabea2e7 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 28 Apr 2026 09:26:12 +0200 Subject: [PATCH 1337/2155] ci: Reduce noise from claude-review workflow --- .github/workflows/claude-review.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index e319214bf087f..6e0c32d6aec30 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -309,14 +309,20 @@ jobs: Then: 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 + 2. Drop any issue whose suggestion is to add a code comment, docstring, + or documentation (e.g. "add a comment explaining…", "missing + docstring", "document this function", "would benefit from a + comment"). Project style is to write no comments unless the WHY is + non-obvious, so these are noise — drop them from `comments` and from + the `summary`. + 3. 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 + 4. 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. + 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: From bb17ded158f820278af849ae6b6a5b122a07f797 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 27 Apr 2026 23:46:37 +0200 Subject: [PATCH 1338/2155] mkosi: Install liburing Make sure liburing is installed so it's available for experimentation in sd-event integration. --- 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 + mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf | 1 + mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf | 1 + mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf | 1 + mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf | 1 + 8 files changed, 8 insertions(+) diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index 229cc6394b172..d3c284a2f4bc4 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -30,6 +30,7 @@ Packages= iputils knot libucontext + liburing linux man-db multipath-tools diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index fc9ffd58c968b..4bf316eb89201 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -45,6 +45,7 @@ Packages= libcap-ng-utils libmicrohttpd libucontext + liburing man-db nmap-ncat openssh-clients diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf index 8a4e534ddad60..7efbf358342d4 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -50,6 +50,7 @@ Packages= libcap-ng-utils libdw-dev libdw1 + liburing2 locales login man-db diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index 5abebc04cf18b..14233915e3df2 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -59,6 +59,7 @@ Packages= libdw1 libmicrohttpd12 libtss2-tcti-device0 + liburing2 libz1 man multipath-tools diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf index 52889cb0b4357..2fcf333324afb 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf @@ -11,6 +11,7 @@ Packages= github-cli lcov libucontext + liburing musl mypy pkgconf diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf index bc998baad6b0c..06ff1b66258f2 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf @@ -13,4 +13,5 @@ Packages= libasan libubsan libucontext-devel + liburing-devel compiler-rt 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 613d9d87d917f..f3e13c40af363 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf @@ -10,5 +10,6 @@ Packages= clang-tidy coccinelle lcov + liburing-dev mypy shellcheck diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf index 6f24649c54c6c..ac15f5c3be927 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf @@ -11,6 +11,7 @@ Packages= gh lcov libtss2-tcti-device0 + liburing-devel mypy python3-ruff rpm-build From 01bf2b9f523d3bcddf74cac6af4ac5c36ce75550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 10:54:43 +0200 Subject: [PATCH 1339/2155] sd-varlink: drop pointless bitfield As is often the case, in this case because of alignment, we are actually not saving any space. With the bitfield we are using one bit of the 8 bytes allocated, and without the bitfield we are using 8 bits of that. But we're paying a price in generated code, at every access site to the field: $ diff <(objdump -S build/libsystemd.so.old) <(objdump -S build/libsystemd.so.new) ... v->protocol_upgrade = false; - fa2d2: 48 8b 45 a8 mov -0x58(%rbp),%rax - fa2d6: 0f b6 90 90 01 00 00 movzbl 0x190(%rax),%edx - fa2dd: 83 e2 fe and $0xfffffffe,%edx - fa2e0: 88 90 90 01 00 00 mov %dl,0x190(%rax) + fa2a9: 48 8b 45 a8 mov -0x58(%rbp),%rax + fa2ad: c6 80 90 01 00 00 00 movb $0x0,0x190(%rax) --- src/libsystemd/sd-varlink/varlink-internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 8087c2c432464..ff359852f488f 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -107,7 +107,7 @@ typedef struct sd_varlink { * 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; + bool protocol_upgrade; void *userdata; From 242fca7516eeedb3157be47be83f608d56592a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 11:00:00 +0200 Subject: [PATCH 1340/2155] sd-varlink: reduce size of varlink structs struct sd_varlink: - /* size: 448, cachelines: 7, members: 21 */ + /* size: 432, cachelines: 7, members: 21 */ struct sd_varlink_server: - /* size: 160, cachelines: 3, members: 21 */ + /* size: 152, cachelines: 3, members: 21 */ --- src/libsystemd/sd-varlink/varlink-internal.h | 28 +++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index ff359852f488f..32d6d5983a75f 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -74,9 +74,8 @@ typedef enum VarlinkState { typedef struct sd_varlink { unsigned n_ref; - sd_varlink_server *server; - VarlinkState state; + sd_varlink_server *server; /* Transport layer: input/output buffers, fd passing, output queue, read/write/parse * step functions, sd-event integration (input/output/time event sources, idle @@ -87,6 +86,13 @@ typedef struct sd_varlink { unsigned n_pending; + /* 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; + sd_varlink_reply_t reply_callback; sd_json_variant *current; @@ -102,13 +108,6 @@ typedef struct sd_varlink { size_t n_previous_fds; char *sentinel; - /* 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; - void *userdata; sd_event_source *quit_event_source; @@ -145,8 +144,12 @@ typedef struct sd_varlink_server { sd_event *event; int64_t event_priority; - unsigned n_connections; Hashmap *by_uid; /* UID_TO_PTR(uid) → UINT_TO_PTR(n_connections) */ + unsigned n_connections; + unsigned connections_max; + unsigned connections_per_uid_max; + + bool exit_on_idle; void *userdata; @@ -155,11 +158,6 @@ typedef struct sd_varlink_server { char *product; char *version; char *url; - - unsigned connections_max; - unsigned connections_per_uid_max; - - bool exit_on_idle; } sd_varlink_server; #define varlink_log_errno(v, error, fmt, ...) \ From d9e0883316d119fb484c677f27ba24f31baded72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 11:20:06 +0200 Subject: [PATCH 1341/2155] sd-json: stop printing debug messages about extension fields The intent was good, but we now print two or three of those messages for each report metrics received on the wire. If the json object is extensible, then it's all good and we don't need to inundate the user with this trivial information. (And the message also sounds like something is wrong or unexpected, when it totally isn't.) ... (string):1:73: Unrecognized object field 'object', assuming extension. (string):1:89: Unrecognized object field 'value', assuming extension. json-stream: Received message: {"parameters":{"name":"io.systemd.Network.CarrierState","object":"virbr0","value":"degraded-carrier"},"continues":true} (string):1:66: Unrecognized object field 'object', assuming extension. (string):1:83: Unrecognized object field 'value', assuming extension. json-stream: Received message: {"parameters":{"name":"io.systemd.Network.CarrierState","object":"lo","value":"carrier"},"continues":true} (string):1:66: Unrecognized object field 'object', assuming extension. (string):1:79: Unrecognized object field 'value', assuming extension. json-stream: Received message: {"parameters":{"name":"io.systemd.Network.CarrierState","object":"wlp0s20f3","value":"carrier"},"continues":true} (string):1:66: Unrecognized object field 'object', assuming extension. (string):1:86: Unrecognized object field 'value', assuming extension. ... --- src/libsystemd/sd-json/sd-json.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 4c541275c42c5..fbc2e55d23f22 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -5294,10 +5294,8 @@ _public_ int sd_json_dispatch_full( done++; } else { - if (flags & SD_JSON_ALLOW_EXTENSIONS) { - json_log(value, flags|SD_JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", sd_json_variant_string(key)); + if (flags & SD_JSON_ALLOW_EXTENSIONS) continue; - } json_log(value, flags, 0, "Unexpected object field '%s'.", sd_json_variant_string(key)); if (flags & SD_JSON_PERMISSIVE) From 5948ff5fe20395859cab609e0d3648fc697385e0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 28 Apr 2026 19:47:14 +0200 Subject: [PATCH 1342/2155] libsystemd: Clean up meson.build Merge the two blocks adding tests, since there seems to be no obvious reason to have two separate blocks, as they both contain tests from the same libraries. --- src/libsystemd/meson.build | 61 +++++++++++-------- .../sd-hwdb}/test-sd-hwdb.c | 0 .../sd-id128}/test-id128.c | 0 src/{test => libsystemd/sd-json}/test-json.c | 0 .../sd-path}/test-sd-path.c | 0 .../sd-varlink}/test-varlink-idl.c | 0 .../sd-varlink}/test-varlink.c | 0 src/{test => shared}/test-varlink-idl-util.h | 0 src/test/meson.build | 15 ----- 9 files changed, 34 insertions(+), 42 deletions(-) rename src/{test => libsystemd/sd-hwdb}/test-sd-hwdb.c (100%) rename src/{test => libsystemd/sd-id128}/test-id128.c (100%) rename src/{test => libsystemd/sd-json}/test-json.c (100%) rename src/{test => libsystemd/sd-path}/test-sd-path.c (100%) rename src/{test => libsystemd/sd-varlink}/test-varlink-idl.c (100%) rename src/{test => libsystemd/sd-varlink}/test-varlink.c (100%) rename src/{test => shared}/test-varlink-idl-util.h (100%) diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 08d8d7c5c39e7..2fab54719474c 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -174,29 +174,6 @@ libsystemd_pc = custom_target( ############################################################ -simple_tests += files( - 'sd-journal/test-audit-type.c', - 'sd-journal/test-catalog.c', - 'sd-journal/test-journal-file.c', - 'sd-journal/test-journal-init.c', - 'sd-journal/test-journal-match.c', - 'sd-journal/test-journal-send.c', - 'sd-journal/test-mmap-cache.c', -) - -libsystemd_tests += [ - { - 'sources' : files('sd-journal/test-journal-enum.c'), - 'timeout' : 360, - }, - { - 'sources' : files('sd-event/test-event.c'), - 'timeout' : 120, - } -] - -############################################################ - simple_tests += files( 'sd-bus/test-bus-creds.c', 'sd-bus/test-bus-introspect.c', @@ -204,20 +181,26 @@ simple_tests += files( 'sd-bus/test-bus-vtable.c', 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', + 'sd-hwdb/test-sd-hwdb.c', + 'sd-id128/test-id128.c', + 'sd-journal/test-audit-type.c', + 'sd-journal/test-catalog.c', + 'sd-journal/test-journal-file.c', 'sd-journal/test-journal-flush.c', + 'sd-journal/test-journal-init.c', 'sd-journal/test-journal-interleaving.c', + 'sd-journal/test-journal-match.c', + 'sd-journal/test-journal-send.c', 'sd-journal/test-journal-stream.c', 'sd-journal/test-journal.c', + 'sd-journal/test-mmap-cache.c', 'sd-login/test-login.c', 'sd-login/test-sd-login.c', 'sd-netlink/test-netlink.c', + 'sd-path/test-sd-path.c', ) libsystemd_tests += [ - { - 'sources' : files('sd-device/test-sd-device.c'), - 'dependencies' : [ threads, libmount_cflags ], - }, { 'sources' : files('sd-bus/test-bus-address.c'), 'dependencies' : threads @@ -275,6 +258,18 @@ libsystemd_tests += [ 'dependencies' : threads, 'timeout' : 120, }, + { + 'sources' : files('sd-device/test-sd-device.c'), + 'dependencies' : [threads, libmount_cflags], + }, + { + 'sources' : files('sd-event/test-event.c'), + 'timeout' : 120, + }, + { + 'sources' : files('sd-journal/test-journal-enum.c'), + 'timeout' : 360, + }, { 'sources' : files('sd-journal/test-journal-append.c'), 'type' : 'manual', @@ -287,11 +282,23 @@ libsystemd_tests += [ 'sources' : files('sd-journal/test-journal-verify.c'), 'timeout' : 90, }, + { + 'sources' : files('sd-json/test-json.c'), + 'dependencies' : libm, + }, { 'sources' : files('sd-resolve/test-resolve.c'), 'dependencies' : threads, 'timeout' : 120, }, + { + 'sources' : files('sd-varlink/test-varlink.c'), + 'dependencies' : threads, + }, + { + 'sources' : files('sd-varlink/test-varlink-idl.c'), + 'dependencies' : threads, + }, ] if cxx_cmd != '' diff --git a/src/test/test-sd-hwdb.c b/src/libsystemd/sd-hwdb/test-sd-hwdb.c similarity index 100% rename from src/test/test-sd-hwdb.c rename to src/libsystemd/sd-hwdb/test-sd-hwdb.c diff --git a/src/test/test-id128.c b/src/libsystemd/sd-id128/test-id128.c similarity index 100% rename from src/test/test-id128.c rename to src/libsystemd/sd-id128/test-id128.c diff --git a/src/test/test-json.c b/src/libsystemd/sd-json/test-json.c similarity index 100% rename from src/test/test-json.c rename to src/libsystemd/sd-json/test-json.c diff --git a/src/test/test-sd-path.c b/src/libsystemd/sd-path/test-sd-path.c similarity index 100% rename from src/test/test-sd-path.c rename to src/libsystemd/sd-path/test-sd-path.c diff --git a/src/test/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c similarity index 100% rename from src/test/test-varlink-idl.c rename to src/libsystemd/sd-varlink/test-varlink-idl.c diff --git a/src/test/test-varlink.c b/src/libsystemd/sd-varlink/test-varlink.c similarity index 100% rename from src/test/test-varlink.c rename to src/libsystemd/sd-varlink/test-varlink.c diff --git a/src/test/test-varlink-idl-util.h b/src/shared/test-varlink-idl-util.h similarity index 100% rename from src/test/test-varlink-idl-util.h rename to src/shared/test-varlink-idl-util.h diff --git a/src/test/meson.build b/src/test/meson.build index 6f9a24eb04483..09c367d3074f3 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -120,7 +120,6 @@ simple_tests += files( 'test-hmac.c', 'test-hostname-setup.c', 'test-hostname-util.c', - 'test-id128.c', 'test-image-filter.c', 'test-image-policy.c', 'test-import-util.c', @@ -180,8 +179,6 @@ simple_tests += files( 'test-replace-var.c', 'test-rlimit-util.c', 'test-rm-rf.c', - 'test-sd-hwdb.c', - 'test-sd-path.c', 'test-secure-bits.c', 'test-serialize.c', 'test-set.c', @@ -346,10 +343,6 @@ executables += [ 'sources' : files('test-ipcrm.c'), 'type' : 'unsafe', }, - test_template + { - 'sources' : files('test-json.c'), - 'dependencies' : libm, - }, test_template + { 'sources' : files('test-kexec.c'), 'link_with' : [libshared], @@ -496,14 +489,6 @@ executables += [ 'sources' : files('test-utmp.c'), 'conditions' : ['ENABLE_UTMP'], }, - test_template + { - 'sources' : files('test-varlink.c'), - 'dependencies' : threads, - }, - test_template + { - 'sources' : files('test-varlink-idl.c'), - 'dependencies' : threads, - }, core_test_template + { 'sources' : files('test-varlink-idl-unit.c'), }, From 087fa20166f0becc7b41056d48e1efb76e4c20f3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 08:59:20 +0200 Subject: [PATCH 1343/2155] shared: add io.systemd.StorageProvider Varlink interface Generic Varlink API for services that hand out file descriptors to storage volumes. Three methods: Acquire() returns an fd for a named volume (optionally creating it from a template), ListVolumes() enumerates available volumes, ListTemplates() enumerates supported creation templates. Volume types follow kernel inode-type naming: blk (block device), reg (regular file), dir (directory). Intent is that multiple providers can sit behind AF_UNIX sockets in a well-known directory and be consumed uniformly by nspawn, vmspawn, the service manager (BindVolume=) and similar tools. --- src/libsystemd/sd-varlink/test-varlink-idl.c | 2 + src/shared/meson.build | 1 + .../varlink-io.systemd.StorageProvider.c | 119 ++++++++++++++++++ .../varlink-io.systemd.StorageProvider.h | 6 + 4 files changed, 128 insertions(+) create mode 100644 src/shared/varlink-io.systemd.StorageProvider.c create mode 100644 src/shared/varlink-io.systemd.StorageProvider.h diff --git a/src/libsystemd/sd-varlink/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c index a645d4d9d360c..a5190897023fd 100644 --- a/src/libsystemd/sd-varlink/test-varlink-idl.c +++ b/src/libsystemd/sd-varlink/test-varlink-idl.c @@ -44,6 +44,7 @@ #include "varlink-io.systemd.Resolve.h" #include "varlink-io.systemd.Resolve.Hook.h" #include "varlink-io.systemd.Resolve.Monitor.h" +#include "varlink-io.systemd.StorageProvider.h" #include "varlink-io.systemd.Udev.h" #include "varlink-io.systemd.Unit.h" #include "varlink-io.systemd.UserDatabase.h" @@ -212,6 +213,7 @@ TEST(parse_format) { &vl_interface_io_systemd_Resolve, &vl_interface_io_systemd_Resolve_Hook, &vl_interface_io_systemd_Resolve_Monitor, + &vl_interface_io_systemd_StorageProvider, &vl_interface_io_systemd_Udev, &vl_interface_io_systemd_Unit, &vl_interface_io_systemd_UserDatabase, diff --git a/src/shared/meson.build b/src/shared/meson.build index c28fe040b6b2b..cd34b02f8506d 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -245,6 +245,7 @@ shared_sources = files( 'varlink-io.systemd.Resolve.Hook.c', 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.Shutdown.c', + 'varlink-io.systemd.StorageProvider.c', 'varlink-io.systemd.Udev.c', 'varlink-io.systemd.Unit.c', 'varlink-io.systemd.UserDatabase.c', diff --git a/src/shared/varlink-io.systemd.StorageProvider.c b/src/shared/varlink-io.systemd.StorageProvider.c new file mode 100644 index 0000000000000..cd2a4f3fda0bc --- /dev/null +++ b/src/shared/varlink-io.systemd.StorageProvider.c @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.StorageProvider.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + VolumeType, + SD_VARLINK_FIELD_COMMENT("Block device storage volumes, block-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(blk), + SD_VARLINK_FIELD_COMMENT("Regular file storage volumes, byte-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(reg), + SD_VARLINK_FIELD_COMMENT("POSIX file system storage volumes, path/offset-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(dir)); + +static SD_VARLINK_DEFINE_ENUM_TYPE( + CreateMode, + SD_VARLINK_FIELD_COMMENT("Open if exists already, create if missing"), + SD_VARLINK_DEFINE_ENUM_VALUE(any), + SD_VARLINK_FIELD_COMMENT("Create if missing, fail if exists already"), + SD_VARLINK_DEFINE_ENUM_VALUE(new), + SD_VARLINK_FIELD_COMMENT("Open if exists already, fail if missing"), + SD_VARLINK_DEFINE_ENUM_VALUE(open)); + +static SD_VARLINK_DEFINE_METHOD( + Acquire, + SD_VARLINK_FIELD_COMMENT("The name of the storage volume to acquire"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Determines whether to open or create a storage volume"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(createMode, CreateMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The template to use when creating a new storage volume"), + SD_VARLINK_DEFINE_INPUT(template, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Controls read/write access to the storage volume. If false and the storage volume cannot be opened in writable mode the call will fail. If null, storage volume will be acquired in writable mode if possible, read-only otherwise. If true, storage volume will be opened in read-only mode (and fail if that's not possible)."), + SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Dictates what kind of storage volume to request. Some storage volumes can be acquired either as regular file or as block device. In all other cases if this value doesn't match the volume type, the request will fail."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(requestAs, VolumeType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The size of the storage volume, if one is created. Has no effect if no storage volume is created."), + SD_VARLINK_DEFINE_INPUT(createSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Returns an index into the array of file descriptors associated with this reply. This may be used to get the file descriptor of the volume. The file descriptor must be properly opened, i.e. not an O_PATH file descriptor."), + SD_VARLINK_DEFINE_OUTPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The storage volume type, i.e. ultimately the inode type of the returned file descriptor"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0), + SD_VARLINK_FIELD_COMMENT("Whether storage volume has been opened in read-only mode"), + SD_VARLINK_DEFINE_OUTPUT(readOnly, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Base UID for the returned file descriptor (if directory). If not specified shall default to 0."), + SD_VARLINK_DEFINE_OUTPUT(baseUID, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Base GID for the returned file descriptor (if directory). If not specified shall default to 0."), + SD_VARLINK_DEFINE_OUTPUT(baseGID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListVolumes, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Specifies a shell glob to filter enumeration by"), + SD_VARLINK_DEFINE_INPUT(matchName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The storage volume's primary name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Additional names"), + SD_VARLINK_DEFINE_OUTPUT(aliases, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("The type of the storage volume"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0), + SD_VARLINK_FIELD_COMMENT("Whether the storage volume is read-only."), + SD_VARLINK_DEFINE_OUTPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Size in bytes, if known"), + SD_VARLINK_DEFINE_OUTPUT(sizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Used bytes, if known"), + SD_VARLINK_DEFINE_OUTPUT(usedBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListTemplates, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Specifies a shell glob to filter enumeration by"), + SD_VARLINK_DEFINE_INPUT(matchName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The template's name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The type of the storage volumes defined by this template"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0)); + +static SD_VARLINK_DEFINE_ERROR(NoSuchVolume); +static SD_VARLINK_DEFINE_ERROR(VolumeExists); +static SD_VARLINK_DEFINE_ERROR(NoSuchTemplate); +static SD_VARLINK_DEFINE_ERROR(TypeNotSupported); +static SD_VARLINK_DEFINE_ERROR(WrongType); +static SD_VARLINK_DEFINE_ERROR(CreateNotSupported); +static SD_VARLINK_DEFINE_ERROR(CreateSizeRequired); +static SD_VARLINK_DEFINE_ERROR(ReadOnlyVolume); +static SD_VARLINK_DEFINE_ERROR(BadTemplate); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_StorageProvider, + "io.systemd.StorageProvider", + SD_VARLINK_INTERFACE_COMMENT("Storage Provider API, a generic interface for acquiring access to storage volumes"), + SD_VARLINK_SYMBOL_COMMENT("Encodes three classes of storage volumes. This follows the kernel's nomenclature for inode types, i.e. reg, dir, blk."), + &vl_type_VolumeType, + SD_VARLINK_SYMBOL_COMMENT("Determines whether to open existing or create a new storage volume."), + &vl_type_CreateMode, + SD_VARLINK_SYMBOL_COMMENT("Acquires a file descriptor for a storage volume."), + &vl_method_Acquire, + SD_VARLINK_SYMBOL_COMMENT("Lists available storage volumes."), + &vl_method_ListVolumes, + SD_VARLINK_SYMBOL_COMMENT("Lists available templates."), + &vl_method_ListTemplates, + SD_VARLINK_SYMBOL_COMMENT("No storage volume under the specified name exists."), + &vl_error_NoSuchVolume, + SD_VARLINK_SYMBOL_COMMENT("A storage volume under the specified name already exists."), + &vl_error_VolumeExists, + SD_VARLINK_SYMBOL_COMMENT("No template under the specified name exists."), + &vl_error_NoSuchTemplate, + SD_VARLINK_SYMBOL_COMMENT("The specified volume type is not supported by this backend or system."), + &vl_error_TypeNotSupported, + SD_VARLINK_SYMBOL_COMMENT("The volume's type does not match the requested volume type."), + &vl_error_WrongType, + SD_VARLINK_SYMBOL_COMMENT("This backend does not support storage volume creation of the requested type."), + &vl_error_CreateNotSupported, + SD_VARLINK_SYMBOL_COMMENT("This backend or selected volume type requires a storage volume size to be specified if the storage volume does not exist yet and needs to be created."), + &vl_error_CreateSizeRequired, + SD_VARLINK_SYMBOL_COMMENT("A storage volume was to be acquired in writable mode, but only read-only access is permitted."), + &vl_error_ReadOnlyVolume, + SD_VARLINK_SYMBOL_COMMENT("Template not suitable for this storage volume type."), + &vl_error_BadTemplate); diff --git a/src/shared/varlink-io.systemd.StorageProvider.h b/src/shared/varlink-io.systemd.StorageProvider.h new file mode 100644 index 0000000000000..707d05644f2cf --- /dev/null +++ b/src/shared/varlink-io.systemd.StorageProvider.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_StorageProvider; From 4fd0df2a4b0aeb7aa317666f7e25626b0129c87a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 09:00:06 +0200 Subject: [PATCH 1344/2155] storage: add systemd-storage-block@.service provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First implementation of io.systemd.StorageProvider, exposing all block devices known to udev (disks, partitions, dm nodes, …) as volumes of type "blk". Names are picked from stable /dev/mapper and /dev/disk/by-* symlinks; content-derived identifiers (by-uuid, by-label, …) are intentionally avoided for security. Volume creation is not supported by this backend. Socket-activated via /run/systemd/io.systemd.StorageProvider/block. Also adds shared storage-util.[ch] (VolumeType / CreateMode helpers) that subsequent providers reuse. --- man/rules/meson.build | 4 + man/systemd-storage-block@.service.xml | 97 ++++++ meson.build | 1 + src/storage/io.systemd.storage.policy | 30 ++ src/storage/meson.build | 11 + src/storage/storage-block.c | 439 ++++++++++++++++++++++++ src/storage/storage-util.c | 23 ++ src/storage/storage-util.h | 43 +++ units/meson.build | 7 + units/systemd-storage-block.socket | 24 ++ units/systemd-storage-block@.service.in | 18 + 11 files changed, 697 insertions(+) create mode 100644 man/systemd-storage-block@.service.xml create mode 100644 src/storage/io.systemd.storage.policy create mode 100644 src/storage/meson.build create mode 100644 src/storage/storage-block.c create mode 100644 src/storage/storage-util.c create mode 100644 src/storage/storage-util.h create mode 100644 units/systemd-storage-block.socket create mode 100644 units/systemd-storage-block@.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 4aae561512991..439c33d5abdc1 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1186,6 +1186,10 @@ manpages = [ ['systemd-ssh-issue', '1', [], ''], ['systemd-ssh-proxy', '1', [], ''], ['systemd-stdio-bridge', '1', [], ''], + ['systemd-storage-block@.service', + '8', + ['systemd-storage-block', 'systemd-storage-block.socket'], + ''], ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'], ['systemd-stub', '7', diff --git a/man/systemd-storage-block@.service.xml b/man/systemd-storage-block@.service.xml new file mode 100644 index 0000000000000..ee6022af053bb --- /dev/null +++ b/man/systemd-storage-block@.service.xml @@ -0,0 +1,97 @@ + + + + + + + + systemd-storage-block@.service + systemd + + + + systemd-storage-block@.service + 8 + + + + systemd-storage-block@.service + systemd-storage-block.socket + systemd-storage-block + Storage provider exposing local block devices as storage volumes + + + + systemd-storage-block@.service + systemd-storage-block.socket + + + + Description + + systemd-storage-block@.service is a system service that implements the + io.systemd.StorageProvider Varlink + interface, exposing the system's block devices (such as disks, partitions, and device-mapper + nodes) as storage volumes that may be acquired by other programs as file descriptors. + + The service is socket-activated via systemd-storage-block.socket, which + listens on the AF_UNIX socket /run/systemd/io.systemd.StorageProvider/block. The + socket directory /run/systemd/io.systemd.StorageProvider/ is the well-known location + where storage providers register, see + storagectl1 for an + enumeration tool. + + See also + systemd-storage-fs@.service8 + for a complementary implementation that exposes regular files and directories from a backing file + system. + + + + Volumes + + The volumes exposed via the provider are identified by an absolute path (which must begin with + /dev/), i.e. as a kernel block device node such as /dev/sda or + /dev/disk/by-id/…. Volume names that are not normalized or that do not begin with + /dev/ are not accepted. + + + + Options + + The following options are understood: + + + + + + + + + Files + + + + /run/systemd/io.systemd.StorageProvider/block + + AF_UNIX socket the service listens on. This is the canonical location + for the block storage provider, and is enumerated by + storagectl providers. + + + + + + + + See Also + + systemd1 + storagectl1 + systemd-storage-fs@.service8 + + + + diff --git a/meson.build b/meson.build index 4f1a791bc7651..325b954a78b24 100644 --- a/meson.build +++ b/meson.build @@ -2139,6 +2139,7 @@ subdir('src/socket-activate') subdir('src/socket-proxy') subdir('src/ssh-generator') subdir('src/stdio-bridge') +subdir('src/storage') subdir('src/storagetm') subdir('src/sulogin-shell') subdir('src/sysctl') diff --git a/src/storage/io.systemd.storage.policy b/src/storage/io.systemd.storage.policy new file mode 100644 index 0000000000000..06af278a5a428 --- /dev/null +++ b/src/storage/io.systemd.storage.policy @@ -0,0 +1,30 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Allow access to block storage volumes + Authentication is required for an application to gain access to block storage volume '$(name)'. + + auth_admin + auth_admin + auth_admin_keep + + + diff --git a/src/storage/meson.build b/src/storage/meson.build new file mode 100644 index 0000000000000..714e50ad9a1ca --- /dev/null +++ b/src/storage/meson.build @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-storage-block', + 'sources' : files('storage-block.c', 'storage-util.c'), + }, +] + +install_data('io.systemd.storage.policy', + install_dir : polkitpolicydir) diff --git a/src/storage/storage-block.c b/src/storage/storage-block.c new file mode 100644 index 0000000000000..4c21795c360ad --- /dev/null +++ b/src/storage/storage-block.c @@ -0,0 +1,439 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-device.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "blockdev-list.h" +#include "build.h" +#include "bus-polkit.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "hashmap.h" +#include "help-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "path-util.h" +#include "storage-util.h" +#include "strv.h" +#include "varlink-io.systemd.StorageProvider.h" +#include "varlink-util.h" + +static int block_device_pick_name( + const BlockDevice *d, + const char **ret_name, + char ***ret_aliases) { + + int r; + + assert(d); + assert(d->node); + assert(ret_name); + assert(ret_aliases); + + static const char *const prefixes[] = { + /* The list of preferred prefixes, in order of preference. Note: for security reasons we only + * use identifiers that do not depend on the *contents* of the device, i.e. we restrict + * ourselves to IDs whose fields are either chosen by whoever created the kernel device or are + * hardware properties, but not names generated from superblock metainformation or similar. */ + "/dev/mapper", + "/dev/disk/by-loop-ref", + "/dev/disk/by-id", + "/dev/disk/by-path", + }; + + const char* found[ELEMENTSOF(prefixes)] = {}; + _cleanup_strv_free_ char **aliases = NULL; + size_t best = SIZE_MAX; + STRV_FOREACH(sl, d->symlinks) { + bool matched = false; + for (size_t i = 0; i < ELEMENTSOF(prefixes); i++) { + if (!path_startswith(*sl, prefixes[i])) + continue; + + if (found[i]) { + /* Two symlinks with the same prefix? Then keep the lower one. */ + if (path_compare(*sl, found[i]) > 0) + continue; + + r = strv_extend(&aliases, found[i]); + if (r < 0) + return r; + } + + found[i] = *sl; + if (i < best) + best = i; + matched = true; + } + + if (!matched) { + r = strv_extend(&aliases, *sl); + if (r < 0) + return r; + } + } + + if (best == SIZE_MAX) /* No preferred prefix found, use the kernel device name */ + *ret_name = d->node; + else { + /* We found a preferred prefix, add the kernel device name to the aliases then. */ + r = strv_extend(&aliases, d->node); + if (r < 0) + return r; + + /* If there are any less preferred prefixes also add them to the aliases array */ + for (size_t i = best + 1; i < ELEMENTSOF(prefixes); i++) { + if (!found[i]) + continue; + + r = strv_extend(&aliases, found[i]); + if (r < 0) + return r; + } + + *ret_name = found[best]; + } + + strv_sort(aliases); + *ret_aliases = TAKE_PTR(aliases); + + return 0; +} + +static bool block_device_match(const BlockDevice *d, const char *match) { + assert(d); + assert(d->node); + + if (!match) + return true; + + if (fnmatch(match, d->node, FNM_NOESCAPE) == 0) + return true; + + STRV_FOREACH(sl, d->symlinks) + if (fnmatch(match, *sl, FNM_NOESCAPE) == 0) + return true; + + return false; +} + +static int vl_method_list_volumes( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + BlockDevice *l = NULL; + size_t n = 0; + CLEANUP_ARRAY(l, n, block_device_array_free); + + r = blockdev_list( + BLOCKDEV_LIST_SHOW_SYMLINKS| + BLOCKDEV_LIST_IGNORE_ROOT| + BLOCKDEV_LIST_IGNORE_EMPTY| + BLOCKDEV_LIST_METADATA, + &l, + &n); + if (r < 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchVolume"); + if (r < 0) + return r; + + FOREACH_ARRAY(d, l, n) { + const char *name = NULL; + _cleanup_strv_free_ char **aliases = NULL; + + if (!block_device_match(d, p.match_name)) + continue; + + r = block_device_pick_name(d, &name, &aliases); + if (r < 0) + return r; + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", aliases), + SD_JSON_BUILD_PAIR_STRING("type", "blk"), + SD_JSON_BUILD_PAIR_CONDITION(d->read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(d->read_only)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", d->size, UINT64_MAX)); + if (r < 0) + return r; + } + + return 0; +} + +static int vl_method_list_templates( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + /* This storage provider does not support templates */ + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); +} + +static int device_open_disk_auto_rw(sd_device *d, int *read_only) { + assert(d); + assert(read_only); + + int fd = sd_device_open(d, *read_only > 0 ? O_RDONLY : O_RDWR); + if (fd < 0) { + if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || *read_only >= 0) + return log_device_debug_errno(d, fd, "Failed to open device in %s mode: %m", *read_only > 0 ? "read-only" : "read-write"); + + /* Try again in read-only mode */ + fd = sd_device_open(d, O_RDONLY); + if (fd < 0) + return log_device_debug_errno(d, fd, "Failed to open device in read-only mode, too: %m"); + + *read_only = true; + } else + *read_only = *read_only > 0; + + return fd; +} + +static int vl_method_acquire( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int r; + + assert(link); + + struct { + const char *name; + CreateMode create_mode; + const char *template; + int read_only; + VolumeType request_as; + uint64_t create_size; + } p = { + .create_mode = CREATE_ANY, + .read_only = -1, + .request_as = _VOLUME_TYPE_INVALID, + .create_size = UINT64_MAX, /* never actually used here, just validated; we don't allow creation of block devices here */ + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "createMode", SD_JSON_VARIANT_STRING, json_dispatch_create_mode, voffsetof(p, create_mode), 0 }, + { "template", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, template), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "requestAs", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(p, request_as), 0 }, + { "createSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, create_size), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!storage_volume_name_is_valid(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + if (!path_startswith(p.name, "/dev") || !path_is_normalized(p.name)) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + if (!IN_SET(p.create_mode, CREATE_ANY, CREATE_OPEN)) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + /* off_t is signed, hence refuse overly long requests */ + if (p.create_size != UINT64_MAX && p.create_size > INT64_MAX) + return sd_varlink_error_invalid_parameter_name(link, "createSizeBytes"); + + if (!isempty(p.template)) { + if (!storage_template_name_is_valid(p.template)) + return sd_varlink_error_invalid_parameter_name(link, "template"); + + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); + } + + if (p.request_as >= 0 && p.request_as != VOLUME_BLK) + return sd_varlink_error(link, "io.systemd.StorageProvider.TypeNotSupported", NULL); + + const char *details[] = { + "name", p.name, + NULL + }; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.storage.block.acquire", + details, + polkit_registry); + if (r <= 0) + return r; + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_devname(&d, p.name); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + if (r < 0) + return r; + + if (!device_in_subsystem(d, "block")) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + /* The error returns are sometimes a bit inconclusive (i.e. read-only media might appear as + * inaccessible due to a permission issue), hence let's do an explicit check first, to give good + * answers */ + if (p.read_only <= 0) { + r = device_get_sysattr_bool(d, "ro"); + if (r < 0) + log_device_debug_errno(d, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", p.name); + else if (r > 0) { + if (p.read_only == 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + + p.read_only = true; + } + } + + _cleanup_close_ int fd = device_open_disk_auto_rw(d, &p.read_only); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + if (fd < 0) + return fd; + + assert(p.read_only >= 0); /* flag is now definitely initialized to either true or false, not negative anymore */ + + int idx = sd_varlink_push_fd(link, fd); + if (idx < 0) + return idx; + + TAKE_FD(fd); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", idx), + SD_JSON_BUILD_PAIR_STRING("type", "blk"), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", p.read_only)); +} + +static int vl_server(void) { + int r; + + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| + SD_VARLINK_SERVER_INHERIT_USERDATA, + &polkit_registry); + 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_StorageProvider); + 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.StorageProvider.Acquire", vl_method_acquire, + "io.systemd.StorageProvider.ListVolumes", vl_method_list_volumes, + "io.systemd.StorageProvider.ListTemplates", vl_method_list_templates); + 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) { + int r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Simple block device backed storage provider"); + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-storage-block", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + 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/storage/storage-util.c b/src/storage/storage-util.c new file mode 100644 index 0000000000000..793946c03a63e --- /dev/null +++ b/src/storage/storage-util.c @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "json-util.h" +#include "string-table.h" +#include "storage-util.h" + +static const char *volume_type_table[_VOLUME_TYPE_MAX] = { + [VOLUME_BLK] = "blk", + [VOLUME_REG] = "reg", + [VOLUME_DIR] = "dir", +}; + +static const char *create_mode_table[_CREATE_MODE_MAX] = { + [CREATE_ANY] = "any", + [CREATE_NEW] = "new", + [CREATE_OPEN] = "open", +}; + +DEFINE_STRING_TABLE_LOOKUP(volume_type, VolumeType); +DEFINE_STRING_TABLE_LOOKUP(create_mode, CreateMode); + +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_volume_type, VolumeType, volume_type_from_string); +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_create_mode, CreateMode, create_mode_from_string); diff --git a/src/storage/storage-util.h b/src/storage/storage-util.h new file mode 100644 index 0000000000000..f7a62aeec0835 --- /dev/null +++ b/src/storage/storage-util.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-json.h" + +#include "string-table-fundamental.h" +#include "string-util.h" + +/* This closely follows the kernel's inode type naming, i.e. is supposed to be a subset of what + * inode_type_from_string() parses. */ +typedef enum VolumeType { + VOLUME_BLK, + VOLUME_REG, + VOLUME_DIR, + _VOLUME_TYPE_MAX, + _VOLUME_TYPE_INVALID = -EINVAL, +} VolumeType; + +typedef enum CreateMode { + CREATE_ANY, + CREATE_NEW, + CREATE_OPEN, + _CREATE_MODE_MAX, + _CREATE_MODE_INVALID = -EINVAL, +} CreateMode; + +DECLARE_STRING_TABLE_LOOKUP(volume_type, VolumeType); +DECLARE_STRING_TABLE_LOOKUP(create_mode, CreateMode); + +int json_dispatch_volume_type(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_create_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +static inline bool storage_volume_name_is_valid(const char *n) { + return string_is_safe(n, /* flags= */ 0); +} + +static inline bool storage_template_name_is_valid(const char *n) { + return string_is_safe(n, /* flags= */ 0); +} + +static inline bool storage_provider_name_is_valid(const char *n) { + return string_is_safe(n, STRING_FILENAME); +} diff --git a/units/meson.build b/units/meson.build index 622e1e69cf7c2..3cac3c876ae1c 100644 --- a/units/meson.build +++ b/units/meson.build @@ -804,6 +804,13 @@ units = [ 'conditions' : ['ENABLE_SYSUSERS'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-storage-block.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { + 'file' : 'systemd-storage-block@.service.in', + }, { 'file' : 'systemd-storagetm.service.in', 'conditions' : ['ENABLE_STORAGETM'], diff --git a/units/systemd-storage-block.socket b/units/systemd-storage-block.socket new file mode 100644 index 0000000000000..1d18b481a375a --- /dev/null +++ b/units/systemd-storage-block.socket @@ -0,0 +1,24 @@ +# 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=Simple Block Device Backed Storage Provider +Documentation=man:systemd-storage-block@..service(8) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.StorageProvider/block +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-storage-block@.service.in b/units/systemd-storage-block@.service.in new file mode 100644 index 0000000000000..801551e2ff802 --- /dev/null +++ b/units/systemd-storage-block@.service.in @@ -0,0 +1,18 @@ +# 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=Simple Block Device Backed Storage Provider +Documentation=man:systemd-storage-block@.service(8) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target + +[Service] +ExecStart=-{{LIBEXECDIR}}/systemd-storage-block From 9825195c255e7818627a3d52c2544c9e635e14fd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 09:00:46 +0200 Subject: [PATCH 1345/2155] storage: add systemd-storage-fs@.service provider Second StorageProvider implementation, exposing regular files and directories from a backing filesystem. In system mode the backing directory is /var/lib/storage/, in user mode $XDG_STATE_HOME/storage/; entries with a .volume suffix are exposed, with the inode type determining whether the volume is reported as reg, dir or (via symlinked/bind-mounted device node) blk. Unlike the block provider, this one supports creating volumes on-demand from a small set of built-in templates: sparse-file, allocated-file, directory and subvolume. --- man/rules/meson.build | 4 + man/systemd-storage-fs@.service.xml | 199 ++++++ src/storage/io.systemd.storage.policy | 10 + src/storage/meson.build | 8 +- src/storage/storage-fs.c | 807 ++++++++++++++++++++++ units/meson.build | 7 + units/systemd-storage-fs.socket | 25 + units/systemd-storage-fs@.service.in | 19 + units/user/meson.build | 7 + units/user/systemd-storage-fs.socket | 23 + units/user/systemd-storage-fs@.service.in | 15 + 11 files changed, 1123 insertions(+), 1 deletion(-) create mode 100644 man/systemd-storage-fs@.service.xml create mode 100644 src/storage/storage-fs.c create mode 100644 units/systemd-storage-fs.socket create mode 100644 units/systemd-storage-fs@.service.in create mode 100644 units/user/systemd-storage-fs.socket create mode 100644 units/user/systemd-storage-fs@.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 439c33d5abdc1..7f4fa07f7ba77 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1190,6 +1190,10 @@ manpages = [ '8', ['systemd-storage-block', 'systemd-storage-block.socket'], ''], + ['systemd-storage-fs@.service', + '8', + ['systemd-storage-fs', 'systemd-storage-fs.socket'], + ''], ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'], ['systemd-stub', '7', diff --git a/man/systemd-storage-fs@.service.xml b/man/systemd-storage-fs@.service.xml new file mode 100644 index 0000000000000..4fe0734398c98 --- /dev/null +++ b/man/systemd-storage-fs@.service.xml @@ -0,0 +1,199 @@ + + + + + + + + systemd-storage-fs@.service + systemd + + + + systemd-storage-fs@.service + 8 + + + + systemd-storage-fs@.service + systemd-storage-fs.socket + systemd-storage-fs + Storage provider exposing regular files and directories as storage volumes + + + + systemd-storage-fs@.service + systemd-storage-fs.socket + + + + Description + + systemd-storage-fs@.service is a service that implements the + io.systemd.StorageProvider Varlink + interface, exposing regular files and directories in /var/lib/storage/*.volume (if + used in system mode) or $XDG_STATE_HOME/storage (when used in user mode) as storage + volumes. Acquired volumes are returned to the caller as file descriptors. Unlike + systemd-storage-block@.service8, + this implementation also supports creating new volumes on demand from a small set of built-in + templates. + + The service is socket-activated via systemd-storage-fs.socket. In system mode + it listens on the AF_UNIX socket /run/systemd/io.systemd.StorageProvider/fs, in user + mode on $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/fs. See + storagectl1 for an + enumeration tool. + + See also + systemd-storage-block@.service8 + for a complementary implementation that exposes local block devices as storage volumes. + + + + Volumes + + Volumes are stored below the storage directory: + + + /var/lib/storage/ when run in system mode. + + $XDG_STATE_HOME/storage/ (typically + ~/.local/state/storage/) when run in user mode. + + + Each volume on disk is stored as a directory entry with a .volume suffix in + the storage directory. Entries which are regular files are exposed as volumes of type + reg; entries which are directories are exposed as volumes of type + dir. Moreover, block device nodes may be symlinked (or bind mounted) into the + directory, which are then exposed as volumes of type blk. + + For directory volumes, the root of the file system passed to clients is placed in a subdirectory + root/ of the NAME.volume directory. The former (and all inodes + below it) must be owned by the foreign UID range, the latter by the host's root. + + When acquiring a volume, symlinks are followed. + + An administrator is permitted to freely manipulate the volume hierarchy directly as long as the + rules described above are followed. In particular, it's permitted to copy, mount or symlink arbitrary + external resources (regardless if directory, regular file or block) into the volume directory, so that + they are exposed as additional volumes. + + + + Templates + + The provider supports creating new volumes automatically when they are acquired. The caller may + select a template that determines configuration details of the volume to create. The + following built-in templates are available: + + + + sparse-file + + Creates a volume backed by a sparsely populated regular file. This is the default + template when creating a regular file volume. (Volume type is reg.) + + + + + + allocated-file + + Creates a volume backed by a fully allocated regular file. (Volume type is + reg.) + + + + + + directory + + Creates a volume backed by a regular directory. (Volume type is + dir.) + + + + + + subvolume + + Creates a btrfs subvolume as backing inode (falling back to a regular directory if + the storage directory is not on btrfs). This is the default template when creating a directory + volume. (Volume type is dir.) + + + + + + + + Options + + The following command-line options are understood: + + + + + + Operate in system mode. Volumes are stored below + /var/lib/storage/. This is the default when invoked from + systemd-storage-fs@.service in the system manager. + + + + + + + + Operate in user mode. Volumes are stored below + $XDG_STATE_HOME/storage/. This is the default when invoked from + systemd-storage-fs@.service in the user manager. + + + + + + + + + + + Files + + + + /var/lib/storage/ + $XDG_STATE_HOME/storage/ + + The storage directory used to back the system mode and user mode service + instances respectively. Each volume is stored as an entry with a + .volume suffix below this directory. + + + + + + /run/systemd/io.systemd.StorageProvider/fs + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/fs + + AF_UNIX sockets the service listens on, in system and user mode + respectively. These are the canonical locations for the fs storage + provider, and are enumerated by storagectl providers. + + + + + + + + See Also + + systemd1 + storagectl1 + systemd-storage-block@.service8 + + + + diff --git a/src/storage/io.systemd.storage.policy b/src/storage/io.systemd.storage.policy index 06af278a5a428..7b25553501520 100644 --- a/src/storage/io.systemd.storage.policy +++ b/src/storage/io.systemd.storage.policy @@ -27,4 +27,14 @@ auth_admin_keep + + + Allow access to file system storage volumes + Authentication is required for an application to gain access to file system storage volume '$(name)'. + + auth_admin + auth_admin + auth_admin_keep + + diff --git a/src/storage/meson.build b/src/storage/meson.build index 714e50ad9a1ca..05c5e24ece4ac 100644 --- a/src/storage/meson.build +++ b/src/storage/meson.build @@ -3,7 +3,13 @@ executables += [ libexec_template + { 'name' : 'systemd-storage-block', - 'sources' : files('storage-block.c', 'storage-util.c'), + 'sources' : files('storage-block.c'), + 'extract' : files('storage-util.c') + }, + libexec_template + { + 'name' : 'systemd-storage-fs', + 'sources' : files('storage-fs.c'), + 'objects' : ['systemd-storage-block'], }, ] diff --git a/src/storage/storage-fs.c b/src/storage/storage-fs.c new file mode 100644 index 0000000000000..47ec9829c494d --- /dev/null +++ b/src/storage/storage-fs.c @@ -0,0 +1,807 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-device.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "build.h" +#include "bus-polkit.h" +#include "chase.h" +#include "chattr-util.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "fs-util.h" +#include "hashmap.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "mount-util.h" +#include "options.h" +#include "path-lookup.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "runtime-scope.h" +#include "stat-util.h" +#include "storage-util.h" +#include "string-table.h" +#include "tmpfile-util.h" +#include "uid-classification.h" +#include "varlink-io.systemd.StorageProvider.h" +#include "varlink-util.h" + +static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + +/* For now we maintain a simple, compiled-in list of templates. One of those days we might want to move these + * into configurable drop-in files on disk. */ +typedef enum Template { + TEMPLATE_SPARSE_FILE, + TEMPLATE_ALLOCATED_FILE, + TEMPLATE_DIRECTORY, + TEMPLATE_SUBVOLUME, + _TEMPLATE_MAX, + _TEMPLATE_INVALID = -EINVAL, +} Template; + +static const char *template_table[_TEMPLATE_MAX] = { + [TEMPLATE_SPARSE_FILE] = "sparse-file", + [TEMPLATE_ALLOCATED_FILE] = "allocated-file", + [TEMPLATE_DIRECTORY] = "directory", + [TEMPLATE_SUBVOLUME] = "subvolume", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(template, Template); + +static VolumeType volume_type_from_template(Template t) { + switch (t) { + + case TEMPLATE_SPARSE_FILE: + case TEMPLATE_ALLOCATED_FILE: + return VOLUME_REG; + + case TEMPLATE_DIRECTORY: + case TEMPLATE_SUBVOLUME: + return VOLUME_DIR; + + default: + return _VOLUME_TYPE_INVALID; + } +} + +static int open_storage_dir(void) { + int r; + + _cleanup_free_ char *state_dir = NULL; + r = state_directory_generic(arg_runtime_scope, /* suffix= */ NULL, &state_dir); + if (r < 0) + return log_error_errno(r, "Failed to get state directory path: %m"); + + _cleanup_close_ int state_fd = chase_and_open(state_dir, /* root= */ NULL, CHASE_TRIGGER_AUTOFS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_CREAT|O_DIRECTORY, /* ret_path= */ NULL); + if (state_fd < 0) + return log_error_errno(state_fd, "Failed to open '%s': %m", state_dir); + + /* First we try to open the storage directory. If it exists this will work and we are happy. If we + * get ENOENT we'll try to create it. If that works, great. If we get EEXIST we'll try to reopen it + * again, to deal with other instances of ourselves racing with us. We only do this exactly once + * though, under the assumption that the dir is never removed, only created during runtime. */ + _cleanup_close_ int storage_fd = chase_and_openat(XAT_FDROOT, state_fd, "storage", CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); + if (storage_fd == -ENOENT) { + storage_fd = xopenat_full(state_fd, "storage", O_EXCL|O_CREAT|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, XO_LABEL|XO_SUBVOLUME, 0700); + if (storage_fd == -EEXIST) + storage_fd = chase_and_openat(XAT_FDROOT, state_fd, "storage", CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); + } + if (storage_fd < 0) + return log_error_errno(storage_fd, "Failed to open '%s/storage/': %m", state_dir); + + return TAKE_FD(storage_fd); +} + +static int vl_method_list_volumes( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + _cleanup_close_ int fd = open_storage_dir(); + if (fd < 0) + return fd; + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT, &dentries); + if (r < 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchVolume"); + if (r < 0) + return r; + + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *d = *dp; + + if (!IN_SET(d->d_type, DT_REG, DT_DIR, DT_LNK, DT_BLK, DT_UNKNOWN)) + continue; + + const char *e = endswith(d->d_name, ".volume"); + if (!e) + continue; + + _cleanup_free_ char *n = strndup(d->d_name, e - d->d_name); + if (!n) + return log_oom_debug(); + + if (!storage_volume_name_is_valid(n)) + continue; + + if (p.match_name && fnmatch(p.match_name, n, FNM_NOESCAPE) != 0) + continue; + + _cleanup_close_ int pin_fd = -EBADF; + r = chaseat(XAT_FDROOT, fd, d->d_name, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) { + log_debug_errno(r, "Failed to stat() '%s' in storage directory, ignoring: %m", d->d_name); + continue; + } + + struct stat st; + if (fstat(pin_fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat() '%s' in storage directory: %m", d->d_name); + + uint64_t size = UINT64_MAX, used = UINT64_MAX; + bool ro = false; + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + ro = (st.st_mode & 0222) == 0; + size = st.st_size; + used = (uint64_t) st.st_blocks * UINT64_C(512); + break; + + case S_IFDIR: + r = fd_is_read_only_fs(pin_fd); + if (r < 0) + log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring", d->d_name); + else + ro = r > 0; + break; + + case S_IFBLK: { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + + r = sd_device_new_from_stat_rdev(&dev, &st); + if (r < 0) + log_debug_errno(r, "Failed to acquire device for '%s', ignoring: %m", d->d_name); + else { + r = device_get_sysattr_bool(dev, "ro"); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to get read/only state of '%s', ignoring: %m", d->d_name); + else + ro = r > 0; + + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", d->d_name); + else + /* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */ + assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */ + } + + break; + } + + default: + log_debug("Volume of unexpected inode type, ignoring: %s", d->d_name); + continue; + } + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", n), + SD_JSON_BUILD_PAIR_STRING("type", inode_type_to_string(st.st_mode)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), + SD_JSON_BUILD_PAIR_CONDITION(size != UINT64_MAX, "sizeBytes", SD_JSON_BUILD_UNSIGNED(size)), + SD_JSON_BUILD_PAIR_CONDITION(used != UINT64_MAX, "usedBytes", SD_JSON_BUILD_UNSIGNED(used))); + if (r < 0) + return r; + } + + return 0; +} + +static int vl_method_list_templates( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchTemplate"); + if (r < 0) + return r; + + for (Template t = 0; t < _TEMPLATE_MAX; t++) { + const char *n = template_to_string(t); + + if (p.match_name && fnmatch(p.match_name, n, FNM_NOESCAPE) != 0) + continue; + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", n), + SD_JSON_BUILD_PAIR_STRING("type", volume_type_to_string(volume_type_from_template(t)))); + if (r < 0) + return r; + } + + return 0; +} + +static int create_volume_dir( + int storage_fd, + const char *filename, + Template t) { + + int r; + + assert(storage_fd >= 0); + assert(filename); + + XOpenFlags xopen_flags; + switch (t) { + + case TEMPLATE_DIRECTORY: + xopen_flags = 0; + break; + + case TEMPLATE_SUBVOLUME: + xopen_flags = XO_SUBVOLUME; + break; + + default: + return -ENOMEDIUM; /* Recognizable error for: template doesn't apply here */ + } + + _cleanup_free_ char *tf = NULL; + r = tempfn_random(filename, /* extra= */ NULL, &tf); + if (r < 0) + return r; + + _cleanup_close_ int volume_fd = xopenat_full(storage_fd, tf, O_CREAT|O_EXCL|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW, xopen_flags, 0700); + if (volume_fd < 0) + return volume_fd; + + _cleanup_close_ int root_fd = xopenat_full(volume_fd, "root", O_CREAT|O_EXCL|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW, xopen_flags, 0755); + if (root_fd < 0) { + r = root_fd; + goto fail; + } + + r = RET_NERRNO(fchown(root_fd, FOREIGN_UID_MIN, FOREIGN_UID_MIN)); + if (r < 0) + goto fail; + + r = rename_noreplace(storage_fd, tf, storage_fd, filename); + if (r < 0) + goto fail; + + return TAKE_FD(root_fd); + +fail: + if (root_fd >= 0) { + assert(volume_fd >= 0); + root_fd = safe_close(root_fd); + (void) unlinkat(volume_fd, "root", AT_REMOVEDIR); + } + + if (volume_fd >= 0) { + volume_fd = safe_close(volume_fd); + (void) unlinkat(storage_fd, tf, AT_REMOVEDIR); + } + + return r; +} + +static int create_volume_reg( + int storage_fd, + const char *filename, + Template t, + uint64_t create_size) { + int r; + + assert(storage_fd >= 0); + assert(filename); + + bool sparse; + switch (t) { + + case TEMPLATE_SPARSE_FILE: + sparse = true; + break; + + case TEMPLATE_ALLOCATED_FILE: + sparse = false; + break; + + default: + return -ENOMEDIUM; /* Recognizable error for: template doesn't apply here */ + } + + _cleanup_free_ char *tf = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(storage_fd, filename, O_RDWR|O_CLOEXEC, &tf); + if (fd < 0) + return fd; + + CLEANUP_TMPFILE_AT(storage_fd, tf); + + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r)) + return r; + + if (create_size > 0) { + if (sparse) + r = RET_NERRNO(ftruncate(fd, create_size)); + else + r = RET_NERRNO(fallocate(fd, /* mode= */ 0, /* offset= */ 0, create_size)); + if (r < 0) + return r; + } + + r = RET_NERRNO(fchmod(fd, 0600)); + if (r < 0) + return r; + + r = link_tmpfile_at(fd, storage_fd, tf, filename, /* flags= */ 0); + if (r < 0) + return r; + + tf = mfree(tf); /* disarm clean-up */ + + return TAKE_FD(fd); +} + +static int vl_method_acquire( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int r; + + assert(link); + + struct { + const char *name; + CreateMode create_mode; + const char *template; + int read_only; + VolumeType request_as; + uint64_t create_size; + } p = { + .create_mode = CREATE_ANY, + .read_only = -1, + .request_as = _VOLUME_TYPE_INVALID, + .create_size = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "createMode", SD_JSON_VARIANT_STRING, json_dispatch_create_mode, voffsetof(p, create_mode), 0 }, + { "template", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, template), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "requestAs", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(p, request_as), 0 }, + { "createSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, create_size), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!storage_volume_name_is_valid(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (!IN_SET(p.create_mode, CREATE_ANY, CREATE_OPEN, CREATE_NEW)) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + /* off_t is signed, hence refuse overly long requests */ + if (p.create_size != UINT64_MAX && p.create_size > INT64_MAX) + return sd_varlink_error_invalid_parameter_name(link, "createSizeBytes"); + + Template t = _TEMPLATE_INVALID; + if (!isempty(p.template)) { + if (!storage_template_name_is_valid(p.template)) + return sd_varlink_error_invalid_parameter_name(link, "template"); + + t = template_from_string(p.template); + if (t < 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); + } + + if (p.read_only > 0) { + if (p.create_mode == CREATE_NEW) + return sd_varlink_error_invalid_parameter_name(link, "readOnly"); + + p.create_mode = CREATE_OPEN; + } + + /* Add a suffix so that we are never attempted to open a temporary file assuming it was a valid + * volume. */ + _cleanup_free_ char *filename = strjoin(p.name, ".volume"); + if (!filename) + return log_oom_debug(); + + if (!filename_is_valid(filename)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (arg_runtime_scope != RUNTIME_SCOPE_USER) { + const char *details[] = { + "name", p.name, + NULL + }; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.storage.fs.acquire", + details, + polkit_registry); + if (r <= 0) + return r; + } + + _cleanup_close_ int storage_fd = open_storage_dir(); + if (storage_fd < 0) + return storage_fd; + + _cleanup_close_ int pin_fd = -EBADF, real_fd = -EBADF; + r = chaseat(XAT_FDROOT, storage_fd, filename, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) { + if (r != -ENOENT) + return r; + if (p.create_mode == CREATE_OPEN || p.read_only > 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + /* Doesn't exist yet: create it now */ + + if (p.request_as < 0) /* Make a choice: pick default type */ + p.request_as = t < 0 ? VOLUME_DIR : volume_type_from_template(t); + + /* Try to create the volume */ + switch (p.request_as) { + + case VOLUME_DIR: { + + if (t < 0) /* Make a choice: pick default template */ + t = TEMPLATE_SUBVOLUME; + + real_fd = create_volume_dir(storage_fd, filename, t); + break; + } + + case VOLUME_REG: { + if (p.create_size == UINT64_MAX) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateSizeRequired", NULL); + + if (t < 0) /* Make a choice: pick default template */ + t = TEMPLATE_SPARSE_FILE; + + real_fd = create_volume_reg(storage_fd, filename, t, p.create_size); + break; + } + + case VOLUME_BLK: + /* We don't support creating block devices, we only support if they are symlinked + * into the storage directory. */ + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + default: + assert_not_reached(); + } + + if (real_fd == -ENOMEDIUM) + return sd_varlink_error(link, "io.systemd.StorageProvider.BadTemplate", NULL); + if (real_fd == -EEXIST) { + if (p.create_mode == CREATE_NEW) + return sd_varlink_error(link, "io.systemd.StorageProvider.VolumeExists", NULL); + + /* If we failed to open the volume and reached this point, then the volume already + * exists by now (i.e. we ran into a race). In that case, try to pin it a second time + * (but only once, let's never loop around this). */ + r = chaseat(XAT_FDROOT, storage_fd, filename, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) + return r; + } else if (real_fd < 0) + return real_fd; + + } else if (p.create_mode == CREATE_NEW) + return sd_varlink_error(link, "io.systemd.StorageProvider.VolumeExists", NULL); + + /* At this point, we either already opened the real fd, or we managed to pin it (but not both) */ + assert((real_fd >= 0) != (pin_fd >= 0)); + + /* Let's first settle the volume type */ + struct stat st; + if (fstat(real_fd >= 0 ? real_fd : pin_fd, &st) < 0) + return -errno; + + if (p.request_as == VOLUME_REG) { + /* First, check for the other supported types and generate a nice error */ + if (IN_SET(st.st_mode & S_IFMT, S_IFDIR, S_IFBLK)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + /* Second verify cover all other types */ + r = stat_verify_regular(&st); + if (r < 0) + return r; + } else if (p.request_as == VOLUME_DIR) { + if (IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFBLK)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + r = stat_verify_directory(&st); + if (r < 0) + return r; + } else if (p.request_as == VOLUME_BLK) { + if (IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFDIR)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + r = stat_verify_block(&st); + if (r < 0) + return r; + + } else if (S_ISREG(st.st_mode)) + p.request_as = VOLUME_REG; + else if (S_ISDIR(st.st_mode)) + p.request_as = VOLUME_DIR; + else if (S_ISBLK(st.st_mode)) + p.request_as = VOLUME_BLK; + else + return log_debug_errno(SYNTHETIC_ERRNO(EBADF), "Unexpected inode type, refusing."); + + /* Let's now acquire a real fd for the pinned fd, if we still need to */ + if (real_fd < 0) { + assert(pin_fd >= 0); + + XOpenFlags xopen_flags = + (p.read_only < 0 && !S_ISDIR(st.st_mode) ? XO_AUTO_RW_RO : 0); + int open_flags = + (p.read_only < 0 ? 0 : (p.read_only > 0 || S_ISDIR(st.st_mode) ? O_RDONLY : O_RDWR)); + + const char *subdir = NULL; + if (p.request_as == VOLUME_DIR) { + /* We place the root of the directory tree one level down, to separate ownership of + * the inode: the upper inode is owned by the host, the lower one by the volume. This + * matters so that the host one can be owned by the host's root, and the volume one + * by the foreign UID range. */ + subdir = "root"; + open_flags |= O_DIRECTORY|O_NOFOLLOW; + } + + real_fd = xopenat_full(pin_fd, subdir, open_flags|O_CLOEXEC, xopen_flags, /* mode= */ MODE_INVALID); + if (real_fd < 0) + return log_debug_errno(real_fd, "Failed to reopen volume fd for '%s': %m", filename); + + /* In directory mode we might be looking at a different inode node, refresh the stat data */ + if (p.request_as == VOLUME_DIR && fstat(real_fd, &st) < 0) + return -errno; + } + + assert(real_fd >= 0); + + bool ro; + switch (p.request_as) { + + case VOLUME_REG: + case VOLUME_BLK: { + assert(IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFBLK)); + + int open_flags = fcntl(real_fd, F_GETFL, 0); + if (open_flags < 0) + return -errno; + + ro = (open_flags & O_ACCMODE_STRICT) == O_RDONLY; + break; + } + + case VOLUME_DIR: { + assert(S_ISDIR(st.st_mode)); + + if (!uid_is_foreign(st.st_uid) || + !gid_is_foreign(st.st_gid)) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Storage directory not owned by foreign UID/GID range."); + + /* Let's now generate a new mount for the directory tree, where propagation is disabled, and the + * flags are all set to good defaults */ + _cleanup_close_ int mount_fd = open_tree_attr_with_fallback( + real_fd, + /* path= */ NULL, + OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW, + &(struct mount_attr) { + .attr_set = (p.read_only > 0 ? MOUNT_ATTR_RDONLY : 0), + .attr_clr = MOUNT_ATTR_NOSUID|MOUNT_ATTR_NOEXEC|MOUNT_ATTR_NODEV, + .propagation = MS_PRIVATE, + }); + if (mount_fd < 0) + return log_debug_errno(mount_fd, "Failed to generate per-volume mount: %m"); + + /* Let's turn on propagation again now that it is disconnected, simply because MS_SHARED is + * generally the default for everything we return. */ + + if (mount_setattr(mount_fd, "", AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW, + &(struct mount_attr) { + .propagation = MS_SHARED, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_debug_errno(errno, "Failed to enable propagation on per-volume mount: %m"); + + close_and_replace(real_fd, mount_fd); + + r = fd_is_read_only_fs(real_fd); + if (r < 0) + return r; + + ro = r > 0; + break; + } + + default: + assert_not_reached(); + } + + if (p.read_only == 0 && ro) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + + int idx = sd_varlink_push_fd(link, real_fd); + if (idx < 0) + return idx; + + TAKE_FD(real_fd); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", idx), + SD_JSON_BUILD_PAIR_STRING("type", inode_type_to_string(st.st_mode)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), + SD_JSON_BUILD_PAIR_CONDITION(p.request_as == VOLUME_DIR, "baseUID", SD_JSON_BUILD_INTEGER(FOREIGN_UID_BASE)), + SD_JSON_BUILD_PAIR_CONDITION(p.request_as == VOLUME_DIR, "baseGID", SD_JSON_BUILD_INTEGER(FOREIGN_UID_BASE))); +} + +static int vl_server(void) { + int r; + + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| + SD_VARLINK_SERVER_INHERIT_USERDATA, + &polkit_registry); + 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_StorageProvider); + 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.StorageProvider.Acquire", vl_method_acquire, + "io.systemd.StorageProvider.ListVolumes", vl_method_list_volumes, + "io.systemd.StorageProvider.ListTemplates", vl_method_list_templates); + 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) { + int r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Simple file system backed storage provider"); + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-storage-fs", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("system", NULL, "Operate in system mode"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Operate in user mode"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + } + + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + 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/units/meson.build b/units/meson.build index 3cac3c876ae1c..0f7ce75bd8967 100644 --- a/units/meson.build +++ b/units/meson.build @@ -811,6 +811,13 @@ units = [ { 'file' : 'systemd-storage-block@.service.in', }, + { + 'file' : 'systemd-storage-fs.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { + 'file' : 'systemd-storage-fs@.service.in', + }, { 'file' : 'systemd-storagetm.service.in', 'conditions' : ['ENABLE_STORAGETM'], diff --git a/units/systemd-storage-fs.socket b/units/systemd-storage-fs.socket new file mode 100644 index 0000000000000..c83cf0a11fda8 --- /dev/null +++ b/units/systemd-storage-fs.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=Simple File System Backed Storage Provider +Documentation=man:systemd-storage-fs@.service(8) +DefaultDependencies=no +RequiresMountsFor=/var/lib/storage +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.StorageProvider/fs +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-storage-fs@.service.in b/units/systemd-storage-fs@.service.in new file mode 100644 index 0000000000000..39b6da36ee76b --- /dev/null +++ b/units/systemd-storage-fs@.service.in @@ -0,0 +1,19 @@ +# 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=Simple File System Backed Storage Provider +Documentation=man:systemd-storage-fs@.service(8) +DefaultDependencies=no +RequiresMountsFor=/var/lib/storage +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target + +[Service] +ExecStart=-{{LIBEXECDIR}}/systemd-storage-fs diff --git a/units/user/meson.build b/units/user/meson.build index a9c6d44281c28..39c41a4c1cd8c 100644 --- a/units/user/meson.build +++ b/units/user/meson.build @@ -61,6 +61,13 @@ units = [ 'file' : 'systemd-journalctl.socket', 'symlinks' : ['sockets.target.wants/'], }, + { + 'file' : 'systemd-storage-fs.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { + 'file' : 'systemd-storage-fs@.service.in', + }, { 'file' : 'systemd-tmpfiles-clean.service' }, { 'file' : 'systemd-tmpfiles-clean.timer' }, { 'file' : 'systemd-tmpfiles-setup.service' }, diff --git a/units/user/systemd-storage-fs.socket b/units/user/systemd-storage-fs.socket new file mode 100644 index 0000000000000..fa8018b2e8552 --- /dev/null +++ b/units/user/systemd-storage-fs.socket @@ -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=Simple File System Backed Storage Provider +Documentation=man:systemd-storage-fs.service(8) +Before=sockets.target + +[Socket] +ListenStream=%t/systemd/io.systemd.StorageProvider/fs +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes +MaxConnectionsPerSource=16 + +[Install] +WantedBy=sockets.target diff --git a/units/user/systemd-storage-fs@.service.in b/units/user/systemd-storage-fs@.service.in new file mode 100644 index 0000000000000..95afa9165fa5f --- /dev/null +++ b/units/user/systemd-storage-fs@.service.in @@ -0,0 +1,15 @@ +# 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=Simple File System Backed Storage Provider +Documentation=man:systemd-storage-fs.service(8) + +[Service] +ExecStart=-{{LIBEXECDIR}}/systemd-storage-fs --user From 4002a7e8be0965b3bbc4aa1833298314464778d8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Apr 2026 23:44:04 +0200 Subject: [PATCH 1346/2155] storage: add 'storagectl' command-line tool CLI for inspecting and using storage providers. Scans /run/systemd/io.systemd.StorageProvider/ (or the user-mode equivalent) for AF_UNIX sockets and talks to each one over Varlink. Verbs: "volumes" lists volumes across all providers, "templates" lists supported creation templates, "providers" lists the endpoints themselves. Also installed as a mount.storage helper, so 'mount -t storage PROVIDER:VOLUME /mnt' (or 'mount -t storage.' to put a fresh filesystem on a block volume) acquires the volume and mounts it. Ships with bash/zsh completions and a man page. --- man/rules/meson.build | 1 + man/storagectl.xml | 281 +++++++++++ shell-completion/bash/meson.build | 1 + shell-completion/bash/storagectl | 74 +++ shell-completion/zsh/_storagectl | 35 ++ shell-completion/zsh/meson.build | 1 + src/storage/meson.build | 10 + src/storage/storagectl.c | 812 ++++++++++++++++++++++++++++++ 8 files changed, 1215 insertions(+) create mode 100644 man/storagectl.xml create mode 100644 shell-completion/bash/storagectl create mode 100644 shell-completion/zsh/_storagectl create mode 100644 src/storage/storagectl.c diff --git a/man/rules/meson.build b/man/rules/meson.build index 7f4fa07f7ba77..719838064c02f 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -972,6 +972,7 @@ manpages = [ ['sd_watchdog_enabled', '3', [], ''], ['shutdown', '8', [], ''], ['smbios-type-11', '7', [], ''], + ['storagectl', '1', ['mount.storage'], ''], ['sysctl.d', '5', [], ''], ['sysext.conf', '5', diff --git a/man/storagectl.xml b/man/storagectl.xml new file mode 100644 index 0000000000000..5fddf3ca08db5 --- /dev/null +++ b/man/storagectl.xml @@ -0,0 +1,281 @@ + + + + + + + + storagectl + systemd + + + + storagectl + 1 + + + + storagectl + mount.storage + Enumerate and mount storage volumes provided by storage providers + + + + + storagectl + OPTIONS + COMMAND + NAME + + + + mount + -t + storage + PROVIDER:VOLUME + DIRECTORY + + + + mount + -t + storage.FSTYPE + PROVIDER:VOLUME + DIRECTORY + + + + + Description + + storagectl may be used to inspect storage providers and the storage + volumes they expose. A storage provider is a service implementing the + io.systemd.StorageProvider Varlink + interface, registered as an AF_UNIX socket below the well-known socket directory + /run/systemd/io.systemd.StorageProvider/ (in system mode) or + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/ (in user mode). The two + storage providers shipped with systemd are + systemd-storage-block@.service8, + which exposes the system's block devices, and + systemd-storage-fs@.service8, + which exposes regular files and directories from a backing file system. + + The tool also provides a mount8 helper + for the file system type storage, which permits mounting storage volumes to arbitrary + places. See "Use as a mount helper" below for details. + + + + Commands + + The following commands are understood: + + + + + volumes GLOB + + List storage volumes provided by all storage providers running on the + system (or, with , in the user runtime). The optional + GLOB argument is a shell-style pattern (see + fnmatch3) + that filters the result by volume name. The output is a table containing the providing + service, the volume name, its type (blk, reg or + dir), whether it is read-only, and — if known — its size and the number + of bytes used. + + This is the default command if none is specified. + + + + + + templates GLOB + + List volume templates supported by the running storage providers. Templates + encapsulate a configuration to use when creating volumes on-the-fly, when they are acquired. Template + support is an optional feature for providers, and only applies to providers that allow creation + of volumes on-the-fly. See the respective provider documentation for details, for example + systemd-storage-fs@.service8. The + optional GLOB argument filters by template name. Storage providers that do + not implement template-based volume creation (such as the block-device provider) do not contribute to + this output. + + + + + + providers + + List the storage providers known to the system. This is determined by scanning the + well-known socket directory for AF_UNIX sockets that look like + io.systemd.StorageProvider endpoints. For each provider it is also reported + whether the socket can currently be connected to. + + + + + + + + Options + + The following options are understood: + + + + + + Operate on system-wide storage providers. Sockets are looked for in + /run/systemd/io.systemd.StorageProvider/. This is the default. + + + + + + + + Operate on per-user storage providers. Sockets are looked for in + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/. + + + + + + + + + + + + + + + Use as a mount helper + + The tool provides the /sbin/mount.storage alias, implementing the + mount8 + "external helper" interface, allowing storage volumes to be mounted with the regular + mount command. The volume to mount is encoded as the source of the mount, + in the form + PROVIDER:VOLUME, where + PROVIDER is the name of a storage provider (as listed by + storagectl providers) and VOLUME is the volume + name. Two file system type spellings are recognized: + + + + storage + + Acquires a directory volume and bind-mounts its directory tree onto the + target. + + + + + + storage.FSTYPE + + Acquires a regular file or block device volume and mounts it as a file system of type + FSTYPE (for example storage.ext4, + storage.btrfs, …). + + + + + + The standard mount options are forwarded to + mount. In addition, the following storage.-prefixed + options are interpreted by mount.storage itself and stripped from the + forwarded list: + + + + MODE + + Takes one of any (open if it exists, otherwise create — the + default), open (fail if the volume does not yet exist) or new + (fail if the volume already exists). + + + + + + NAME + + The template to use when creating a new volume, if it is missing and the provider + supports on-the-fly creation of volumes. + + + + + + BYTES + + When creating a new volume on-the-fly, the size in bytes to allocate. Accepts the + usual K/M/G/T suffixes + (base 1024). Required when creating a regular file volume. + + + + + + + + + Examples + + + Enumerate available storage providers, volumes and templates + + $ storagectl providers +$ storagectl volumes +$ storagectl volumes '*foo*' +$ storagectl templates + + + + Mount a directory volume from the file system provider + + # mount -t storage fs:myvol /mnt/myvol + + If the volume myvol does not yet exist, it will be created using + the default subvolume template. + + + + Create and mount an ext4 file system from a regular file. + + # mount -t storage.ext4 fs:scratch /mnt/scratch -o loop + + + + Mount a block device volume read-only + + # mount -t storage.ext4 -o ro block:/dev/disk/by-id/usb-foo /mnt/foo + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + + + See Also + + systemd1 + systemd-storage-block@.service8 + systemd-storage-fs@.service8 + varlinkctl1 + mount8 + + + + diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index 154910979ea56..b0e56608e8f37 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -36,6 +36,7 @@ foreach item : [ ['portablectl', 'ENABLE_PORTABLED'], ['resolvectl', 'ENABLE_RESOLVE'], ['run0', ''], + ['storagectl', ''], ['systemd-analyze', ''], ['systemd-cat', ''], ['systemd-cgls', ''], diff --git a/shell-completion/bash/storagectl b/shell-completion/bash/storagectl new file mode 100644 index 0000000000000..5aefc30ed162d --- /dev/null +++ b/shell-completion/bash/storagectl @@ -0,0 +1,74 @@ +# shellcheck shell=bash +# storagectl(1) 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 +} + +_storagectl() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + + local -A OPTS=( + [STANDALONE]='-h --help --version --no-pager --no-legend --no-ask-password + --system --user' + [ARG]='--json' + ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --json) + comps=$( storagectl --json=help 2>/dev/null ) + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [STANDALONE]='volumes templates providers help' + ) + + 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 + comps=${VERBS[*]} + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + comps='' + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _storagectl storagectl diff --git a/shell-completion/zsh/_storagectl b/shell-completion/zsh/_storagectl new file mode 100644 index 0000000000000..b2fdf595a1076 --- /dev/null +++ b/shell-completion/zsh/_storagectl @@ -0,0 +1,35 @@ +#compdef storagectl +# SPDX-License-Identifier: LGPL-2.1-or-later + +(( $+functions[_storagectl_commands] )) || _storagectl_commands() +{ + local -a _storagectl_cmds + _storagectl_cmds=( + "volumes:List storage volumes" + "templates:List storage volume templates" + "providers:List storage providers" + "help:Prints a short help text and exits" + ) + if (( CURRENT == 1 )); then + _describe -t commands 'storagectl command' _storagectl_cmds + else + local curcontext="$curcontext" + cmd="${${_storagectl_cmds[(r)$words[1]:*]%%:*}}" + if (( $+functions[_storagectl_$cmd] )); then + _storagectl_$cmd + else + _message "no more options" + fi + fi +} + +_arguments \ + '(- *)'{-h,--help}'[Prints a short help text and exits.]' \ + '(- *)--version[Prints a short version string and exits.]' \ + '--no-pager[Do not pipe output into a pager]' \ + '--no-legend[Do not show the headers and footers]' \ + '--no-ask-password[Do not query the user for authentication]' \ + '--json=[Show output as JSON]:mode:(pretty short off help)' \ + '--system[Operate in system mode]' \ + '--user[Operate in user mode]' \ + '*::storagectl command:_storagectl_commands' diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index b1bff151e41a3..6cc8a2d57f83e 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -33,6 +33,7 @@ foreach item : [ ['_sd_machines', 'ENABLE_MACHINED'], ['_sd_outputmodes', ''], ['_sd_unit_files', ''], + ['_storagectl', ''], ['_systemd', ''], ['_systemd-analyze', ''], ['_systemd-delta', ''], diff --git a/src/storage/meson.build b/src/storage/meson.build index 05c5e24ece4ac..21456141dec8c 100644 --- a/src/storage/meson.build +++ b/src/storage/meson.build @@ -11,7 +11,17 @@ executables += [ 'sources' : files('storage-fs.c'), 'objects' : ['systemd-storage-block'], }, + executable_template + { + 'name' : 'storagectl', + 'public' : true, + 'sources' : files('storagectl.c'), + 'objects' : ['systemd-storage-block'], + }, ] +install_symlink('mount.storage', + pointing_to : sbin_to_bin + 'storagectl', + install_dir : sbindir) + install_data('io.systemd.storage.policy', install_dir : polkitpolicydir) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c new file mode 100644 index 0000000000000..a21072e78f204 --- /dev/null +++ b/src/storage/storagectl.c @@ -0,0 +1,812 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ansi-color.h" +#include "argv-util.h" +#include "build.h" +#include "bus-util.h" +#include "errno-list.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "format-table.h" +#include "format-util.h" +#include "help-util.h" +#include "json-util.h" +#include "main-func.h" +#include "mount-util.h" +#include "namespace-util.h" +#include "options.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "polkit-agent.h" +#include "recurse-dir.h" +#include "runtime-scope.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "storage-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "varlink-util.h" +#include "verbs.h" + +static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static bool arg_ask_password = true; +static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + +static int help(void) { + int r; + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Enumerate storage volumes and providers."); + + _cleanup_(table_unrefp) Table *verbs = NULL; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + help_section("Commands:"); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("storagectl", "1"); + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +static const char *ro_color(int ro) { + if (ro > 0) + return ansi_highlight_red(); + if (ro == 0) + return ansi_highlight_green(); + + return NULL; +} + +static int on_list_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void* userdata) { + + Table *t = ASSERT_PTR(userdata); + int r; + + assert(link); + + const char *d = ASSERT_PTR(sd_varlink_get_description(link)); + + if (error_id) { + log_debug("%s: Received error '%s', ignoring.", d, error_id); + return 0; + } + + _cleanup_free_ char *provider = NULL; + r = path_extract_filename(d, &provider); + if (r < 0) + return log_error_errno(r, "Failed to extract provider name from socket path: %m"); + + struct { + const char *name; + const char *type; + int read_only; + uint64_t size_bytes; + uint64_t used_bytes; + } p = { + .read_only = -1, + .size_bytes = UINT64_MAX, + .used_bytes = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, size_bytes), 0 }, + { "usedBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, used_bytes), 0 }, + {} + }; + + r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to decode List() reply: %m"); + + r = table_add_many( + t, + TABLE_STRING, provider, + TABLE_STRING, p.name, + TABLE_STRING, p.type, + TABLE_TRISTATE, p.read_only, + TABLE_SET_COLOR, ro_color(p.read_only)); + if (r < 0) + return table_log_add_error(r); + + if (p.size_bytes == UINT64_MAX) + r = table_add_many(t, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); + else + r = table_add_many(t, TABLE_SIZE, p.size_bytes, TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + + if (p.used_bytes == UINT64_MAX) + r = table_add_many(t, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); + else + r = table_add_many(t, TABLE_SIZE, p.used_bytes, TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +VERB(verb_list_volumes, "volumes", "GLOB", /* min_args= */ VERB_ANY, /* max_args= */ 2, VERB_DEFAULT, "List storage volumes"); +static int verb_list_volumes(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc <= 2); + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "name", "type", "ro", "size", "used"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0, (size_t) 1); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (argc >= 2) { + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("matchName", argv[1])); + if (r < 0) + return log_oom(); + } + + ssize_t n = varlink_execute_directory( + socket_path, + "io.systemd.StorageProvider.ListVolumes", + v, + /* more= */ true, + /* timeout_usec= */ 0, /* 0 means default */ + on_list_reply, + t); + if (n < 0 && n != -ENOENT) + return log_error_errno(n, "Failed to enumerate storage volumes: %m"); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No storage volumes.\n"); + else + printf("\n%zu storage volumes listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +static int on_list_templates_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void* userdata) { + + Table *t = ASSERT_PTR(userdata); + int r; + + assert(link); + + const char *d = ASSERT_PTR(sd_varlink_get_description(link)); + + if (error_id) { + log_debug("%s: Received error '%s', ignoring.", d, error_id); + return 0; + } + + _cleanup_free_ char *provider = NULL; + r = path_extract_filename(d, &provider); + if (r < 0) + return log_error_errno(r, "Failed to extract provider name from socket path: %m"); + + struct { + const char *name; + const char *type; + } p = { + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), 0 }, + {} + }; + + r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to decode ListTemplates() reply: %m"); + + r = table_add_many( + t, + TABLE_STRING, provider, + TABLE_STRING, p.name, + TABLE_STRING, p.type); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +VERB(verb_templates, "templates", "GLOB", /* min_args= */ VERB_ANY, /* max_args= */ 2, /* flags= */ 0, "List storage volume templates"); +static int verb_templates(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc <= 2); + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "name", "type"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0, (size_t) 1); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (argc >= 2) { + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("matchName", argv[1])); + if (r < 0) + return log_oom(); + } + + ssize_t n = varlink_execute_directory( + socket_path, + "io.systemd.StorageProvider.ListTemplates", + v, + /* more= */ true, + /* timeout_usec= */ 0, /* 0 means default */ + on_list_templates_reply, + t); + if (n < 0 && n != -ENOENT) + return log_error_errno(n, "Failed to enumerate storage volume templates: %m"); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No templates.\n"); + else + printf("\n%zu templates listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +VERB_NOARG(verb_providers, "providers", "List storage providers"); +static int verb_providers(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "listening"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_close_ int fd = open(socket_path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open '%s': %m", socket_path); + } else { + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &dentries); + if (r < 0) + return log_error_errno(r, "Failed to enumerate '%s': %m", socket_path); + + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *de = *dp; + + if (de->d_type != DT_SOCK) + continue; + + if (!storage_provider_name_is_valid(de->d_name)) + continue; + + _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (socket_fd < 0) + return log_error_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); + + _cleanup_free_ char *no = NULL; + r = connect_unix_path(socket_fd, fd, de->d_name); + if (r < 0) { + no = strjoin("no (", ERRNO_NAME(r), ")"); + if (!no) + return log_oom(); + } + + r = table_add_many(t, + TABLE_STRING, de->d_name, + TABLE_STRING, no ?: "yes", + TABLE_SET_COLOR, ansi_highlight_green_red(!no)); + if (r < 0) + return table_log_add_error(r); + } + } + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No providers.\n"); + else + printf("\n%zu providers listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***args) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_COMMON_NO_LEGEND: + arg_legend = false; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_LONG("system", NULL, "Operate in system mode"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Operate in user mode"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + } + + *args = option_parser_get_args(&opts); + return 1; +} + +static int run_as_mount_helper(int argc, char *argv[]) { + int c, r; + + /* Implements util-linux "external helper" command line interface, as per mount(8) man page. + * + * Usage: + * + * mount -t storage fs:mydirvolume /some/place # Directory volumes + * mount -t storage.ext4 fs:myblkvolume /some/place # Block volumes + */ + + const char *fstype = NULL, *options = NULL; + bool fake = false; + + while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + switch (c) { + + case 'f': + fake = true; + break; + + case 'o': + options = optarg; + break; + + case 't': + fstype = startswith(optarg, "storage."); + if (fstype) { + /* Paranoia: don't allow "storage.storage.storage.…" chains... */ + if (startswith(fstype, "storage.") || streq(fstype, "storage")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing nested storage volumes."); + } else if (!streq(optarg, "storage")) + log_warning("Unexpected file system type '%s', ignoring.", optarg); + + break; + + case 's': /* sloppy mount options */ + case 'n': /* aka --no-mtab */ + case 'v': /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", c); + break; + + case 'N': /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); + + case '?': + return -EINVAL; + } + } + + if (optind + 2 != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected a storage volume specification and target directory as only arguments."); + + const char *colon = strchr(argv[optind], ':'); + if (!colon) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume specification, refusing: %s", argv[optind]); + + _cleanup_free_ char *provider = strndup(argv[optind], colon - argv[optind]); + if (!provider) + return log_oom(); + if (!storage_provider_name_is_valid(provider)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage provider name: %s", provider); + + _cleanup_free_ char *name = strdup(colon + 1); + if (!name) + return log_oom(); + if (!storage_volume_name_is_valid(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume name: %s", name); + + _cleanup_free_ char *path = NULL; + r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &path); + if (r < 0) + return r; + + _cleanup_free_ char *filtered = NULL, *template = NULL; + CreateMode create_mode = _CREATE_MODE_INVALID; + uint64_t create_size = UINT64_MAX; + int read_only = -1; + for (const char *p = options;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE|EXTRACT_UNESCAPE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract mount option: %m"); + if (r == 0) + break; + + const char *t = startswith(word, "storage."); + if (t) { + const char *v; + if ((v = startswith(t, "create="))) { + create_mode = create_mode_from_string(v); + if (create_mode < 0) + return log_error_errno(create_mode, "Failed to parse storage.create= parameter: %s", v); + } else if ((v = startswith(t, "create-size="))) { + r = parse_size(v, /* base= */ 1024, &create_size); + if (r < 0) + return log_error_errno(r, "Failed to parse storage.create-size= parameter: %s", v); + } else if ((v = startswith(t, "template="))) { + if (!storage_template_name_is_valid(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid template name, refusing: %s", v); + + r = free_and_strdup(&template, v); + if (r < 0) + return log_oom(); + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown mount option '%s', refusing.", word); + } else if (streq(word, "ro")) + read_only = true; + else if (streq(word, "rw")) + read_only = false; + else if (!strextend_with_separator(&filtered, ",", word)) + return log_oom(); + } + + if (fake) + return 0; + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + if (!path_extend(&socket_path, provider)) + return log_oom(); + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, socket_path); + if (r < 0) + return log_error_errno(r, "Failed to connect to '%s': %m", socket_path); + + r = sd_varlink_set_allow_fd_passing_input(link, true); + if (r < 0) + return log_error_errno(r, "Failed to enable file descriptor passing: %m"); + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + sd_json_variant *mreply = NULL; + const char *merror_id = NULL, *vtype = fstype ? "reg" : "dir"; + r = sd_varlink_callbo( + link, + "io.systemd.StorageProvider.Acquire", + &mreply, + &merror_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_CONDITION(create_mode >= 0, "createMode", SD_JSON_BUILD_STRING(create_mode_to_string(create_mode))), + JSON_BUILD_PAIR_STRING_NON_EMPTY("template", template), + SD_JSON_BUILD_PAIR_CONDITION(read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(read_only)), + SD_JSON_BUILD_PAIR_STRING("requestAs", vtype), + SD_JSON_BUILD_PAIR_CONDITION(create_size != UINT64_MAX, "createSizeBytes", SD_JSON_BUILD_UNSIGNED(create_size)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = sd_json_variant_ref(mreply); + if (merror_id) { + /* Copy out the error ID, as the follow-up call will invalidate it */ + _cleanup_free_ char *error_id = strdup(merror_id); + if (!error_id) + return log_oom(); + + /* Hmm, the type might not have been right for the backend or the volume? then try + * again, and switch from "reg" to "blk", maybe it works then. (We keep the original + * reply referenced, since we prefer generating an error for the first error.) */ + if (streq(vtype, "reg") && STR_IN_SET(error_id, + "io.systemd.StorageProvider.TypeNotSupported", + "io.systemd.StorageProvider.WrongType")) { + + sd_json_variant *freply = NULL; + const char *ferror_id = NULL; + r = sd_varlink_callbo( + link, + "io.systemd.StorageProvider.Acquire", + &freply, + &ferror_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_CONDITION(create_mode >= 0, "createMode", SD_JSON_BUILD_STRING(create_mode_to_string(create_mode))), + JSON_BUILD_PAIR_STRING_NON_EMPTY("template", template), + SD_JSON_BUILD_PAIR_CONDITION(read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(read_only)), + SD_JSON_BUILD_PAIR_STRING("requestAs", "blk"), + SD_JSON_BUILD_PAIR_CONDITION(create_size != UINT64_MAX, "createSizeBytes", SD_JSON_BUILD_UNSIGNED(create_size)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); + if (!ferror_id) { + /* The 2nd call worked? then let's forget about the first failure */ + sd_json_variant_unref(reply); + reply = sd_json_variant_ref(freply); + error_id = mfree(error_id); + } + + /* NB: if both fail we show the Varlink error of the first call here, i.e. of the preferred type */ + } + + if (error_id) { + if (streq(error_id, "io.systemd.StorageProvider.NoSuchVolume")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Volume '%s' not known.", name); + if (streq(error_id, "io.systemd.StorageProvider.NoSuchTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Template '%s' not known.", template); + if (streq(error_id, "io.systemd.StorageProvider.VolumeExists")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Volume '%s' exists already.", name); + if (streq(error_id, "io.systemd.StorageProvider.TypeNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support the specified volume type '%s'.", vtype); + if (streq(error_id, "io.systemd.StorageProvider.WrongType")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Volume '%s' is not of type '%s'.", name, vtype); + if (streq(error_id, "io.systemd.StorageProvider.CreateNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support creating volumes."); + if (streq(error_id, "io.systemd.StorageProvider.CreateSizeRequired")) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Storage provider requires a create size to be provided when creating volumes on-the-fly. Use 'storage.create-size=' mount option."); + if (streq(error_id, "io.systemd.StorageProvider.ReadOnlyVolume")) + return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Volume '%s' is read-only.", name); + if (streq(error_id, "io.systemd.StorageProvider.BadTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Template does not apply to this volume type."); + + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); + + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %s", error_id); + } + } + + struct { + unsigned fd_idx; + int read_only; + const char *type; + uid_t base_uid; + gid_t base_gid; + } p = { + .fd_idx = UINT_MAX, + .read_only = -1, + .base_uid = UID_INVALID, + .base_gid = GID_INVALID, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, fd_idx), SD_JSON_MANDATORY }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, + { "baseUID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(p, base_uid), 0 }, + { "baseGID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(p, base_gid), 0 }, + {} + }; + + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to decode Acquire() reply: %m"); + + _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_idx); + if (fd < 0) + return log_error_errno(fd, "Failed to acquire fd from Varlink connection: %m"); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat returned file descriptor: %m"); + + _cleanup_strv_free_ char **cmdline = strv_new("mount", "-c"); + if (!cmdline) + return log_oom(); + + if (fstype) { + if (!STR_IN_SET(p.type, "reg", "blk")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mounting as file system type '%s' requested, but volume is not a block device or regular file.", fstype); + + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "File descriptor for block/regular volume is not a block or regular inode: %m"); + + if (strv_extend_strv(&cmdline, STRV_MAKE("-t", fstype), /* filter_duplicates= */ false) < 0) + return log_oom(); + } else { + if (!streq(p.type, "dir")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mount as directory requested, but volume is not a directory."); + + if (!uid_is_valid(p.base_uid) || !gid_is_valid(p.base_gid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provider did not report base UID/GID, cannot mount."); + + if (p.base_uid > UINT32_MAX - 0x10000U || + p.base_gid > UINT32_MAX - 0x10000U) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Returned base UID/GID out of range."); + + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "File descriptor for directory volume is not a directory inode: %m"); + + if (st.st_uid < p.base_uid || st.st_uid >= p.base_uid + 0x10000 || + st.st_gid < p.base_gid || st.st_gid >= p.base_gid + 0x10000) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File descriptor for directory volume is not owned by base UID/GID range, refusing."); + + /* Now move the mount into our own UID/GID range */ + _cleanup_free_ char *uid_line = asprintf_safe( + UID_FMT " " UID_FMT " " UID_FMT "\n", + p.base_uid, (uid_t) 0, (uid_t) 0x10000); + _cleanup_free_ char *gid_line = asprintf_safe( + GID_FMT " " GID_FMT " " GID_FMT "\n", + p.base_gid, (gid_t) 0, (gid_t) 0x10000); + if (!uid_line || !gid_line) + return log_oom(); + + _cleanup_close_ int userns_fd = userns_acquire(uid_line, gid_line, /* setgroups_deny= */ true); + if (userns_fd < 0) + return log_error_errno(userns_fd, "Failed to acquire new user namespace: %m"); + + _cleanup_close_ int remapped_fd = open_tree_attr_with_fallback( + fd, + /* path= */ NULL, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = userns_fd, + }); + if (remapped_fd < 0) + return log_error_errno(remapped_fd, "Failed to set ID mapping on returned mount: %m"); + + close_and_replace(fd, remapped_fd); + + if (strv_extend(&cmdline, "--bind") < 0) + return log_oom(); + } + + if (p.read_only > 0) + read_only = true; + + if (!strextend_with_separator(&filtered, ",", read_only > 0 ? "ro" : "rw")) + return log_oom(); + + if (strv_extend_strv(&cmdline, STRV_MAKE("-o", filtered), /* filter_duplicates= */ false) < 0) + return log_oom(); + + if (strv_extend_strv(&cmdline, STRV_MAKE(FORMAT_PROC_FD_PATH(fd), path), /* filter_duplicates= */ false) < 0) + return log_oom(); + + r = fd_cloexec(fd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for mount fd: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *q = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); + log_debug("Chain-loading: %s", strna(q)); + } + + /* NB: we do not honour $PATH here, since as plugin to /bin/mount we might be called in a setuid() + * context, and hence don't want to chain to programs potentially under user control. */ + execv("/bin/mount", cmdline); + return log_error_errno(errno, "Failed to execute mount tool: %m"); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + if (invoked_as(argv, "mount.storage")) + return run_as_mount_helper(argc, argv); + + char **args = NULL; + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + return dispatch_verb_with_args(args, /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); From 804bf405d932fd1305b1c8f7a09990fb03cb8fcc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 02:25:07 +0200 Subject: [PATCH 1347/2155] test: add integration test for storagectl and storage providers VM-only test that exercises both shipped providers through storagectl: verifies the well-known sockets exist, lists providers/volumes/ templates, creates and acquires volumes from each template (sparse-file, allocated-file, directory, subvolume), attaches a loop device to cover the block provider, and exercises the mount.storage helper. --- test/units/TEST-87-AUX-UTILS-VM.storagectl.sh | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.storagectl.sh diff --git a/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh b/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh new file mode 100755 index 0000000000000..a11a952a8e8da --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v storagectl >/dev/null; then + echo "storagectl not found, skipping." + exit 77 +fi + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Unset $PAGER so we don't have to use --no-pager everywhere +export PAGER= + +# storagectl runs in a VM-only test +if systemd-detect-virt -cq ; then + echo "can't run in a container, skipping." + exit 77 +fi + +at_exit() { + set +e + + if [[ -n "${MOUNT_DIR:-}" ]] && mountpoint -q "$MOUNT_DIR"; then + umount "$MOUNT_DIR" + fi + if [[ -n "${LOOP:-}" ]]; then + systemd-dissect --detach "$LOOP" + fi + if [[ -n "${WORK_DIR:-}" ]]; then + rm -fr "$WORK_DIR" + fi + rm -fr /var/lib/storage/test-87-storage-*.volume +} +trap at_exit EXIT + +# The storage providers are socket-activated by sockets.target, so the listening +# AF_UNIX sockets should already exist. +test -S /run/systemd/io.systemd.StorageProvider/block +test -S /run/systemd/io.systemd.StorageProvider/fs + +WORK_DIR="$(mktemp -d /tmp/test-storagectl.XXXXXXXXXX)" +MOUNT_DIR="$WORK_DIR/mnt" +mkdir -p "$MOUNT_DIR" + +# --- storagectl basic --- + +storagectl --help +storagectl --version +storagectl help + +# Unknown verb / option +(! storagectl this-verb-does-not-exist) +(! storagectl --no-such-option providers) + +# --- storagectl providers --- + +storagectl providers +storagectl providers --no-legend +storagectl providers --no-pager +storagectl providers --json=pretty | jq . +storagectl providers --json=short | jq . + +providers_output="$(storagectl providers --no-legend)" +assert_in 'block' "$providers_output" +assert_in 'fs' "$providers_output" +assert_in 'yes' "$providers_output" + +# --- storagectl volumes --- + +# 'volumes' is the default verb +storagectl +storagectl volumes +storagectl volumes --no-legend +storagectl volumes --no-pager +storagectl volumes --json=pretty | jq . +storagectl volumes --json=short | jq . + +# Glob filter that matches nothing should not error +storagectl volumes 'no-such-volume-*' + +# --- storagectl templates --- + +storagectl templates +storagectl templates --no-legend --no-pager +storagectl templates --json=pretty | jq . +storagectl templates --json=short | jq --seq . + +templates_output="$(storagectl templates --no-legend)" +assert_in 'sparse-file' "$templates_output" +assert_in 'allocated-file' "$templates_output" +assert_in 'directory' "$templates_output" +assert_in 'subvolume' "$templates_output" + +# Glob filter +storagectl templates 'sparse-*' --no-legend | grep sparse-file >/dev/null +(! storagectl templates 'sparse-*' --no-legend | grep allocated-file >/dev/null) +storagectl templates 'no-such-template-*' + +# --- direct varlink calls --- + +varlinkctl introspect /run/systemd/io.systemd.StorageProvider/block io.systemd.StorageProvider +varlinkctl introspect /run/systemd/io.systemd.StorageProvider/fs io.systemd.StorageProvider + +# Block provider does not expose templates +varlinkctl call --more /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.ListTemplates '{}' \ + --graceful=io.systemd.StorageProvider.NoSuchTemplate + +# fs provider lists the four built-in templates +varlinkctl call --more --json=short /run/systemd/io.systemd.StorageProvider/fs \ + io.systemd.StorageProvider.ListTemplates '{}' | grep '"name":"sparse-file"' >/dev/null + +# Block provider rejects names not under /dev/ +varlinkctl call /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.Acquire '{"name":"/tmp/no-such-dev"}' \ + --graceful=io.systemd.StorageProvider.NoSuchVolume + +# fs provider rejects bad volume names (contain '/' → not a valid filename) +varlinkctl call /run/systemd/io.systemd.StorageProvider/fs \ + io.systemd.StorageProvider.Acquire '{"name":"bad/name"}' \ + --graceful=org.varlink.service.InvalidParameter + +# --- mount.storage: regular file via fs provider --- + +TESTVOL_REG="test-87-storage-reg-$RANDOM" +truncate -s 32M "/var/lib/storage/$TESTVOL_REG.volume" +mkfs.ext4 "/var/lib/storage/$TESTVOL_REG.volume" +mount -t storage.ext4 "fs:$TESTVOL_REG" "$MOUNT_DIR" +mountpoint -q "$MOUNT_DIR" +echo "hello reg" >"$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Volume now appears in 'storagectl volumes' +volumes_after_create="$(storagectl volumes "$TESTVOL_REG" --no-legend)" +assert_in "$TESTVOL_REG" "$volumes_after_create" +assert_in 'reg' "$volumes_after_create" + +# Re-mount existing (default storage.create=any) +mount -t storage.ext4 "fs:$TESTVOL_REG" "$MOUNT_DIR" +test -f "$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# storage.create=open succeeds for existing volume +mount -t storage.ext4 -o "storage.create=open" "fs:$TESTVOL_REG" "$MOUNT_DIR" +umount "$MOUNT_DIR" + +# storage.create=new on existing volume must fail +(! mount -t storage.ext4 -o "storage.create=new,storage.create-size=16M" "fs:$TESTVOL_REG" "$MOUNT_DIR") + +# Read-only mount +mount -t storage.ext4 -o ro "fs:$TESTVOL_REG" "$MOUNT_DIR" +findmnt -n -o options "$MOUNT_DIR" | grep -E '(^|,)ro(,|$)' >/dev/null +(! touch "$MOUNT_DIR/readonly-test") +umount "$MOUNT_DIR" + +rm -f "/var/lib/storage/$TESTVOL_REG.volume" + +# storage.create=open on missing volume must fail +(! mount -t storage.ext4 -o "storage.create=open" "fs:test-87-storage-missing-$RANDOM" "$MOUNT_DIR") + +# --- mount.storage: directory volume via fs provider (requires idmapped mounts) --- + +TESTVOL_DIR="test-87-storage-dir-$RANDOM" +if mount -t storage "fs:$TESTVOL_DIR" "$MOUNT_DIR"; then + mountpoint -q "$MOUNT_DIR" + test -d "/var/lib/storage/$TESTVOL_DIR.volume/root" + echo "dir test" >"$MOUNT_DIR/hello" + test -f "/var/lib/storage/$TESTVOL_DIR.volume/root/hello" + umount "$MOUNT_DIR" + rm -fr "/var/lib/storage/$TESTVOL_DIR.volume" +else + echo "Directory volume mounting failed (idmapped mounts unsupported?), skipping." + rm -fr "/var/lib/storage/$TESTVOL_DIR.volume" +fi + +# --- mount.storage: block device via block provider --- + +truncate -s 32M "$WORK_DIR/block.img" +mkfs.ext4 -L sd-storage-blk "$WORK_DIR/block.img" +LOOP="$(systemd-dissect --attach --loop-ref=test-storagectl "$WORK_DIR/block.img")" + +mount -t storage.ext4 "block:$LOOP" "$MOUNT_DIR" +mountpoint -q "$MOUNT_DIR" +echo "hello blk" >"$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Read-only mount of the block volume +mount -t storage.ext4 -o ro "block:$LOOP" "$MOUNT_DIR" +findmnt -n -o options "$MOUNT_DIR" | grep -E '(^|,)ro(,|$)' >/dev/null +test -f "$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Block volume is enumerable; matchName globs over device node and aliases +varlinkctl call --more --json=short /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.ListVolumes "{\"matchName\":\"$LOOP\"}" | + grep '"type":"blk"' >/dev/null + +systemd-dissect --detach "$LOOP" +unset LOOP + +# --- error cases --- + +# Bad provider name (no such socket) +(! mount -t storage.ext4 "no-such-provider:foo" "$MOUNT_DIR") +# Bad volume specification (no colon) +(! mount -t storage.ext4 "no-colon-here" "$MOUNT_DIR") +# Refuse nested storage volumes (FS type "storage.storage") +(! mount -t storage.storage "fs:something" "$MOUNT_DIR") From eccfd2c97b66694ee1b7c18f47837f1dc17ea839 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 12:16:24 +0200 Subject: [PATCH 1348/2155] TODO: track StorageProvider follow-ups, sketch a NetworkProvider sibling Records the still-missing StorageProvider integrations (nspawn, vmspawn, service-manager BindVolume=) and replaces the now-obsolete generic "storage API via varlink" entry with a NetworkProvider proposal modelled on it. --- TODO.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index e51f6734f065e..304f6c3a5ecb8 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,11 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- StorageProvider interface + storagectl + - hook-up in systemd-nspawn + - hook-up in systemd-vmspawn + - hook-up in service manager (BindVolume=) + - 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 @@ -2545,8 +2550,9 @@ SPDX-License-Identifier: LGPL-2.1-or-later - 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: add storage API via varlink, where everyone can drop a socket in a - dir, similar, do the same thing for networking +- add a networking provider API, inspired by the StorageProvider. Make networkd + a provider that exposes interfaces for adding tap, tun, veth via the api, + base this on .netdev logic somehow. - $SYSTEMD_EXECPID that the service manager sets should be augmented with $SYSTEMD_EXECPIDFD (and similar for From 0d71d58da084a5548bcb762aa4b0fad9dc49fac9 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 29 Apr 2026 11:50:01 +0200 Subject: [PATCH 1349/2155] sd-bus: store the strv size when extending it So strv_push_with_size() doesn't have to recalculate the size every time. --- src/libsystemd/sd-bus/bus-message.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 94be969f7f420..017ffb7a6127a 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -4331,6 +4331,7 @@ int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) { char type; const char *contents, *s; + size_t n; int r; assert(m); @@ -4347,9 +4348,10 @@ _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) { if (r <= 0) return r; + n = strv_length(*l); /* sd_bus_message_read_basic() does content validation for us. */ while ((r = sd_bus_message_read_basic(m, *contents, &s)) > 0) { - r = strv_extend(l, s); + r = strv_extend_with_size(l, &n, s); if (r < 0) return r; } From 51a88ac72330f20a030b8938b6bfce2b2215d8a0 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 29 Apr 2026 12:02:32 +0200 Subject: [PATCH 1350/2155] core: limit the number of units that can be requested over ListUnitsByNames --- src/core/dbus-manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 1bc73e7b434c9..6579708df0117 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -971,6 +971,10 @@ static int method_list_units_by_names(sd_bus_message *message, void *userdata, s if (r < 0) return r; + if (strv_length(units) > MAX(hashmap_size(m->units), (unsigned) MANAGER_MAX_NAMES / 2)) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many unit names requested."); + r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; From c3ace5621b0dad786fd9675914ba0f60ed69373a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 12:01:59 +0200 Subject: [PATCH 1351/2155] cpio: move TPM PCR info into CpioTarget The PR to measure into is closely associated with where we place a resource in the initrd cpios. Hence, let's also track it in CpioTarget, thus simplifying our function parameter lists that way. No change in behaviour. --- src/boot/cpio.c | 31 +++++++++++++++++++++++-------- src/boot/cpio.h | 3 +-- src/boot/stub.c | 8 -------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 81792b00a89f4..31638b1c8fc22 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -5,6 +5,7 @@ #include "iovec-util-fundamental.h" #include "measure.h" #include "string-util-fundamental.h" +#include "tpm2-pcr.h" #include "util.h" static char *write_cpio_word(char *p, uint32_t v) { @@ -306,7 +307,6 @@ EFI_STATUS pack_cpio( const char16_t *match_suffix, const char16_t *exclude_suffix, const CpioTarget *target, - uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured) { @@ -425,12 +425,16 @@ EFI_STATUS pack_cpio( return log_error_status(err, "Failed to pack cpio trailer: %m"); err = tpm_log_ipl_event( - tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); + target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(buffer), + buffer_size, + tpm_description, + ret_measured); if (err != EFI_SUCCESS) return log_error_status( err, - "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m", - tpm_pcr, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + target->tpm_pcr, tpm_description); *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); @@ -450,7 +454,6 @@ EFI_STATUS pack_cpio_literal( size_t data_size, const CpioTarget *target, const char16_t *target_filename, - uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured) { @@ -486,12 +489,16 @@ EFI_STATUS pack_cpio_literal( return log_error_status(err, "Failed to pack cpio trailer: %m"); err = tpm_log_ipl_event( - tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); + target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(buffer), + buffer_size, + tpm_description, + ret_measured); if (err != EFI_SUCCESS) return log_error_status( err, - "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m", - tpm_pcr, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + target->tpm_pcr, tpm_description); *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); @@ -506,46 +513,54 @@ const CpioTarget cpio_target_credentials = { .directory = ".extra/credentials", .dir_mode = 0500, .access_mode = 0400, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, }; const CpioTarget cpio_target_global_credentials = { .directory = ".extra/global_credentials", .dir_mode = 0500, .access_mode = 0400, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, }; const CpioTarget cpio_target_sysext = { .directory = ".extra/sysext", .dir_mode = 0555, .access_mode = 0444, + .tpm_pcr = TPM2_PCR_SYSEXTS, }; const CpioTarget cpio_target_global_sysext = { .directory = ".extra/global_sysext", .dir_mode = 0555, .access_mode = 0444, + .tpm_pcr = TPM2_PCR_SYSEXTS, }; const CpioTarget cpio_target_confext = { .directory = ".extra/confext", .dir_mode = 0555, .access_mode = 0444, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, }; const CpioTarget cpio_target_global_confext = { .directory = ".extra/global_confext", .dir_mode = 0555, .access_mode = 0444, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, }; const CpioTarget cpio_target_meta = { .directory = ".extra", .dir_mode = 0555, .access_mode = 0444, + .tpm_pcr = UINT32_MAX, }; const CpioTarget cpio_target_meta_secret = { .directory = ".extra", .dir_mode = 0555, .access_mode = 0400, + .tpm_pcr = UINT32_MAX, }; diff --git a/src/boot/cpio.h b/src/boot/cpio.h index 3c311bc714d28..3aa525779344f 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -8,6 +8,7 @@ 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 */ + uint32_t tpm_pcr; /* Where to measure this data into */ } CpioTarget; EFI_STATUS pack_cpio_one( @@ -35,7 +36,6 @@ EFI_STATUS pack_cpio( const char16_t *match_suffix, const char16_t *exclude_suffix, const CpioTarget *target, - uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); @@ -45,7 +45,6 @@ EFI_STATUS pack_cpio_literal( size_t data_size, const CpioTarget *target, const char16_t *target_filename, - uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); diff --git a/src/boot/stub.c b/src/boot/stub.c index 8632a603a21de..52927e91ff077 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -819,7 +819,6 @@ static void generate_sidecar_initrds( u".cred", /* exclude_suffix= */ NULL, &cpio_target_credentials, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", initrds + INITRD_CREDENTIAL, &m) == EFI_SUCCESS) @@ -830,7 +829,6 @@ static void generate_sidecar_initrds( u".cred", /* exclude_suffix= */ NULL, &cpio_target_global_credentials, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", initrds + INITRD_GLOBAL_CREDENTIAL, &m) == EFI_SUCCESS) @@ -841,7 +839,6 @@ static void generate_sidecar_initrds( 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 */ &cpio_target_sysext, - /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", initrds + INITRD_SYSEXT, &m) == EFI_SUCCESS) @@ -852,7 +849,6 @@ static void generate_sidecar_initrds( u".raw", /* as above */ u".confext.raw", &cpio_target_global_sysext, - /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"Global system extension initrd", initrds + INITRD_GLOBAL_SYSEXT, &m) == EFI_SUCCESS) @@ -863,7 +859,6 @@ static void generate_sidecar_initrds( u".confext.raw", /* exclude_suffix= */ NULL, &cpio_target_confext, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", initrds + INITRD_CONFEXT, &m) == EFI_SUCCESS) @@ -874,7 +869,6 @@ static void generate_sidecar_initrds( u".confext.raw", /* exclude_suffix= */ NULL, &cpio_target_global_confext, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global configuration extension initrd", initrds + INITRD_GLOBAL_CONFEXT, &m) == EFI_SUCCESS) @@ -926,7 +920,6 @@ static void generate_embedded_initrds( sections[t->section].memory_size, &cpio_target_meta, t->filename, - /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + t->initrd_index, /* ret_measured= */ NULL); @@ -948,7 +941,6 @@ static void generate_boot_secret_initrd( BOOT_SECRET_SIZE, &cpio_target_meta_secret, u"boot-secret", - /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + INITRD_BOOT_SECRET, /* ret_measured= */ NULL); From feba5dcc5fdac6886a5c7e250cfd980e669ab4d7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 17:46:15 +0100 Subject: [PATCH 1352/2155] boot: parse 'extra' type 1 stanza too This loads the new 'extra' stanza, but doesn't actually do anything with it yet. That's added in a later commit. Replaces: #39286 Implements: https://github.com/uapi-group/specifications/pull/212 --- src/boot/boot.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index df5ce31fa3a3f..6ee2aded0895f 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -120,6 +120,7 @@ typedef struct BootEntry { char16_t *options; bool options_implied; /* If true, these options are implied if we invoke the PE binary without any parameters (as in: UKI). If false we must specify these options explicitly. */ char16_t **initrd; + char16_t **extras; char16_t key; EFI_STATUS (*call)(const struct BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image); int tries_done; @@ -424,6 +425,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" url: %ls\n", entry->url); STRV_FOREACH(initrd, entry->initrd) printf(" initrd: %ls\n", *initrd); + STRV_FOREACH(extra, entry->extras) + printf(" extra: %ls\n", *extra); if (entry->devicetree) printf(" devicetree: %ls\n", entry->devicetree); if (entry->options) @@ -1047,6 +1050,7 @@ static BootEntry* boot_entry_free(BootEntry *entry) { free(entry->devicetree); free(entry->options); strv_free(entry->initrd); + strv_free(entry->extras); free(entry->directory); free(entry->current_name); free(entry->next_name); @@ -1363,7 +1367,7 @@ static void boot_entry_add_type1( _cleanup_(boot_entry_freep) BootEntry *entry = NULL; char *line; - size_t pos = 0, n_initrd = 0; + size_t pos = 0, n_initrd = 0, n_extras = 0; char *key, *value; EFI_STATUS err; @@ -1492,6 +1496,14 @@ static void boot_entry_add_type1( entry->initrd[n_initrd++] = xstr8_to_path(value); entry->initrd[n_initrd] = NULL; + } else if (streq8(key, "extra")) { + entry->extras = xrealloc( + entry->extras, + n_extras == 0 ? 0 : (n_extras + 1) * sizeof(uint16_t *), + (n_extras + 2) * sizeof(uint16_t *)); + entry->extras[n_extras++] = xstr8_to_path(value); + entry->extras[n_extras] = NULL; + } else if (streq8(key, "options")) { _cleanup_free_ char16_t *new = NULL; From d5572aca2c38aa7f573e6f36e28dcf82860af8b5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:15:38 +0100 Subject: [PATCH 1353/2155] boot: load extra files for UKIs into memory and register as initrds This generates on-the-fly cpio initrds from 'extra' resources declared in Type #1 entries and installs them via the Linux initrd protocol so that they get passed to the Linux kernel. Replaces: #39286 --- src/boot/boot.c | 210 +++++++++++++++++++++++++++++++++++++++++-- src/boot/meson.build | 2 +- 2 files changed, 204 insertions(+), 8 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 6ee2aded0895f..fff35de864b8c 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -3,6 +3,7 @@ #include "bcd.h" #include "bootspec-fundamental.h" #include "console.h" +#include "cpio.h" #include "device-path-util.h" #include "devicetree.h" #include "drivers.h" @@ -38,6 +39,9 @@ #include "version.h" #include "vmm.h" +/* Safety margin, refuse larger extra files (this is not load bearing, only a safety net for robustness reasons). */ +#define EXTRA_SIZE_MAX (1024U * 1024U * 1536U) + /* Magic string for recognizing our own binaries */ #define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####" DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC); @@ -2581,7 +2585,9 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_pages); assert(ret_initrd_size); - if (entry->type != LOADER_LINUX || strv_isempty(entry->initrd)) { + assert(entry->type == LOADER_LINUX); + + if (strv_isempty(entry->initrd)) { *ret_options = NULL; *ret_initrd_pages = (Pages) {}; *ret_initrd_size = 0; @@ -2685,6 +2691,174 @@ static EFI_STATUS initrd_prepare( return EFI_SUCCESS; } +static EFI_STATUS load_extras( + EFI_FILE *root, + const BootEntry *entry, + Pages *ret_initrd_pages, + size_t *ret_initrd_size) { + + EFI_STATUS err; + + assert(root); + assert(entry); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + assert(IN_SET(entry->type, LOADER_UKI, LOADER_UKI_URL)); + + _cleanup_(iovec_done) struct iovec previous_initrd = {}, confext_initrd = {}, sysext_initrd = {}, credential_initrd = {}; + + const struct ExtraResourceInfo { + const char16_t *suffix; + const CpioTarget *target; + struct iovec *iovec; + const char16_t *tpm_description; + } table[] = { + { u".cred", &cpio_target_credentials, &credential_initrd, u"Entry credentials initrd" }, + { u".sysext.raw", &cpio_target_sysext, &sysext_initrd, u"Entry system extension initrd" }, + { u".confext.raw", &cpio_target_confext, &confext_initrd, u"Entry configuration extension initrd" }, + }; + + if (strv_isempty(entry->extras)) + goto nothing; + + uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ + unsigned n = 0; + + STRV_FOREACH(i, entry->extras) { + _cleanup_file_close_ EFI_FILE *handle = NULL; + err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, /* Attributes= */ 0); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to open extra file '%ls', ignoring: %m", *i); + continue; + } + + _cleanup_free_ EFI_FILE_INFO *info = NULL; + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to get information about file '%ls', ignoring: %m", *i); + continue; + } + + if (FLAGS_SET(info->Attribute, EFI_FILE_DIRECTORY)) { + log_warning("Extra file '%ls' is a directory, ignoring.", *i); + continue; + } + + if (info->FileSize == 0) { + log_warning("Extra file '%ls' is empty, ignoring.", *i); + continue; + } + if (info->FileSize > EXTRA_SIZE_MAX) { + log_warning("Extra file '%ls' is larger than allowed extra file size, ignoring.", *i); + continue; + } + + if (!is_ascii(info->FileName)) { + log_warning("Extra file name '%ls' is not valid ASCII, ignoring.", *i); + continue; + } + if (strlen16(info->FileName) > 255) { /* Max filename size on Linux */ + log_warning("Filename '%ls' too long, ignoring.", *i); + continue; + } + + const struct ExtraResourceInfo *x = NULL; + FOREACH_ELEMENT(j, table) { + if (endswith_no_case(info->FileName, j->suffix)) { + x = j; + break; + } + } + if (!x) { + log_warning("Unrecognized type of extra file '%ls', ignoring.", info->FileName); + continue; + } + + _cleanup_free_ char *content = NULL; + size_t contentsize = 0; /* avoid false maybe-uninitialized warning */ + err = file_handle_read(handle, /* offset= */ 0, info->FileSize, &content, &contentsize); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to read '%ls', ignoring: %m", *i); + continue; + } + + /* 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. Note that we potentially do redundant work here: a prior iteration might already + * have created the prefix for us, but to simplify this we regenerate it anyway. It's very + * little data, and simplifies the implementation here a lot. */ + err = pack_cpio_prefix(x->target, &inode, &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio prefix '%s': %m", x->target->directory); + + err = pack_cpio_one( + info->FileName, + content, contentsize, + x->target, + &inode, + &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio file '%ls': %m", info->FileName); + + n++; + } + + if (n == 0) /* Nothing actually loaded */ + goto nothing; + + FOREACH_ELEMENT(x, table) { + if (x->iovec->iov_len <= 0) + continue; + + err = pack_cpio_trailer(&x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio trailer: %m"); + + err = tpm_log_ipl_event( + x->target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(x->iovec->iov_base), + x->iovec->iov_len, + x->tpm_description, + /* ret_measured= */ NULL); + if (err != EFI_SUCCESS) + return log_error_status( + err, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + x->target->tpm_pcr, + x->tpm_description); + } + + /* Be nice: pick up any previously registered initrds and prepend them to what we are generating here */ + err = initrd_read_previous(&previous_initrd); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd installed."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously installed initrd (%zu bytes).", previous_initrd.iov_len); + + err = combine_initrds( + (const struct iovec[]) { + previous_initrd, + credential_initrd, + sysext_initrd, + confext_initrd, + }, + /* n_initrds= */ 4, + ret_initrd_pages, + ret_initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to combine previous with extra initrds: %m"); + + return EFI_SUCCESS; + +nothing: + *ret_initrd_pages = (Pages) {}; + *ret_initrd_size = 0; + return EFI_SUCCESS; +} + static EFI_STATUS expand_path( EFI_HANDLE parent_image, EFI_DEVICE_PATH *path, @@ -2833,15 +3007,11 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading EFI binary %ls: %m", entry->loader); } - _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; _cleanup_free_ char16_t *options_initrd = NULL; - _cleanup_pages_ Pages initrd_pages = {}; + _cleanup_pages_ Pages initrd_pages = {}; /* Note: please keep order intact: these pages should be released after the initrd handle is released */ + _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; size_t initrd_size = 0; if (image_root) { - err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); - if (err != EFI_SUCCESS) - return log_error_status(err, "Error preparing initrd: %m"); - /* DTBs are loaded by the kernel before ExitBootServices(), and they can be used to map and * assign arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here * is unverified. */ @@ -2851,9 +3021,35 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading %ls: %m", entry->devicetree); } + switch (entry->type) { + + case LOADER_LINUX: + /* For traditional Linux we follow 'initrd' links, because that's how things worked in the good old days */ + err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error preparing initrd: %m"); + + break; + + case LOADER_UKI: + case LOADER_UKI_URL: + /* For modern UKIs we'll not bother with 'initrd', but we'll instead support 'extra' + * for loading credentials, sysext and confext. */ + + err = load_extras(image_root, entry, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return err; /* load_extras() logs on its own */ + break; + + default: + ; + } + err = initrd_register(&IOVEC_MAKE(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size), &initrd_handle); if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); + + /* NB: the initrd pages remain in our possession, we will free them if executing the image fails below */ } EFI_LOADED_IMAGE_PROTOCOL *loaded_image; diff --git a/src/boot/meson.build b/src/boot/meson.build index dfac98f034a6d..29fb64efbee1b 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -309,6 +309,7 @@ endif libefi_sources = files( 'chid.c', 'console.c', + 'cpio.c', 'device-path-util.c', 'devicetree.c', 'drivers.c', @@ -341,7 +342,6 @@ systemd_boot_sources = files( stub_sources = files( 'boot-secret.c', - 'cpio.c', 'linux.c', 'splash.c', 'stub.c', From a3d0e761d4a6e1e59844beb153f88e8daa21b2cc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 12:39:26 +0200 Subject: [PATCH 1354/2155] boot: downgrade log level for an error we ignore --- src/boot/cpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 31638b1c8fc22..36c536681cc12 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -406,7 +406,7 @@ EFI_STATUS pack_cpio( err = file_read(extra_dir, items[i], 0, 0, &content, &contentsize); if (err != EFI_SUCCESS) { - log_error_status(err, "Failed to read %ls, ignoring: %m", items[i]); + log_warning_status(err, "Failed to read %ls, ignoring: %m", items[i]); continue; } From 6b1324fb867d89147585ee20160dbe8f37beefc8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 13:35:41 +0200 Subject: [PATCH 1355/2155] man: add a brief note about type 1 extra lines --- man/systemd-stub.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index bf23c900d026c..95f62ca66b56a 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -291,6 +291,14 @@ by systemd-creds encrypt -T (see systemd-creds1 for details); in case of the system extension images by using signed Verity images. + + Note that earlier components of the boot process might register additional initrds, and thus + additional "companion" resources such as system extensions, configuration extensions and credentials for + consumption by the kernel and OS eventually booted. For example, + systemd-boot7 does + this for resources configured in UAPI.1 Type #1 extra + lines. systemd-stub will combine any resources provided that way with the companion + file resources it acquires itself. From fb0143f1ceb03f1b8f8437f5787d5d402a0d2dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:01:30 +0200 Subject: [PATCH 1356/2155] shared/options: add option_parser_get_help_table_ns() helper It'll be used in the next commit. --- src/run/run.c | 2 +- src/shared/options.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/run/run.c b/src/run/run.c index ce35b48fba4cb..5827d91e1f9e4 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -188,7 +188,7 @@ static int help_sudo_mode(void) { * sudo's short switches, hence please do not introduce new short switches unless they have a roughly * equivalent purpose on sudo. Use long options for everything private to run0. */ - r = option_parser_get_help_table_full("run0", /* group= */ NULL, &opts_table); + r = option_parser_get_help_table_ns("run0", &opts_table); if (r < 0) return r; diff --git a/src/shared/options.h b/src/shared/options.h index 5f55dd5d19fa7..f50fbdb3cb3e5 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -236,6 +236,8 @@ int _option_parser_get_help_table_full( Table **ret); #define option_parser_get_help_table_full(namespace, group, ret) \ _option_parser_get_help_table_full(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, namespace, group, ret) +#define option_parser_get_help_table_ns(ns, ret) \ + option_parser_get_help_table_full(ns, /* group= */ NULL, ret) #define option_parser_get_help_table_group(group, ret) \ option_parser_get_help_table_full(/* namespace= */ NULL, group, ret) #define option_parser_get_help_table(ret) \ From c2c98878520e816c2d6535edebd3a5e233360ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 26 Apr 2026 12:51:36 +0200 Subject: [PATCH 1357/2155] udevadm: convert option parsing to the new option parser Verb dispatch is left untouched for now. Co-developed-by: Claude Opus 4.7 (1M context) --- src/udev/udevadm.c | 70 +++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 70ff213cb9999..23e03d6fb0e64 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include -#include "alloc-util.h" #include "argv-util.h" +#include "format-table.h" +#include "help-util.h" #include "label-util.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "udev-util.h" #include "udevadm.h" #include "udevd.h" @@ -28,60 +28,53 @@ static int help(void) { { "lock", "Lock a block device" }, }; - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" - "Send control commands or test the device manager.\n\n" - "Commands:\n", - program_invocation_short_name); + help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); + help_abstract("Send control commands or test the device manager."); + help_section("Commands:"); FOREACH_ELEMENT(desc, short_descriptions) printf(" %-12s %s\n", (*desc)[0], (*desc)[1]); - printf("\nSee the %s for details.\n", link); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; - int c; - +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, "+dhV", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'd': - log_set_max_level(LOG_DEBUG); - break; + OPTION_NAMESPACE("udevadm"): {} - case 'h': + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION('d', "debug", NULL, "Enable debug logging"): + log_set_max_level(LOG_DEBUG); + break; } + *remaining_args = option_parser_get_args(&opts); return 1; /* work to do */ } @@ -99,7 +92,7 @@ static int verb_help_main(int argc, char *argv[], uintptr_t _data, void *userdat return help(); } -static int udevadm_main(int argc, char *argv[]) { +static int udevadm_main(char **args) { static const Verb verbs[] = { { "cat", VERB_ANY, VERB_ANY, 0, verb_cat_main }, { "info", VERB_ANY, VERB_ANY, 0, verb_info_main }, @@ -118,10 +111,11 @@ static int udevadm_main(int argc, char *argv[]) { {} }; - return dispatch_verb(argc, argv, verbs, NULL); + return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); } static int run(int argc, char *argv[]) { + char **args = NULL; int r; if (invoked_as(argv, "udevd")) @@ -130,7 +124,7 @@ static int run(int argc, char *argv[]) { (void) udev_parse_config(); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -138,7 +132,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return udevadm_main(argc, argv); + return udevadm_main(args); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 07fc22cd0384da5dc4a5d576cc90b7e02d38cfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 27 Apr 2026 09:30:54 +0200 Subject: [PATCH 1358/2155] udevadm-cat: convert to OPTION macros --- src/udev/udevadm-cat.c | 90 +++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index 9d94f5a86c652..48ca72041627f 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -1,14 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "alloc-util.h" #include "conf-files.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udevadm.h" #include "udevadm-util.h" @@ -19,83 +19,75 @@ static bool arg_config = false; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-cat", &options); if (r < 0) - return log_oom(); - - printf("%s cat [OPTIONS] [FILE...]\n" - "\n%sShow udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --tldr Skip comments and empty lines\n" - " --config Show udev.conf rather than udev rules files\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + help_cmdline("cat [OPTIONS...] [FILE...]"); + help_abstract("Show udev rules files."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_TLDR, - ARG_CONFIG, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "config", no_argument, NULL, ARG_CONFIG }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "udevadm-cat" }; - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-cat"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - 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(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_TLDR: + + OPTION_LONG("tldr", NULL, + "Skip comments and empty lines"): arg_cat_flags = CAT_TLDR; break; - case ARG_CONFIG: + + OPTION_LONG("config", NULL, + "Show udev.conf rather than udev rules files"): arg_config = true; break; - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (arg_config && optind < argc) + if (arg_config && option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --config and FILEs is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -107,7 +99,7 @@ int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { CLEANUP_ARRAY(files, n_files, conf_file_free_array); - r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; From 765fc4125b871394580e988192c497ad5625ae90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:02:25 +0200 Subject: [PATCH 1359/2155] shared/options: add OPTION_COMMON_RESOLVE_NAMES --- src/shared/options.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shared/options.h b/src/shared/options.h index f50fbdb3cb3e5..a171f5f6a43f7 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -150,10 +150,13 @@ typedef struct Option { "(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: {} \ +#define OPTION_COMMON_VERSION_WITH_HIDDEN_V \ + OPTION_COMMON_VERSION: {} \ OPTION_SHORT('V', NULL, /* help= */ NULL) +#define OPTION_COMMON_RESOLVE_NAMES \ + OPTION('N', "resolve-names", "MODE", \ + "When to resolve users and groups (early, late, or never)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; From bfc07f83da24a883da843ae25fefd8376a1e217d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 20:28:00 +0200 Subject: [PATCH 1360/2155] udev: fix stale optarg use Fixup for 8623980980d3798f26f23aa56c1491cfd6ceb7b2. This didn't cause any problems until the conversion away from getopt_long(). --- src/udev/udevadm-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index c30af47ff7c73..7e2420a77e8a9 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -144,7 +144,7 @@ int parse_resolve_name_timing(const char *str, ResolveNameTiming *ret) { if (streq(str, "help")) return DUMP_STRING_TABLE(resolve_name_timing, ResolveNameTiming, _RESOLVE_NAME_TIMING_MAX); - ResolveNameTiming v = resolve_name_timing_from_string(optarg); + ResolveNameTiming v = resolve_name_timing_from_string(str); if (v < 0) return log_error_errno(v, "--resolve-names= must be 'early', 'late', or 'never'."); From 5893cf3dcf35f8017c15e34dacdc51a695163aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 10:48:04 +0200 Subject: [PATCH 1361/2155] udev: convert udev-config.c to OPTION macros --timeout-signal is now documented (fixup for e209926778267cbd3e09ed8137bf45b7f370aed0). Co-developed-by: Claude Opus 4.7 --- src/udev/udev-config.c | 130 ++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 74 deletions(-) diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index 17deadfe76071..e234d6fe6d994 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "conf-parser.h" @@ -8,10 +7,12 @@ #include "daemon-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" +#include "help-util.h" #include "limits-util.h" +#include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "serialize.h" #include "signal-util.h" @@ -149,110 +150,91 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-udevd.service", "8", &link); + r = option_parser_get_help_table_ns("udevd", &options); if (r < 0) - return log_oom(); - - printf("%s [OPTIONS...]\n\n" - "Rule-based manager for device events and files.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --daemon Detach and run in the background\n" - " -D --debug Enable debug output\n" - " -c --children-max=INT Set maximum number of workers\n" - " -e --exec-delay=SECONDS Seconds to wait before executing RUN=\n" - " -t --event-timeout=SECONDS Seconds to wait before terminating an event\n" - " -N --resolve-names=early|late|never\n" - " When to resolve users and groups\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Rule-based manager for device events and files."); + + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-udevd.service", "8"); return 0; } static int parse_argv(int argc, char *argv[], UdevConfig *config) { - enum { - ARG_TIMEOUT_SIGNAL, - }; - - static const struct option options[] = { - { "daemon", no_argument, NULL, 'd' }, - { "debug", no_argument, NULL, 'D' }, - { "children-max", required_argument, NULL, 'c' }, - { "exec-delay", required_argument, NULL, 'e' }, - { "event-timeout", required_argument, NULL, 't' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "timeout-signal", required_argument, NULL, ARG_TIMEOUT_SIGNAL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); assert(config); - while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, OPTION_PARSER_NORMAL, "udevd" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'd': + OPTION_NAMESPACE("udevd"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + printf("%s\n", GIT_VERSION); + return 0; + + OPTION('d', "daemon", NULL, "Detach and run in the background"): arg_daemonize = true; break; - case 'c': - r = safe_atou(optarg, &config->children_max); - if (r < 0) - log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", optarg); + + OPTION('D', "debug", NULL, "Enable debug output"): + arg_debug = true; + config->log_level = LOG_DEBUG; break; - case 'e': - r = parse_sec(optarg, &config->exec_delay_usec); + + OPTION('c', "children-max", "INT", "Set maximum number of workers"): + r = safe_atou(opts.arg, &config->children_max); if (r < 0) - log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", opts.arg); break; - case ARG_TIMEOUT_SIGNAL: - r = signal_from_string(optarg); - if (r <= 0) - log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", optarg); - else - config->timeout_signal = r; - break; - case 't': - r = parse_sec(optarg, &config->timeout_usec); + OPTION('e', "exec-delay", "SECONDS", "Seconds to wait before executing RUN="): + r = parse_sec(opts.arg, &config->exec_delay_usec); if (r < 0) - log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", opts.arg); break; - case 'D': - arg_debug = true; - config->log_level = LOG_DEBUG; + + OPTION('t', "event-timeout", "SECONDS", "Seconds to wait before terminating an event"): + r = parse_sec(opts.arg, &config->timeout_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", opts.arg); break; - case 'N': { - ResolveNameTiming t; - t = resolve_name_timing_from_string(optarg); + OPTION_COMMON_RESOLVE_NAMES: { + ResolveNameTiming t = resolve_name_timing_from_string(opts.arg); if (t < 0) - log_warning("Invalid --resolve-names= value '%s', ignoring.", optarg); + log_warning("Invalid --resolve-names= value '%s', ignoring.", opts.arg); else config->resolve_name_timing = t; break; } - case 'h': - return help(); - case 'V': - printf("%s\n", GIT_VERSION); - return 0; - case '?': - return -EINVAL; - default: - assert_not_reached(); + OPTION_LONG("timeout-signal", "SIGNAL", "Signal used when terminating an event"): + r = signal_from_string(opts.arg); + if (r <= 0) + log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", opts.arg); + else + config->timeout_signal = r; + break; } - } return 1; } From ce4746f228085950b455c2c0c55a3ab9d17ba89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:00:00 +0200 Subject: [PATCH 1362/2155] udevadm-hwdb: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-hwdb.c | 97 +++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 5810efefd8ce2..f4060673ebfe7 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "format-table.h" +#include "help-util.h" #include "hwdb-util.h" #include "log.h" +#include "options.h" #include "udevadm.h" static const char *arg_test = NULL; @@ -14,65 +16,64 @@ static bool arg_update = false; static bool arg_strict = false; static int help(void) { - printf("%s hwdb [OPTIONS]\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -u --update Update the hardware database\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -t --test=MODALIAS Query database and print result\n" - " -r --root=PATH Alternative root path in the filesystem\n\n" - "NOTE:\n" - "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" - "Please use systemd-hwdb instead.\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-hwdb", &options); + if (r < 0) + return r; + help_cmdline("hwdb [OPTIONS]"); + help_abstract("Update or query the hardware database."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nNOTE:\n" + "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" + "Please use systemd-hwdb instead.\n"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_USR = 0x100, - }; - - static const struct option options[] = { - { "update", no_argument, NULL, 'u' }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "test", required_argument, NULL, 't' }, - { "root", required_argument, NULL, 'r' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int c; - - while ((c = getopt_long(argc, argv, "ust:r:Vh", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-hwdb" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'u': + + OPTION_NAMESPACE("udevadm-hwdb"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('u', "update", NULL, "Update the hardware database"): arg_update = true; break; - 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 't': - arg_test = optarg; + + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; + + OPTION('t', "test", "MODALIAS", "Query database and print result"): + arg_test = opts.arg; break; - case 'r': - arg_root = optarg; + + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = opts.arg; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } return 1; From 57516477999d09f27be689a01c79360b32fb370f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:01:28 +0200 Subject: [PATCH 1363/2155] udevadm-test-builtin: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-test-builtin.c | 70 ++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index f17df9a7d51a2..31ac569957017 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include - #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "udev-builtin.h" #include "udevadm.h" #include "udevadm-util.h" @@ -15,51 +15,57 @@ static const char *arg_command = NULL; static const char *arg_syspath = NULL; 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" - " -a --action=ACTION|help Set action string\n" - "\nCommands:\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; - udev_builtin_list(); + r = option_parser_get_help_table_ns("udevadm-test-builtin", &options); + if (r < 0) + return r; + help_cmdline("test-builtin [OPTIONS] COMMAND DEVPATH"); + help_abstract("Test a built-in command."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_section("Commands:"); + udev_builtin_list(); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + int r; - int r, c; + assert(argc >= 0); + assert(argv); - while ((c = getopt_long(argc, argv, "a:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-test-builtin" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test-builtin"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc != optind + 2) + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: command string and device path."); - arg_command = ASSERT_PTR(argv[optind]); - arg_syspath = ASSERT_PTR(argv[optind+1]); + char **args = option_parser_get_args(&opts); + arg_command = ASSERT_PTR(args[0]); + arg_syspath = ASSERT_PTR(args[1]); return 1; } From 12e5e0e90381b79f1d58d86f9ec06973f6b9b9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:16:04 +0200 Subject: [PATCH 1364/2155] udevadm-verify: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-verify.c | 93 +++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 6af7f06ab05fe..1ecc1fbee9c78 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -1,16 +1,17 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "alloc-util.h" +#include "ansi-color.h" #include "conf-files.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udev-rules.h" #include "udevadm.h" #include "udevadm-util.h" @@ -23,81 +24,66 @@ static bool arg_style = true; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-verify", &options); if (r < 0) - return log_oom(); - - printf("%s verify [OPTIONS] [FILE...]\n" - "\n%sVerify udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --no-summary Do not show summary\n" - " --no-style Ignore style issues\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + + help_cmdline("verify [OPTIONS] [FILE...]"); + help_abstract("Verify udev rules files."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_NO_SUMMARY, - ARG_NO_STYLE, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "no-summary", no_argument, NULL, ARG_NO_SUMMARY }, - { "no-style", no_argument, NULL, ARG_NO_STYLE }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "udevadm-verify" }; - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-verify"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; 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(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_NO_SUMMARY: + + OPTION_LONG("no-summary", NULL, "Do not show summary"): arg_summary = false; break; - case ARG_NO_STYLE: + OPTION_LONG("no-style", NULL, "Ignore style issues"): arg_style = false; break; - - case '?': - return -EINVAL; - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -158,9 +144,10 @@ static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_file int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -173,7 +160,7 @@ int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { CLEANUP_ARRAY(files, n_files, conf_file_free_array); - r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; From a517a6297e6dfc98cfd823baf692be3553088899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:17:14 +0200 Subject: [PATCH 1365/2155] udevadm-test: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-test.c | 95 +++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index f3ac39717e946..a7841333016f9 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -3,7 +3,6 @@ * Copyright © 2003-2004 Greg Kroah-Hartman */ -#include #include #include @@ -12,7 +11,10 @@ #include "alloc-util.h" #include "device-private.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "static-destruct.h" #include "strv.h" @@ -33,55 +35,59 @@ static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-test", &options); + if (r < 0) + return r; - printf("%s test [OPTIONS] DEVPATH\n\n" - "Test an event run.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -a --action=ACTION|help Set action string\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " -D --extra-rules-dir=DIR Also load rules from the directory\n" - " -v --verbose Show verbose logs\n" - " --json=pretty|short|off Generate JSON output\n", - program_invocation_short_name); + help_cmdline("test [OPTIONS] DEVPATH"); + help_abstract("Test an event run."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_JSON = 0x100, - }; - - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "extra-rules-dir", required_argument, NULL, 'D' }, - { "verbose", no_argument, NULL, 'v' }, - { "json", required_argument, NULL, ARG_JSON }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int r, c; - - while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-test" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; break; - case 'D': { + + OPTION('D', "extra-rules-dir", "DIR", "Also load rules from the directory"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -90,25 +96,20 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; } - case 'v': + + OPTION('v', "verbose", NULL, "Show verbose logs"): arg_verbose = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - arg_syspath = argv[optind]; + char **args = option_parser_get_args(&opts); + arg_syspath = args[0]; if (!arg_syspath) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "syspath parameter missing."); From 5a2144f9bc78c41d670791f7c8063a4574296f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:22:32 +0200 Subject: [PATCH 1366/2155] udevadm-monitor: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-monitor.c | 95 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 6f33cc3710cca..0c165241a2e3d 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "sd-device.h" #include "sd-event.h" @@ -9,8 +7,11 @@ #include "device-monitor-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" #include "format-util.h" #include "hashmap.h" +#include "help-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-util.h" @@ -99,60 +100,70 @@ static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_ } static int help(void) { - printf("%s monitor [OPTIONS]\n\n" - "Listen to kernel and udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -p --property Print the event properties\n" - " -k --kernel Print kernel uevents\n" - " -u --udev Print udev events\n" - " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" - " -t --tag-match=TAG Filter events by tag\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-monitor", &options); + if (r < 0) + return r; + + help_cmdline("monitor [OPTIONS]"); + help_abstract("Listen to kernel and udev events."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "property", no_argument, NULL, 'p' }, - { "environment", no_argument, NULL, 'e' }, /* alias for -p */ - { "kernel", no_argument, NULL, 'k' }, - { "udev", no_argument, NULL, 'u' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "tag-match", required_argument, NULL, 't' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int r, c; - - while ((c = getopt_long(argc, argv, "pekus:t:Vh", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-monitor" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'p': - case 'e': + + OPTION_NAMESPACE("udevadm-monitor"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "environment", NULL, /* help= */ NULL): {} /* hidden alias for -p */ + OPTION('p', "property", NULL, "Print the event properties"): arg_show_property = true; break; - case 'k': + + OPTION('k', "kernel", NULL, "Print kernel uevents"): arg_print_kernel = true; break; - case 'u': + + OPTION('u', "udev", NULL, "Print udev events"): arg_print_udev = true; break; - case 's': { + + OPTION('s', "subsystem-match", "SUBSYSTEM[/DEVTYPE]", + "Filter events by subsystem"): { _cleanup_free_ char *subsystem = NULL, *devtype = NULL; const char *slash; - slash = strchr(optarg, '/'); + slash = strchr(opts.arg, '/'); if (slash) { devtype = strdup(slash + 1); if (!devtype) return log_oom(); - subsystem = strndup(optarg, slash - optarg); + subsystem = strndup(opts.arg, slash - opts.arg); } else - subsystem = strdup(optarg); + subsystem = strdup(opts.arg); if (!subsystem) return log_oom(); @@ -165,20 +176,12 @@ static int parse_argv(int argc, char *argv[]) { TAKE_PTR(devtype); break; } - case 't': - r = set_put_strdup(&arg_tag_filter, optarg); + + OPTION('t', "tag-match", "TAG", "Filter events by tag"): + r = set_put_strdup(&arg_tag_filter, opts.arg); if (r < 0) return log_oom(); break; - - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } if (!arg_print_kernel && !arg_print_udev) { From ed2b92e2057a4bdae32ab7c81480a3a6c70e2487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:28:35 +0200 Subject: [PATCH 1367/2155] udevadm-settle: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-settle.c | 89 +++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index b71759dc818e6..1292462d28c25 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -4,7 +4,6 @@ * Copyright © 2009 Scott James Remnant */ -#include #include #include "sd-bus.h" @@ -14,6 +13,9 @@ #include "alloc-util.h" #include "bus-util.h" +#include "format-table.h" +#include "help-util.h" +#include "options.h" #include "path-util.h" #include "string-util.h" #include "strv.h" @@ -28,60 +30,63 @@ static usec_t arg_timeout_usec = 120 * USEC_PER_SEC; static const char *arg_exists = NULL; static int help(void) { - printf("%s settle [OPTIONS]\n\n" - "Wait for pending udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -t --timeout=SEC Maximum time to wait for events\n" - " -E --exit-if-exists=FILE Stop waiting if file exists\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-settle", &options); + if (r < 0) + return r; + help_cmdline("settle [OPTIONS]"); + help_abstract("Wait for pending udev events."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "exit-if-exists", required_argument, NULL, 'E' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "seq-start", required_argument, NULL, 's' }, /* removed */ - { "seq-end", required_argument, NULL, 'e' }, /* removed */ - { "quiet", no_argument, NULL, 'q' }, /* removed */ - {} - }; - - int c, r; - - while ((c = getopt_long(argc, argv, "t:E:Vhs:e:q", options, NULL)) >= 0) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-settle" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + + OPTION_NAMESPACE("udevadm-settle"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for events"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); break; - case 'E': - if (!path_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", optarg); - arg_exists = optarg; + OPTION('E', "exit-if-exists", "FILE", "Stop waiting if file exists"): + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", opts.arg); + + arg_exists = opts.arg; break; - case 'V': - return print_version(); - case 'h': - return help(); - case 's': - case 'e': - case 'q': + + OPTION('s', "seq-start", "ARG", NULL): {} /* removed */ + OPTION('e', "seq-end", "ARG", NULL): {} /* removed */ + OPTION('q', "quiet", NULL, NULL): /* removed */ return log_info_errno(SYNTHETIC_ERRNO(EINVAL), "Option -%c no longer supported.", - c); - case '?': - return -EINVAL; - default: - assert_not_reached(); + opts.opt->short_code); } - } return 1; } From a00de0b648aa9e62daef97c8d5461be8e8d55afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:31:51 +0200 Subject: [PATCH 1368/2155] udevadm-lock: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-lock.c | 91 ++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 483b64973d401..cebce08007eb0 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include #include @@ -9,11 +8,14 @@ #include "device-util.h" #include "fd-util.h" #include "fdset.h" +#include "format-table.h" +#include "glyph-util.h" #include "hash-funcs.h" +#include "help-util.h" #include "lock-util.h" +#include "options.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "sort-util.h" @@ -33,70 +35,52 @@ STATIC_DESTRUCTOR_REGISTER(arg_backing, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-lock", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND\n" - "%s [OPTIONS...] --print\n" - "\n%sLock a block device and run a command.%s\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --device=DEVICE Block device to lock\n" - " -b --backing=FILE File whose backing block device to lock\n" - " -t --timeout=SECS Block at most the specified time waiting for lock\n" - " -p --print Only show which block device the lock would be taken on\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("lock [OPTIONS...] COMMAND"); + help_cmdline("lock [OPTIONS...] --print"); + help_abstract("Lock a block device and run a command."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "device", required_argument, NULL, 'd' }, - { "backing", required_argument, NULL, 'b' }, - { "timeout", required_argument, NULL, 't' }, - { "print", no_argument, NULL, 'p' }, - {} - }; - - int c, r; + int r; 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, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm-lock" }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_NAMESPACE("udevadm-lock"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'd': - case 'b': { + OPTION('d', "device", "DEVICE", "Block device to lock"): {} /* fall through */ + OPTION('b', "backing", "FILE", "File whose backing block device to lock"): { _cleanup_free_ char *s = NULL; - char ***l = c == 'd' ? &arg_devices : &arg_backing; + char ***l = opts.opt->short_code == 'd' ? &arg_devices : &arg_backing; - r = path_make_absolute_cwd(optarg, &s); + r = path_make_absolute_cwd(opts.arg, &s); if (r < 0) - return log_error_errno(r, "Failed to make path '%s' absolute: %m", optarg); + return log_error_errno(r, "Failed to make path '%s' absolute: %m", opts.arg); path_simplify(s); @@ -107,31 +91,26 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + OPTION('t', "timeout", "SECS", "Block at most the specified time waiting for lock"): + r = parse_sec(opts.arg, &arg_timeout_usec); 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", opts.arg); break; - case 'p': + OPTION('p', "print", NULL, "Only show which block device the lock would be taken on"): arg_print = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&opts); if (arg_print) { - if (optind != argc) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected."); } else { - if (optind + 1 > argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, command to execute."); - arg_cmdline = strv_copy(argv + optind); + arg_cmdline = strv_copy(args); if (!arg_cmdline) return log_oom(); } From 0257deff36d1045d31144976a9c4b58e90d7bc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:33:16 +0200 Subject: [PATCH 1369/2155] udevadm-control: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-control.c | 141 +++++++++++++++---------------------- 1 file changed, 56 insertions(+), 85 deletions(-) diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 964f721731ceb..ed586d5542d1f 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include "creds-util.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "static-destruct.h" @@ -47,151 +48,121 @@ static bool arg_has_control_commands(void) { } static int help(void) { - printf("%s control OPTION\n\n" - "Control the udev daemon.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -e --exit Instruct the daemon to cleanup and exit\n" - " -l --log-level=LEVEL Set the udev log level for the daemon\n" - " -s --stop-exec-queue Do not execute events, queue only\n" - " -S --start-exec-queue Execute events, flush queue\n" - " -R --reload Reload rules and databases\n" - " -p --property=KEY=VALUE Set a global property for all events\n" - " -m --children-max=N Maximum number of children\n" - " --ping Wait for udev to respond to a ping message\n" - " --trace=BOOL Enable/disable trace logging\n" - " --revert Revert previously set configurations\n" - " -t --timeout=SECONDS Maximum time to block for a reply\n" - " --load-credentials Load udev rules from credentials\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-control", &options); + if (r < 0) + return r; + + help_cmdline("control OPTION"); + help_abstract("Control the udev daemon."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PING = 0x100, - ARG_TRACE, - ARG_REVERT, - ARG_LOAD_CREDENTIALS, - }; - - static const struct option options[] = { - { "exit", no_argument, NULL, 'e' }, - { "log-level", required_argument, NULL, 'l' }, - { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */ - { "stop-exec-queue", no_argument, NULL, 's' }, - { "start-exec-queue", no_argument, NULL, 'S' }, - { "reload", no_argument, NULL, 'R' }, - { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ - { "property", required_argument, NULL, 'p' }, - { "env", required_argument, NULL, 'p' }, /* alias for -p */ - { "children-max", required_argument, NULL, 'm' }, - { "ping", no_argument, NULL, ARG_PING }, - { "trace", required_argument, NULL, ARG_TRACE }, - { "revert", no_argument, NULL, ARG_REVERT }, - { "timeout", required_argument, NULL, 't' }, - { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "el:sSRp:m:t:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-control" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'e': + OPTION_NAMESPACE("udevadm-control"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "exit", NULL, "Instruct the daemon to cleanup and exit"): arg_exit = true; break; - case 'l': - arg_log_level = log_level_from_string(optarg); + OPTION_LONG("log-priority", "LEVEL", NULL): {} /* backward compat alias for --log-level */ + OPTION('l', "log-level", "LEVEL", "Set the udev log level for the daemon"): + arg_log_level = log_level_from_string(opts.arg); if (arg_log_level < 0) - return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", optarg); + return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", opts.arg); break; - case 's': + OPTION('s', "stop-exec-queue", NULL, "Do not execute events, queue only"): arg_start_exec_queue = false; break; - case 'S': + OPTION('S', "start-exec-queue", NULL, "Execute events, flush queue"): arg_start_exec_queue = true; break; - case 'R': + OPTION_LONG("reload-rules", NULL, NULL): {} /* hidden alias for -R */ + OPTION('R', "reload", NULL, "Reload rules and databases"): arg_reload = true; break; - case 'p': - if (!strchr(optarg, '=')) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", optarg); + OPTION_LONG("env", "KEY=VALUE", NULL): {} /* hidden alias for -p */ + OPTION('p', "property", "KEY=VALUE", "Set a global property for all events"): + if (!strchr(opts.arg, '=')) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", opts.arg); - r = strv_extend(&arg_env, optarg); + r = strv_extend(&arg_env, opts.arg); if (r < 0) return log_error_errno(r, "Failed to extend environment: %m"); break; - case 'm': { + OPTION('m', "children-max", "N", "Maximum number of children"): { unsigned i; - r = safe_atou(optarg, &i); + r = safe_atou(opts.arg, &i); if (r < 0) - return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", optarg); + return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", opts.arg); arg_max_children = i; break; } - case ARG_PING: + OPTION_LONG("ping", NULL, "Wait for udev to respond to a ping message"): arg_ping = true; break; - case ARG_TRACE: - r = parse_boolean_argument("--trace=", optarg, NULL); + OPTION_LONG("trace", "BOOL", "Enable/disable trace logging"): + r = parse_boolean_argument("--trace=", opts.arg, NULL); if (r < 0) return r; arg_trace = r; break; - case ARG_REVERT: + OPTION_LONG("revert", NULL, "Revert previously set configurations"): arg_revert = true; break; - case 't': - r = parse_sec(optarg, &arg_timeout); + OPTION('t', "timeout", "SECONDS", "Maximum time to block for a reply"): + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); break; - case ARG_LOAD_CREDENTIALS: + OPTION_LONG("load-credentials", NULL, "Load udev rules from credentials"): arg_load_credentials = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_has_control_commands() && !arg_load_credentials) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No control command option is specified."); - if (optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Extraneous argument: %s", argv[optind]); + "This subprogram takes no positional arguments."); return 1; } From 5656636a417cd859cd5717798719e220962097ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:34:15 +0200 Subject: [PATCH 1370/2155] udevadm-wait: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-wait.c | 98 +++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 0e285fc36b247..a361bac61a3a7 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include @@ -10,7 +8,10 @@ #include "device-monitor-private.h" #include "device-util.h" #include "event-util.h" +#include "format-table.h" #include "fs-util.h" +#include "help-util.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "static-destruct.h" @@ -297,79 +298,72 @@ static int setup_periodic_timer(sd_event *event) { } static int help(void) { - printf("%s wait [OPTIONS] DEVICE [DEVICE…]\n\n" - "Wait for devices or device symlinks being created.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -t --timeout=SEC Maximum time to wait for the device\n" - " --initialized=BOOL Wait for devices being initialized by systemd-udevd\n" - " --removed Wait for devices being removed\n" - " --settle Also wait for all queued events being processed\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-wait", &options); + if (r < 0) + return r; + + help_cmdline("wait [OPTIONS] DEVICE [DEVICE…]"); + help_abstract("Wait for devices or device symlinks being created."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_INITIALIZED = 0x100, - ARG_REMOVED, - ARG_SETTLE, - }; - - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "initialized", required_argument, NULL, ARG_INITIALIZED }, - { "removed", no_argument, NULL, ARG_REMOVED }, - { "settle", no_argument, NULL, ARG_SETTLE }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; - - int c, r; - - while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-wait" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + + OPTION_NAMESPACE("udevadm-wait"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for the device"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", opts.arg); break; - case ARG_INITIALIZED: - r = parse_boolean(optarg); + OPTION_LONG("initialized", "BOOL", + "Wait for devices being initialized by systemd-udevd"): + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --initialized= parameter: %s", opts.arg); arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED; break; - case ARG_REMOVED: + OPTION_LONG("removed", NULL, "Wait for devices being removed"): arg_wait_until = WAIT_UNTIL_REMOVED; break; - case ARG_SETTLE: + OPTION_LONG("settle", NULL, "Also wait for all queued events being processed"): arg_settle = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + char **args = option_parser_get_args(&opts); + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, expected at least one device path or device symlink."); - arg_devices = strv_copy(argv + optind); + arg_devices = strv_copy(args); if (!arg_devices) return log_oom(); From 222b417494092ed677ebfd2349605d52ae00d87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:36:00 +0200 Subject: [PATCH 1371/2155] udevadm-trigger: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-trigger.c | 220 +++++++++++++++---------------------- 1 file changed, 89 insertions(+), 131 deletions(-) diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index afa6a84262084..62ccba37c5b8d 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "sd-device.h" @@ -10,7 +9,10 @@ #include "device-enumerator-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "id128-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-table.h" @@ -320,214 +322,170 @@ static int setup_matches(sd_device_enumerator *e) { } static int help(void) { - printf("%s trigger [OPTIONS] DEVPATH\n\n" - "Request events from the kernel.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -v --verbose Print the list of devices while running\n" - " -n --dry-run Do not actually trigger the events\n" - " -q --quiet Suppress error logging in triggering events\n" - " -t --type= Type of events to trigger\n" - " devices sysfs devices (default)\n" - " subsystems sysfs subsystems and drivers\n" - " all sysfs devices, subsystems, and drivers\n" - " -c --action=ACTION|help Event action value, default is \"change\"\n" - " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" - " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" - " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" - " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" - " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" - " -g --tag-match=TAG Trigger devices with a matching tag\n" - " -y --sysname-match=NAME Trigger devices with this /sys path\n" - " --name-match=NAME Trigger devices with this /dev name\n" - " -b --parent-match=NAME Trigger devices with that parent device\n" - " --include-parents Trigger parent devices of found devices\n" - " --initialized-match Trigger devices that are already initialized\n" - " --initialized-nomatch Trigger devices that are not initialized yet\n" - " -w --settle Wait for the triggered events to complete\n" - " --wait-daemon[=SECONDS] Wait for udevd daemon to be initialized\n" - " before triggering uevents\n" - " --uuid Print synthetic uevent UUID\n" - " --prioritized-subsystem=SUBSYSTEM[,SUBSYSTEM…]\n" - " Trigger devices from a matching subsystem first\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-trigger", &options); + if (r < 0) + return r; + + help_cmdline("trigger [OPTIONS] DEVPATH"); + help_abstract("Request events from the kernel."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_NAME = 0x100, - ARG_PING, - ARG_UUID, - ARG_PRIORITIZED_SUBSYSTEM, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - ARG_INCLUDE_PARENTS, - }; - - static const struct option options[] = { - { "verbose", no_argument, NULL, 'v' }, - { "dry-run", no_argument, NULL, 'n' }, - { "quiet", no_argument, NULL, 'q' }, - { "type", required_argument, NULL, 't' }, - { "action", required_argument, NULL, 'c' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "subsystem-nomatch", required_argument, NULL, 'S' }, - { "attr-match", required_argument, NULL, 'a' }, - { "attr-nomatch", required_argument, NULL, 'A' }, - { "property-match", required_argument, NULL, 'p' }, - { "tag-match", required_argument, NULL, 'g' }, - { "sysname-match", required_argument, NULL, 'y' }, - { "name-match", required_argument, NULL, ARG_NAME }, - { "parent-match", required_argument, NULL, 'b' }, - { "include-parents", no_argument, NULL, ARG_INCLUDE_PARENTS }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - { "settle", no_argument, NULL, 'w' }, - { "wait-daemon", optional_argument, NULL, ARG_PING }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "uuid", no_argument, NULL, ARG_UUID }, - { "prioritized-subsystem", required_argument, NULL, ARG_PRIORITIZED_SUBSYSTEM }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "vnqt:c:s:S:a:A:p:g:y:b:wVh", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "udevadm-trigger" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'v': + + OPTION_NAMESPACE("udevadm-trigger"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('v', "verbose", NULL, "Print the list of devices while running"): arg_verbose = true; break; - case 'n': + OPTION('n', "dry-run", NULL, "Do not actually trigger the events"): arg_dry_run = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress error logging in triggering events"): arg_quiet = true; break; - case 't': - arg_scan_type = scan_type_from_string(optarg); + OPTION('t', "type", "TYPE", "Type of sysfs events to trigger:"): {} + OPTION_HELP_VERBATIM(" devices", "- devices (default)"): {} + OPTION_HELP_VERBATIM(" subsystems", "- subsystems and drivers"): {} + OPTION_HELP_VERBATIM(" all", "- devices, subsystems, and drivers"): + arg_scan_type = scan_type_from_string(opts.arg); if (arg_scan_type < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", opts.arg); break; - case 'c': - r = parse_device_action(optarg, &arg_action); + OPTION('c', "action", "ACTION|help", "Event action value, default is \"change\""): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 's': - r = strv_extend(&arg_subsystem_match, optarg); + OPTION('s', "subsystem-match", "SUBSYSTEM", + "Trigger devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case 'S': - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION('S', "subsystem-nomatch", "SUBSYSTEM", + "Exclude devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - r = strv_extend(&arg_attr_match, optarg); + OPTION('a', "attr-match", "FILE[=VALUE]", + "Trigger devices with a matching attribute"): + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case 'A': - r = strv_extend(&arg_attr_nomatch, optarg); + OPTION('A', "attr-nomatch", "FILE[=VALUE]", + "Exclude devices with a matching attribute"): + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'p': - r = strv_extend(&arg_property_match, optarg); + OPTION('p', "property-match", "KEY=VALUE", + "Trigger devices with a matching property"): + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case 'g': - r = strv_extend(&arg_tag_match, optarg); + OPTION('g', "tag-match", "TAG", "Trigger devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case 'y': - r = strv_extend(&arg_sysname_match, optarg); + OPTION('y', "sysname-match", "NAME", "Trigger devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case 'b': - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("name-match", "NAME", "Trigger devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INCLUDE_PARENTS: + OPTION('b', "parent-match", "NAME", "Trigger devices with that parent device"): + r = strv_extend(&arg_parent_match, opts.arg); + if (r < 0) + return log_oom(); + break; + + OPTION_LONG("include-parents", NULL, "Trigger parent devices of found devices"): arg_include_parents = true; break; - case 'w': - arg_settle = true; + OPTION_LONG("initialized-match", NULL, + "Trigger devices that are already initialized"): + arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_NAME: - r = strv_extend(&arg_name_match, optarg); - if (r < 0) - return log_oom(); + OPTION_LONG("initialized-nomatch", NULL, + "Trigger devices that are not initialized yet"): + arg_initialized_match = MATCH_INITIALIZED_NO; + break; + + OPTION('w', "settle", NULL, "Wait for the triggered events to complete"): + arg_settle = true; break; - case ARG_PING: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "wait-daemon", "SECONDS", + "Wait for udevd daemon to be initialized before triggering uevents"): arg_ping = true; - if (optarg) { - r = parse_sec(optarg, &arg_ping_timeout_usec); + if (opts.arg) { + r = parse_sec(opts.arg, &arg_ping_timeout_usec); if (r < 0) - log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", optarg); + log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", opts.arg); } break; - case ARG_UUID: + OPTION_LONG("uuid", NULL, "Print synthetic uevent UUID"): arg_uuid = true; break; - case ARG_PRIORITIZED_SUBSYSTEM: - r = strv_split_and_extend(&arg_prioritized_subsystems, optarg, ",", /* filter_duplicates= */ false); + OPTION_LONG("prioritized-subsystem", "SUBSYSTEM[,SUBSYSTEM…]", + "Trigger devices from a matching subsystem first"): + r = strv_split_and_extend(&arg_prioritized_subsystems, opts.arg, ",", /* filter_duplicates= */ false); if (r < 0) return log_oom(); break; - - case ARG_INITIALIZED_MATCH: - arg_initialized_match = MATCH_INITIALIZED_YES; - break; - - case ARG_INITIALIZED_NOMATCH: - arg_initialized_match = MATCH_INITIALIZED_NO; - break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); From c86ba4e037e1879e0bc7e93449b3fdfe4939dde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:40:54 +0200 Subject: [PATCH 1372/2155] udevadm-info: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-info.c | 292 ++++++++++++++++------------------------ 1 file changed, 113 insertions(+), 179 deletions(-) diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 62d7dce4217de..3795856592c1d 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -20,7 +19,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glyph-util.h" +#include "help-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "sort-util.h" @@ -800,51 +802,21 @@ static int query_device(QueryType query, sd_device* device) { } static int help(void) { - printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" - "Query sysfs or the udev database.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -q --query=TYPE Query device information:\n" - " name Name of device node\n" - " symlink Pointing to node\n" - " path sysfs device path\n" - " property The device properties\n" - " all All values\n" - " --property=NAME Show only properties by this name\n" - " --value When showing properties, print only their values\n" - " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" - " -n --name=NAME Node or symlink name used for query or attribute walk\n" - " -r --root Prepend dev directory to path names\n" - " -a --attribute-walk Print all key matches walking along the chain\n" - " of parent devices\n" - " -t --tree Show tree of devices\n" - " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" - " -x --export Export key/value pairs\n" - " -P --export-prefix Export the key name with a prefix\n" - " -e --export-db Export the content of the udev database\n" - " -c --cleanup-db Clean up the udev database\n" - " -w --wait-for-initialization[=SECONDS]\n" - " Wait for device to be initialized\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --subsystem-match=SUBSYSTEM\n" - " Query devices matching a subsystem\n" - " --subsystem-nomatch=SUBSYSTEM\n" - " Query devices not matching a subsystem\n" - " --attr-match=FILE[=VALUE]\n" - " Query devices that match an attribute\n" - " --attr-nomatch=FILE[=VALUE]\n" - " Query devices that do not match an attribute\n" - " --property-match=KEY=VALUE\n" - " Query devices with matching properties\n" - " --tag-match=TAG Query devices with a matching tag\n" - " --sysname-match=NAME Query devices with this /sys path\n" - " --name-match=NAME Query devices with this /dev name\n" - " --parent-match=NAME Query devices with this parent device\n" - " --initialized-match Query devices that are already initialized\n" - " --initialized-nomatch Query devices that are not initialized yet\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-info", &options); + if (r < 0) + return r; + + help_cmdline("info [OPTIONS] [DEVPATH|FILE]"); + help_abstract("Query sysfs or the udev database."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } @@ -1006,90 +978,64 @@ static int print_tree(sd_device* below) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_PROPERTY = 0x100, - ARG_VALUE, - ARG_NO_PAGER, - ARG_JSON, - ARG_SUBSYSTEM_MATCH, - ARG_SUBSYSTEM_NOMATCH, - ARG_ATTR_MATCH, - ARG_ATTR_NOMATCH, - ARG_PROPERTY_MATCH, - ARG_TAG_MATCH, - ARG_SYSNAME_MATCH, - ARG_NAME_MATCH, - ARG_PARENT_MATCH, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - }; - - static const struct option options[] = { - { "attribute-walk", no_argument, NULL, 'a' }, - { "tree", no_argument, NULL, 't' }, - { "cleanup-db", no_argument, NULL, 'c' }, - { "device-id-of-file", required_argument, NULL, 'd' }, - { "export", no_argument, NULL, 'x' }, - { "export-db", no_argument, NULL, 'e' }, - { "export-prefix", required_argument, NULL, 'P' }, - { "help", no_argument, NULL, 'h' }, - { "name", required_argument, NULL, 'n' }, - { "path", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "query", required_argument, NULL, 'q' }, - { "root", no_argument, NULL, 'r' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "version", no_argument, NULL, 'V' }, - { "wait-for-initialization", optional_argument, NULL, 'w' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "subsystem-match", required_argument, NULL, ARG_SUBSYSTEM_MATCH }, - { "subsystem-nomatch", required_argument, NULL, ARG_SUBSYSTEM_NOMATCH }, - { "attr-match", required_argument, NULL, ARG_ATTR_MATCH }, - { "attr-nomatch", required_argument, NULL, ARG_ATTR_NOMATCH }, - { "property-match", required_argument, NULL, ARG_PROPERTY_MATCH }, - { "tag-match", required_argument, NULL, ARG_TAG_MATCH }, - { "sysname-match", required_argument, NULL, ARG_SYSNAME_MATCH }, - { "name-match", required_argument, NULL, ARG_NAME_MATCH }, - { "parent-match", required_argument, NULL, ARG_PARENT_MATCH }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "atced:n:p:q:rxP:w::Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-info" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case ARG_PROPERTY: + OPTION_NAMESPACE("udevadm-info"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('q', "query", "TYPE", "Query device information:"): {} + OPTION_HELP_VERBATIM(" name", "- name of device node"): {} + OPTION_HELP_VERBATIM(" symlink", "- pointing to node"): {} + OPTION_HELP_VERBATIM(" path", "- sysfs device path"): {} + OPTION_HELP_VERBATIM(" property", "- the device properties"): {} + OPTION_HELP_VERBATIM(" all", "- all values"): + arg_query = query_type_from_string(opts.arg); + if (arg_query < 0) { + if (streq(opts.arg, "env")) /* deprecated */ + arg_query = QUERY_PROPERTY; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", opts.arg); + } + break; + + OPTION_LONG("property", "NAME", "Show only properties by this name"): /* Make sure that if the empty property list was specified, we won't show any properties. */ - if (isempty(optarg) && !arg_properties) { + if (isempty(opts.arg) && !arg_properties) { arg_properties = new0(char*, 1); if (!arg_properties) return log_oom(); } else { - r = strv_split_and_extend(&arg_properties, optarg, ",", true); + r = strv_split_and_extend(&arg_properties, opts.arg, ",", true); if (r < 0) return log_oom(); } break; - case ARG_VALUE: + OPTION_LONG("value", NULL, + "When showing properties, print only their values"): arg_value = true; break; - case 'n': - case 'p': { - const char *prefix = c == 'n' ? "/dev/" : "/sys/"; + OPTION('p', "path", "SYSPATH", "sysfs device path used for query or attribute walk"): {} /* fall through */ + OPTION('n', "name", "NAME", "Node or symlink name used for query or attribute walk"): { + const char *prefix = opts.opt->short_code == 'n' ? "/dev/" : "/sys/"; char *path; - path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg); + path = path_join(path_startswith(opts.arg, prefix) ? NULL : prefix, opts.arg); if (!path) return log_oom(); @@ -1099,159 +1045,147 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'q': - arg_query = query_type_from_string(optarg); - if (arg_query < 0) { - if (streq(optarg, "env")) /* deprecated */ - arg_query = QUERY_PROPERTY; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", optarg); - } + OPTION('r', "root", NULL, "Prepend dev directory to path names"): + arg_root = true; break; - case 'r': - arg_root = true; + OPTION('a', "attribute-walk", NULL, + "Print all key matches walking along the chain of parent devices"): + arg_action_type = ACTION_ATTRIBUTE_WALK; + break; + + OPTION('t', "tree", NULL, "Show tree of devices"): + arg_action_type = ACTION_TREE; break; - case 'd': + OPTION('d', "device-id-of-file", "FILE", + "Print major:minor of device containing this file"): arg_action_type = ACTION_DEVICE_ID_FILE; - r = free_and_strdup(&arg_name, optarg); + r = free_and_strdup(&arg_name, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - arg_action_type = ACTION_ATTRIBUTE_WALK; + OPTION('x', "export", NULL, "Export key/value pairs"): + arg_export = true; break; - case 't': - arg_action_type = ACTION_TREE; + OPTION('P', "export-prefix", "NAME", "Export the key name with a prefix"): + arg_export = true; + arg_export_prefix = opts.arg; break; - case 'e': + OPTION('e', "export-db", NULL, "Export the content of the udev database"): arg_action_type = ACTION_EXPORT; break; - case 'c': + OPTION('c', "cleanup-db", NULL, "Clean up the udev database"): arg_action_type = ACTION_CLEANUP_DB; break; - case 'x': - arg_export = true; - break; - - case 'P': - arg_export = true; - arg_export_prefix = optarg; - break; - - case 'w': - if (optarg) { - r = parse_sec(optarg, &arg_wait_for_initialization_timeout); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'w', "wait-for-initialization", "SECS", + "Wait for device to be initialized"): + if (opts.arg) { + r = parse_sec(opts.arg, &arg_wait_for_initialization_timeout); if (r < 0) return log_error_errno(r, "Failed to parse timeout value: %m"); } else arg_wait_for_initialization_timeout = USEC_INFINITY; break; - case 'V': - return print_version(); - - case 'h': - return help(); - - 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(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_SUBSYSTEM_MATCH: - r = strv_extend(&arg_subsystem_match, optarg); + OPTION_LONG("subsystem-match", "SUBSYSTEM", + "Query devices matching a subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SUBSYSTEM_NOMATCH: - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION_LONG("subsystem-nomatch", "SUBSYSTEM", + "Query devices not matching a subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-match", "FILE[=VALUE]", + "Query devices that match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_match, optarg); + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_NOMATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-nomatch", "FILE[=VALUE]", + "Query devices that do not match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_nomatch, optarg); + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PROPERTY_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("property-match", "KEY=VALUE", + "Query devices with matching properties"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_property_match, optarg); + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_TAG_MATCH: - r = strv_extend(&arg_tag_match, optarg); + OPTION_LONG("tag-match", "TAG", "Query devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SYSNAME_MATCH: - r = strv_extend(&arg_sysname_match, optarg); + OPTION_LONG("sysname-match", "NAME", "Query devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_NAME_MATCH: - r = strv_extend(&arg_name_match, optarg); + OPTION_LONG("name-match", "NAME", "Query devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PARENT_MATCH: - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("parent-match", "NAME", "Query devices with this parent device"): + r = strv_extend(&arg_parent_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INITIALIZED_MATCH: + OPTION_LONG("initialized-match", NULL, + "Query devices that are already initialized"): arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_INITIALIZED_NOMATCH: + OPTION_LONG("initialized-nomatch", NULL, + "Query devices that are not initialized yet"): arg_initialized_match = MATCH_INITIALIZED_NO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); From 408d18f4d215747f7eba352cff1ea3b8c14fb574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 13:24:36 +0200 Subject: [PATCH 1373/2155] udev-builtin-blkid: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udev-builtin-blkid.c | 37 ++++++++++++++--------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index ca40b62d782b9..16eaced0dcf46 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -10,7 +10,6 @@ #endif #include -#include #include #include #include @@ -26,6 +25,7 @@ #include "fd-util.h" #include "initrd-util.h" #include "gpt.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -506,13 +506,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { int64_t offset = 0; int r; - static const struct option options[] = { - { "offset", required_argument, NULL, 'o' }, - { "hint", required_argument, NULL, 'H' }, - { "noraid", no_argument, NULL, 'R' }, - {} - }; - r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return log_device_debug_errno(dev, r, "blkid not available: %m"); @@ -522,32 +515,32 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { if (!pr) return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to create blkid prober: %m"); - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-blkid" }; - option = getopt_long(argc, argv, "o:H:R", options, NULL); - if (option == -1) - break; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_NAMESPACE("udev-builtin-blkid"): {} - switch (option) { - case 'H': + OPTION('H', "hint", "HINT", NULL): errno = 0; - r = sym_blkid_probe_set_hint(pr, optarg, 0); + r = sym_blkid_probe_set_hint(pr, opts.arg, 0); if (r < 0) - return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", optarg); + return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", opts.arg); break; - case 'o': - r = safe_atoi64(optarg, &offset); + + OPTION('o', "offset", "OFFSET", NULL): + r = safe_atoi64(opts.arg, &offset); if (r < 0) - return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", optarg); + return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", opts.arg); if (offset < 0) return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid offset %"PRIi64".", offset); break; - case 'R': + + OPTION('R', "noraid", NULL, NULL): noraid = true; break; } - } r = sd_device_get_devname(dev, &devnode); if (r < 0) From ab9acf8c8cf75906ebe0827c200f5bbd31f28580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:20:58 +0200 Subject: [PATCH 1374/2155] shared/options: add new helper option_parser_get_arg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit option_parser_next_arg() is renamed to option_parser_peek_next_arg() to match option_parser_consume_next_arg(). A new helper is added option_parser_get_arg(…, n). It is a common pattern to only need a single arg, and getting an array and extracting a single item from it is too verbose. --- src/cryptenroll/cryptenroll.c | 13 +++++-------- src/growfs/growfs.c | 3 +-- src/nspawn/nspawn.c | 2 +- src/shared/options.c | 12 ++++++++++-- src/shared/options.h | 8 +++++++- src/test/test-options.c | 19 ++++++++++++------- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 5d0c782689392..f7e7ff121804a 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -31,7 +31,6 @@ #include "process-util.h" #include "string-table.h" #include "string-util.h" -#include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" @@ -579,14 +578,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&opts); + if (option_parser_get_n_args(&opts) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - if (strv_length(args) > 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Too many arguments, refusing."); - - if (args[0]) - r = parse_path_argument(args[0], false, &arg_node); + const char *arg = option_parser_get_arg(&opts, 0); + if (arg) + r = parse_path_argument(arg, false, &arg_node); else if (!wipe_requested()) r = determine_default_node(); else diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 3e9eb678bf038..efb94e3765053 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -181,8 +181,7 @@ static int parse_argv(int argc, char *argv[]) { "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = option_parser_get_args(&opts)[0]; - + arg_target = option_parser_get_arg(&opts, 0); return 1; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e4e0359ce6d79..6c9c1050c6921 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1420,7 +1420,7 @@ static int parse_argv(int argc, char *argv[]) { * the old container user functionality. To maintain backwards compatibility * with the space-separated form (--user NAME), if the next opts.arg does not look * like an option, interpret it as a user name. */ - const char *t = option_parser_next_arg(&opts); + const char *t = option_parser_peek_next_arg(&opts); if (t && t[0] != '-') { opts.arg = option_parser_consume_next_arg(&opts); log_warning("--user NAME is deprecated, use --uid=NAME instead."); diff --git a/src/shared/options.c b/src/shared/options.c index 01684fc1fced5..85ab3155bf867 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -344,7 +344,7 @@ int option_parse( return r; } -char* option_parser_next_arg(const OptionParser *state) { +char* option_parser_peek_next_arg(const OptionParser *state) { /* Peek at the next argument, whatever it is (option or position arg). * May return NULL. */ @@ -360,7 +360,7 @@ char* option_parser_consume_next_arg(OptionParser *state) { * so we won't try to interpret it as an option. * May return NULL. */ - char *t = option_parser_next_arg(state); + char *t = option_parser_peek_next_arg(state); if (t) shift_arg(state->argv, state->positional_offset++, state->optind++); return t; @@ -388,6 +388,14 @@ size_t option_parser_get_n_args(const OptionParser *state) { return state->argc - state->positional_offset; } +char* option_parser_get_arg(const OptionParser *state, size_t i) { + assert(state->optind > 0); + assert(state->state == OPTION_PARSER_DONE); + assert(state->positional_offset <= state->argc); + + return (size_t) (state->argc - state->positional_offset) > i ? state->argv[state->positional_offset + i] : NULL; +} + char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar) { assert(opt); assert(!(opt->flags & (OPTION_NAMESPACE_MARKER | diff --git a/src/shared/options.h b/src/shared/options.h index a171f5f6a43f7..5803eb120ef67 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -224,11 +224,17 @@ int option_parse( break; \ } else -char* option_parser_next_arg(const OptionParser *state); +/* Those helpers are used *during* option parsing and allow looking at or taking the next item in + * the argv array, either an option or a positional parameter. */ +char* option_parser_peek_next_arg(const OptionParser *state); char* option_parser_consume_next_arg(OptionParser *state); +/* Those helpers are used *after* option parsing and return the positional arguments (and unparsed + * options in case option parsing was stopped early, e.g. via "--"). */ char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); +char* option_parser_get_arg(const OptionParser *state, size_t i); + char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar); int _option_parser_get_help_table_full( diff --git a/src/test/test-options.c b/src/test/test-options.c index 04fddc1c34700..efa3a73d69edd 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -58,7 +58,12 @@ 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(&opts), strv_length(remaining)); + size_t l = strv_length(remaining); + ASSERT_EQ(option_parser_get_n_args(&opts), l); + ASSERT_STREQ(option_parser_get_arg(&opts, 0), l > 0 ? remaining[0] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 1), l > 1 ? remaining[1] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 2), l > 2 ? remaining[2] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 3), l > 3 ? remaining[3] : NULL); } static void test_option_invalid_one( @@ -1331,7 +1336,7 @@ TEST(option_optional_arg_consume) { /* --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. */ + * use option_parser_peek_next_arg/consume_next_arg to grab it. */ { char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); int argc = strv_length(argv); @@ -1341,7 +1346,7 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_STREQ(option_parser_next_arg(&opts), "someuser"); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "someuser"); ASSERT_STREQ(option_parser_consume_next_arg(&opts), "someuser"); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1361,7 +1366,7 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); ASSERT_NULL(option_parser_consume_next_arg(&opts)); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1381,12 +1386,12 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_STREQ(option_parser_next_arg(&opts), "-u"); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "-u"); ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "uid"); ASSERT_STREQ(opts.arg, "nobody"); - ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); ASSERT_NULL(option_parser_consume_next_arg(&opts)); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1413,7 +1418,7 @@ TEST(option_optional_arg_consume) { const char *arg = opts.arg; if (!arg) { - const char *t = option_parser_next_arg(&opts); + const char *t = option_parser_peek_next_arg(&opts); if (t && t[0] != '-') arg = option_parser_consume_next_arg(&opts); } From fcfd42a30c98743935b822f57a38a35bce060cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 13:24:40 +0200 Subject: [PATCH 1375/2155] udev-builtin-hwdb: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udev-builtin-hwdb.c | 42 +++++++++++++++--------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 082af2e6031bd..4817c3af24e20 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-hwdb.h" @@ -9,6 +8,7 @@ #include "alloc-util.h" #include "device-util.h" #include "hwdb-util.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" #include "udev-builtin.h" @@ -128,13 +128,6 @@ static int udev_builtin_hwdb_search( } static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { - static const struct option options[] = { - { "filter", required_argument, NULL, 'f' }, - { "device", required_argument, NULL, 'd' }, - { "subsystem", required_argument, NULL, 's' }, - { "lookup-prefix", required_argument, NULL, 'p' }, - {} - }; const char *filter = NULL, *device = NULL, *subsystem = NULL, *prefix = NULL; _cleanup_(sd_device_unrefp) sd_device *srcdev = NULL; sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); @@ -143,35 +136,34 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { if (!hwdb) return -EINVAL; - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-hwdb" }; - option = getopt_long(argc, argv, "f:d:s:p:", options, NULL); - if (option == -1) - break; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_NAMESPACE("udev-builtin-hwdb"): {} - switch (option) { - case 'f': - filter = optarg; + OPTION('f', "filter", "FILTER", NULL): + filter = opts.arg; break; - case 'd': - device = optarg; + OPTION('d', "device", "DEVICE", NULL): + device = opts.arg; break; - case 's': - subsystem = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", NULL): + subsystem = opts.arg; break; - case 'p': - prefix = optarg; + OPTION('p', "lookup-prefix", "PREFIX", NULL): + prefix = opts.arg; break; } - } /* query a specific key given as argument */ - if (argv[optind]) { - r = udev_builtin_hwdb_lookup(event, prefix, argv[optind], filter); + char *modalias = option_parser_get_arg(&opts, 0); + if (modalias) { + r = udev_builtin_hwdb_lookup(event, prefix, modalias, filter); if (r < 0) return log_device_debug_errno(dev, r, "Failed to look up hwdb: %m"); if (r == 0) From 13f6feda78664ef55cc92cf2c71e83d43a78b23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 14:52:26 +0200 Subject: [PATCH 1376/2155] udevadm: convert verb dispatch to VERB macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm.c | 79 ++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 23e03d6fb0e64..856d1fc4c23e2 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -14,33 +14,26 @@ #include "verbs.h" static int help(void) { - static const char *const short_descriptions[][2] = { - { "info", "Query sysfs or the udev database" }, - { "trigger", "Request events from the kernel" }, - { "settle", "Wait for pending udev events" }, - { "control", "Control the udev daemon" }, - { "monitor", "Listen to kernel and udev events" }, - { "test", "Test an event run" }, - { "test-builtin", "Test a built-in command" }, - { "verify", "Verify udev rules files" }, - { "cat", "Show udev rules files" }, - { "wait", "Wait for device or device symlink" }, - { "lock", "Lock a block device" }, - }; - - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table_ns("udevadm", &options); if (r < 0) return r; + (void) table_sync_column_widths(0, verbs, options); + help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); help_abstract("Send control commands or test the device manager."); help_section("Commands:"); - FOREACH_ELEMENT(desc, short_descriptions) - printf(" %-12s %s\n", (*desc)[0], (*desc)[1]); + r = table_print_or_warn(verbs); + if (r < 0) + return r; help_section("Options:"); r = table_print_or_warn(options); @@ -51,6 +44,26 @@ static int help(void) { return 0; } +VERB_COMMON_HELP(help); + +VERB_SCOPE(, verb_info_main, "info", "[DEVPATH|FILE]", VERB_ANY, VERB_ANY, 0, "Query sysfs or the udev database"); +VERB_SCOPE(, verb_trigger_main, "trigger", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Request events from the kernel"); +VERB_SCOPE(, verb_settle_main, "settle", NULL, VERB_ANY, VERB_ANY, 0, "Wait for pending udev events"); +VERB_SCOPE(, verb_control_main, "control", "OPTION", VERB_ANY, VERB_ANY, 0, "Control the udev daemon"); +VERB_SCOPE(, verb_monitor_main, "monitor", NULL, VERB_ANY, VERB_ANY, 0, "Listen to kernel and udev events"); +VERB_SCOPE(, verb_test_main, "test", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Test an event run"); +VERB_SCOPE(, verb_builtin_main, "test-builtin", "COMMAND DEVPATH", VERB_ANY, VERB_ANY, 0, "Test a built-in command"); +VERB_SCOPE(, verb_verify_main, "verify", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Verify udev rules files"); +VERB_SCOPE(, verb_cat_main, "cat", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Show udev rules files"); +VERB_SCOPE(, verb_wait_main, "wait", "DEVICE [DEVICE…]", VERB_ANY, VERB_ANY, 0, "Wait for device or device symlink"); +VERB_SCOPE(, verb_lock_main, "lock", "[OPTIONS…] COMMAND", VERB_ANY, VERB_ANY, 0, "Lock a block device"); +VERB_SCOPE(, verb_hwdb_main, "hwdb", NULL, VERB_ANY, VERB_ANY, 0, /* help= */ NULL); /* deprecated */ + +VERB_NOARG(verb_version_main, "version", /* help= */ NULL); +static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + return print_version(); +} + static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); @@ -84,36 +97,6 @@ int print_version(void) { return 0; } -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[], uintptr_t _data, void *userdata) { - return help(); -} - -static int udevadm_main(char **args) { - static const Verb verbs[] = { - { "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 }, - {} - }; - - return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); -} - static int run(int argc, char *argv[]) { char **args = NULL; int r; @@ -132,7 +115,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return udevadm_main(args); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 32a291abe9d22efcbb7f613b41d90c68d7c315b2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 15:52:29 +0200 Subject: [PATCH 1377/2155] sd-boot: minor tweaks as follow-up for #41863 This addresses some trivial points made by @keszybz in the PR review. --- src/boot/boot.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index fff35de864b8c..a2a1becc9aaa0 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2764,12 +2764,11 @@ static EFI_STATUS load_extras( } const struct ExtraResourceInfo *x = NULL; - FOREACH_ELEMENT(j, table) { + FOREACH_ELEMENT(j, table) if (endswith_no_case(info->FileName, j->suffix)) { x = j; break; } - } if (!x) { log_warning("Unrecognized type of extra file '%ls', ignoring.", info->FileName); continue; @@ -3034,7 +3033,7 @@ static EFI_STATUS call_image_start( case LOADER_UKI: case LOADER_UKI_URL: /* For modern UKIs we'll not bother with 'initrd', but we'll instead support 'extra' - * for loading credentials, sysext and confext. */ + * for loading credentials, sysexts, and confexts. */ err = load_extras(image_root, entry, &initrd_pages, &initrd_size); if (err != EFI_SUCCESS) From fd035af9bf9611aee6c4ded0d5f485ea95acc703 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 16:05:36 +0200 Subject: [PATCH 1378/2155] update TODO This is mostly stuff discussed in #41776. --- TODO.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/TODO.md b/TODO.md index 304f6c3a5ecb8..60a11dcee3ad1 100644 --- a/TODO.md +++ b/TODO.md @@ -132,6 +132,16 @@ SPDX-License-Identifier: LGPL-2.1-or-later - hook-up in systemd-nspawn - hook-up in systemd-vmspawn - hook-up in service manager (BindVolume=) + - introduce a locking concept: right now all access to volumes is fully + shared. Let's add a basic locking concept: supporting backends can take an + additional locking flag (which has to be combined with Varlink's "more"), + in which case access would only be handed out to one client at a time, with + the lock's lifetime synced up with the Varlink connection lifetime. + - introduce a volume lifecycle concept: optionally support volumes whose + whole lifecycle is associated with the varlink connections they are tied + to: when the last varlink connection that acquired them goes away, the + volume is auto-destroyed. Would be exposed via a new flag on the Acquire + call, similar to the locking logic above. - 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, From 82614a4c6f19f0902b6fefc2988ccc54edb47eb5 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 29 Apr 2026 13:48:49 +0200 Subject: [PATCH 1379/2155] sd-json,user-record: store the strv size when extending it So strv_push_with_size() doesn't have to recalculate the size every time. --- src/libsystemd/sd-json/json-util.c | 3 ++- src/libsystemd/sd-json/sd-json.c | 3 ++- src/shared/user-record.c | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index c321579ef5093..27306409fe7c4 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -291,6 +291,7 @@ int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispa int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { _cleanup_strv_free_ char **n = NULL; char ***l = ASSERT_PTR(userdata); + size_t s = 0; int r; assert(variant); @@ -310,7 +311,7 @@ int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_ if (r < 0) return r; - r = strv_extend(&n, a); + r = strv_extend_with_size(&n, &s, a); if (r < 0) return json_log_oom(variant, flags); } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index fbc2e55d23f22..659dffb2bac7e 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -5661,6 +5661,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s _cleanup_strv_free_ char **l = NULL; char ***s = userdata; sd_json_variant *e; + size_t n = 0; int r; assert_return(variant, -EINVAL); @@ -5694,7 +5695,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s 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)); + r = strv_extend_with_size(&l, &n, sd_json_variant_string(e)); if (r < 0) return json_log(e, flags, r, "Failed to append array element: %m"); } diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 4dfb2c72d70f0..cf33d92215b8d 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -518,6 +518,7 @@ static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_ char ***l = userdata; const char *locale; sd_json_variant *e; + size_t s = 0; int r; if (sd_json_variant_is_null(variant)) { @@ -536,7 +537,7 @@ static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_ if (!locale_is_valid(locale)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name)); - r = strv_extend(&n, locale); + r = strv_extend_with_size(&n, &s, locale); if (r < 0) return json_log_oom(variant, flags); } @@ -593,6 +594,7 @@ static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_j int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { char ***list = ASSERT_PTR(userdata); _cleanup_strv_free_ char **l = NULL; + size_t s = 0; int r; if (!sd_json_variant_is_array(variant)) @@ -606,7 +608,7 @@ int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd if (!valid_user_group_name(sd_json_variant_string(e), FLAGS_SET(flags, SD_JSON_RELAX) ? VALID_USER_RELAX : 0)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", sd_json_variant_string(e)); - r = strv_extend(&l, sd_json_variant_string(e)); + r = strv_extend_with_size(&l, &s, sd_json_variant_string(e)); if (r < 0) return json_log(e, flags, r, "Failed to append array element: %m"); } From 994f016a7fb621d782500f54cd3b3b2a06d0d9a4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 29 Apr 2026 18:28:16 +0200 Subject: [PATCH 1380/2155] blockdev-list: fix per-element leak in block_device_array_free() (#41869) FOREACH_ARRAY declares 'i' as the iterator but the body passed 'd' (the array base) to block_device_done(). Since mfree() leaves the field NULL after the first call, element 0 is freed repeatedly while elements 1..N-1 leak their node, symlinks strv, model, vendor and subsystem. The bug predates the sanitizer-instrumented callers. PR #41776's new systemd-storage-block daemon runs blockdev_list() under ASan/LSan in TEST-87-AUX-UTILS-VM and exposes it (15 allocs / 804 bytes leaked per ListVolumes request). The fix also benefits repart and blockdev_list's internal CLEANUP_ARRAY cleanup. Follow-up for 9f6b2745eaa15be80568fde2a44d0a10ed6eb2a1 --- src/shared/blockdev-list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index 5b11c8169477f..0efc90fd54696 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -27,7 +27,7 @@ void block_device_done(BlockDevice *d) { void block_device_array_free(BlockDevice *d, size_t n_devices) { FOREACH_ARRAY(i, d, n_devices) - block_device_done(d); + block_device_done(i); free(d); } From 33ac56f46230f2c425c16eb6297b979f1bb91228 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 29 Apr 2026 15:36:32 +0100 Subject: [PATCH 1381/2155] man: add section about systemd-boot Type#1 sidecars Follow-up for 6b1324fb867d89147585ee20160dbe8f37beefc8 Co-developed-by: Claude Opus 4.7 --- man/systemd-boot.xml | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index dab10ed8ef12a..1acf5d083e580 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -406,6 +406,66 @@ loader.conf5. + + Companion Files + + For Type #1 boot loader entries (as defined in the UAPI.1 Boot Loader + Specification) systemd-boot will collect additional companion resources + declared via the extra key in the entry, dynamically generate + cpio initrd archives from them, and register those archives via the Linux initrd EFI + protocol so that they are passed to the kernel together with the entry's own initrd. This is supported + for entries referencing a Unified Kernel Image (UKI) via the uki or + uki-url keys. Each extra key references a single regular file + (relative to the root of the file system containing the entry snippet) and the key may be specified + multiple times. Companion resources are recognized by file name suffix: + + + Files with the .cred suffix are packed into a + cpio archive placed in the /.extra/credentials/ directory of + the initrd file hierarchy. This is intended to convey auxiliary, encrypted, authenticated credentials + for use with LoadCredentialEncrypted=. See + systemd.exec5 and + systemd-creds1 for + details on encrypted credentials. The generated cpio archive is measured into TPM + PCR 12 (if a TPM is present). + + Files with the .sysext.raw suffix are packed into a + cpio archive placed in the /.extra/sysext/ directory of the + initrd file hierarchy. This is intended to pass additional entry-specific system extension images to + the initrd. See + systemd-sysext8 for + details on system extension images. The generated cpio archive is measured into TPM + PCR 13 (if a TPM is present). + + Files with the .confext.raw suffix are packed into a + cpio archive placed in the /.extra/confext/ directory of the + initrd file hierarchy. This is intended to pass additional entry-specific configuration extension + images to the initrd. See + systemd-confext8 + for details on configuration extension images. The generated cpio archive is + measured into TPM PCR 12 (if a TPM is present). + + + When the booted kernel is a UKI, the systemd-stub UEFI stub embedded in it will + combine the companion resources injected here with any companion files it itself collects from the UKI's + .extra.d/ drop-in directory and from /loader/credentials/ and + /loader/extensions/, so that all sources are merged uniformly into + /.extra/ in the initrd. See + systemd-stub7 for + details. + + Example Type #1 entry making use of the extra key: + + title My OS +version 1.2.3 +machine-id 6a9857a393724b7a981ebb5b8495b9ea +uki /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/img.efi +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/foo.cred +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/bar.sysext.raw +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/baz.confext.raw + + EFI Variables From 3f2189ca2544cce99c2aa7a35881007830dc221a Mon Sep 17 00:00:00 2001 From: Samuel Dainard Date: Tue, 28 Apr 2026 15:57:26 +0000 Subject: [PATCH 1382/2155] binfmt-util: handle ELOOP/EACCES from automount in read-only bind mounts When /proc is bind-mounted read-only (common in mock/Koji buildroots, containers, and other sandboxed environments), opening /proc/sys/fs/binfmt_misc returns ELOOP if it is an automount point that cannot be triggered in the read-only context. Currently binfmt_mounted_and_writable() only handles ENOENT, so ELOOP propagates as an error. This causes test-binfmt-util to fail with SIGABRT and disable_binfmt() to log a spurious warning at shutdown. Treat ELOOP and EACCES the same as ENOENT: binfmt_misc is not usably available, return false. Note: PR #37006 (merged April 2025) addressed ELOOP in the xstatfsat() path, but the open() call in binfmt_mounted_and_writable() remained unhandled. Fixes #38070 --- src/shared/binfmt-util.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/binfmt-util.c b/src/shared/binfmt-util.c index d21fd10136fb4..0faca5966341c 100644 --- a/src/shared/binfmt-util.c +++ b/src/shared/binfmt-util.c @@ -18,6 +18,12 @@ int binfmt_mounted_and_writable(void) { fd = RET_NERRNO(open("/proc/sys/fs/binfmt_misc", O_CLOEXEC | O_DIRECTORY | O_PATH)); if (fd == -ENOENT) return false; + /* ELOOP happens when binfmt_misc is an automount point under a read-only bind mount of /proc — + * the kernel cannot trigger the automount and returns ELOOP instead. Common in mock/Koji buildroots. */ + if (fd == -ELOOP || ERRNO_IS_NEG_PRIVILEGE(fd)) { + log_debug_errno(fd, "Failed to open /proc/sys/fs/binfmt_misc, ignoring: %m"); + return false; + } if (fd < 0) return fd; From a69f0b8b28d06786581d21281665a89a4318c309 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 18:47:48 +0200 Subject: [PATCH 1383/2155] repart: hide read-only block devices from candidates If they are read-only they are no candidates, since we cannot write to them. --- src/repart/repart.c | 9 ++++++++- src/shared/blockdev-list.c | 21 ++++++++++++++------- src/shared/blockdev-list.h | 1 + 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 2d35da28c2b12..b82827f869ee3 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9896,7 +9896,13 @@ static int parse_argv(int argc, char *argv[]) { 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); + r = blockdev_list( + BLOCKDEV_LIST_SHOW_SYMLINKS| + BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING| + BLOCKDEV_LIST_IGNORE_ZRAM| + BLOCKDEV_LIST_IGNORE_READ_ONLY, + /* ret_devices= */ NULL, + /* ret_n_devices= */ NULL); if (r < 0) return r; @@ -10876,6 +10882,7 @@ static int vl_method_list_candidate_devices( BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING| BLOCKDEV_LIST_IGNORE_ZRAM| BLOCKDEV_LIST_METADATA| + BLOCKDEV_LIST_IGNORE_READ_ONLY| (p.ignore_empty ? BLOCKDEV_LIST_IGNORE_EMPTY : 0)| (p.ignore_root ? BLOCKDEV_LIST_IGNORE_ROOT : 0), &l, diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index 0efc90fd54696..181afb42890f5 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -188,6 +188,20 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re } } + int ro = -1; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY) || FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) { + 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 (ro > 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY)) { + log_device_debug(dev, "Device '%s' is read-only, skipping.", node); + continue; + } + } + _cleanup_strv_free_ char **list = NULL; if (FLAGS_SET(flags, BLOCKDEV_LIST_SHOW_SYMLINKS)) { FOREACH_DEVICE_DEVLINK(dev, sl) @@ -198,17 +212,10 @@ 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) { diff --git a/src/shared/blockdev-list.h b/src/shared/blockdev-list.h index d82345435f7e2..67f8efba97187 100644 --- a/src/shared/blockdev-list.h +++ b/src/shared/blockdev-list.h @@ -11,6 +11,7 @@ typedef enum BlockDevListFlags { 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, read_only */ + BLOCKDEV_LIST_IGNORE_READ_ONLY = 1 << 7, /* Ignore read-only block devices */ } BlockDevListFlags; typedef struct BlockDevice { From cd7aceeaa31e3890d02e773fb1e68769abdf5809 Mon Sep 17 00:00:00 2001 From: Dan Anderson Date: Wed, 29 Apr 2026 22:53:10 -0400 Subject: [PATCH 1384/2155] Improve error logging for fstat failure Small hygiene fix. r must be >= 0 as per the prior statement (otherwise we would have returned). This is really only going to be r == 0, which means return r; is return 0; I'm updating this to use log_debug_errno --- 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 54a5203da2cc6..9f469d6061fde 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -1362,7 +1362,7 @@ static int vl_method_make_directory( struct stat parent_stat; if (fstat(parent_fd, &parent_stat) < 0) - return r; + return log_debug_errno(errno, "Failed to fstat parent directory fd: %m"); r = stat_verify_directory(&parent_stat); if (r < 0) From 9d2f5b4611a47b9e5a31296cea70c2d8c6c86bbb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 27 Apr 2026 18:03:51 +0000 Subject: [PATCH 1385/2155] fundamental/cleanup: add CLEANUP_ELEMENTS() and DEFINE_POINTER_ARRAY_CLEAR_FUNC() DEFINE_POINTER_ARRAY_CLEAR_FUNC() generates a helper of the form helper_array_clear(T *array, size_t n) that drops each element but does not free the array itself, parallel to DEFINE_POINTER_ARRAY_FREE_FUNC() for cases where the array has automatic storage duration. CLEANUP_ELEMENTS() pairs with these helpers to provide a _cleanup_-like attribute for fixed-size arrays: the bound is taken from ELEMENTSOF(), and the helper is invoked across the elements at scope exit. Compared to CLEANUP_ARRAY(), the storage is neither freed nor zeroed. Migrate various logic across the tree over to the new macros. sd-device: use DEFINE_POINTER_ARRAY_CLEAR_FUNC() for sd_device_unref_array_clear() Replace the local device_unref_many() helper with the macro-generated equivalent. format-table: switch help-table arrays to CLEANUP_ELEMENTS() Generate table_unref_array_clear() via DEFINE_POINTER_ARRAY_CLEAR_FUNC() and convert the help-table arrays in bootctl, cryptenroll, nspawn, repart and vmspawn to CLEANUP_ELEMENTS(). The arrays no longer need a trailing NULL slot, so the size matches ELEMENTSOF() of the groups array. firewall-util: switch netlink message arrays to CLEANUP_ELEMENTS() Generate sd_netlink_message_unref_array_clear() via DEFINE_POINTER_ARRAY_CLEAR_FUNC() in place of the NULL-terminated sd_netlink_message_unref_many(), and convert the two stack arrays of sd_netlink_message pointers to CLEANUP_ELEMENTS(). --- src/bootctl/bootctl.c | 6 ++- src/cryptenroll/cryptenroll.c | 3 +- src/fundamental/cleanup-fundamental.h | 39 ++++++++++++++++++++ src/libsystemd/sd-device/device-enumerator.c | 13 ++----- src/nspawn/nspawn.c | 3 +- src/repart/repart.c | 3 +- src/run/run.c | 3 +- src/shared/firewall-util.c | 8 ++-- src/shared/format-table.h | 2 +- src/vmspawn/vmspawn.c | 3 +- 10 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 04213dc8e17aa..967c21458d9ee 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -298,8 +298,10 @@ static int help(void) { "Options", }; - _cleanup_(table_unref_many) Table *verb_tables[ELEMENTSOF(verb_groups) + 1] = {}; - _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + Table *verb_tables[ELEMENTSOF(verb_groups)] = {}; + CLEANUP_ELEMENTS(verb_tables, table_unref_array_clear); + Table *option_tables[ELEMENTSOF(option_groups)] = {}; + CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { r = verbs_get_help_table_group(verb_groups[i], &verb_tables[i]); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index f7e7ff121804a..6561d86107843 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -241,7 +241,8 @@ static int help(void) { "TPM2 Enrollment", }; - _cleanup_(table_unref_many) Table *tables[ELEMENTSOF(groups) + 1] = {}; + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_group(groups[i], &tables[i]); diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index 9094cff2331e0..8d499e5c3498b 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -64,6 +64,15 @@ free(array); \ } +/* Like DEFINE_POINTER_ARRAY_FREE_FUNC() but does not deallocate the array itself, useful for + * arrays with automatic storage duration (e.g. on the stack). */ +#define DEFINE_POINTER_ARRAY_CLEAR_FUNC(type, helper) \ + void helper ## _array_clear(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + *item = helper(*item); \ + } + /* 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) \ @@ -108,3 +117,33 @@ static inline void array_cleanup(const ArrayCleanup *c) { _f; \ }), \ } + +/* An automatic _cleanup_-like logic for fixed-size arrays where the bound is known via + * ELEMENTSOF(). Unlike CLEANUP_ARRAY() this neither frees the storage nor zeroes it: it just + * invokes func() across the elements when leaving scope. */ +typedef struct ElementsCleanup { + void *array; + size_t n; + free_array_func_t pfunc; +} ElementsCleanup; + +static inline void elements_cleanup(const ElementsCleanup *c) { + assert(c); + + if (c->n == 0) + return; + + assert(c->array); + assert(c->pfunc); + c->pfunc(c->array, c->n); +} + +#define CLEANUP_ELEMENTS(_array, _func) \ + _cleanup_(elements_cleanup) _unused_ const ElementsCleanup CONCATENATE(_cleanup_elements_, UNIQ) = { \ + .array = (_array), \ + .n = ELEMENTSOF(_array), \ + .pfunc = (free_array_func_t) ({ \ + void (*_f)(typeof((_array)[0]) *a, size_t b) = _func; \ + _f; \ + }), \ + } diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c index b3fe85a976167..d1a48defe906c 100644 --- a/src/libsystemd/sd-device/device-enumerator.c +++ b/src/libsystemd/sd-device/device-enumerator.c @@ -82,18 +82,13 @@ _public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { return 0; } -static void device_unref_many(sd_device **devices, size_t n) { - assert(devices || n == 0); - - for (size_t i = 0; i < n; i++) - sd_device_unref(devices[i]); -} +static DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_device*, sd_device_unref); static void device_enumerator_unref_devices(sd_device_enumerator *enumerator) { assert(enumerator); hashmap_clear(enumerator->devices_by_syspath); - device_unref_many(enumerator->devices, enumerator->n_devices); + sd_device_unref_array_clear(enumerator->devices, enumerator->n_devices); enumerator->devices = mfree(enumerator->devices); enumerator->n_devices = 0; } @@ -461,7 +456,7 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { typesafe_qsort(devices + n_sorted, n - n_sorted, device_compare); - device_unref_many(enumerator->devices, enumerator->n_devices); + sd_device_unref_array_clear(enumerator->devices, enumerator->n_devices); enumerator->n_devices = n; free_and_replace(enumerator->devices, devices); @@ -470,7 +465,7 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { return 0; failed: - device_unref_many(devices, n); + sd_device_unref_array_clear(devices, n); free(devices); return r; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 6c9c1050c6921..f96a6b08b981c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -405,7 +405,8 @@ static int help(void) { "Other", }; - _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_group(groups[i], &tables[i]); diff --git a/src/repart/repart.c b/src/repart/repart.c index b82827f869ee3..26588c6242b4d 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9648,7 +9648,8 @@ static int help(void) { "El Torito boot catalog", }; - _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + Table *option_tables[ELEMENTSOF(option_groups)] = {}; + CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); diff --git a/src/run/run.c b/src/run/run.c index 5827d91e1f9e4..9d1042e845a33 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -151,7 +151,8 @@ static int help(void) { "Timer options", }; - _cleanup_(table_unref_many) Table *tables[ELEMENTSOF(groups) + 1] = {}; + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_full("systemd-run", groups[i], &tables[i]); diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 651870e369889..4693972ff2752 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -50,7 +50,7 @@ static const char* dnat_map_name(void) { return cached; } -static DEFINE_ARRAY_DONE_FUNC(sd_netlink_message*, sd_netlink_message_unref); +static DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_netlink_message*, sd_netlink_message_unref); static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { int r; @@ -724,7 +724,8 @@ 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) { - _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[10] = {}; + sd_netlink_message *messages[10] = {}; + CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear); size_t msgcnt = 0, ip_type_size; uint32_t set_id = 0; int ip_type, r; @@ -1045,7 +1046,8 @@ static int fw_nftables_add_local_dnat_internal( uint16_t remote_port, const union in_addr_union *previous_remote) { - _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[3] = {}; + sd_netlink_message *messages[3] = {}; + CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear); uint32_t data[5], key[2], dlen; size_t msgcnt = 0; int r; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 5b98d49017524..ba4d33cfc6719 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -101,7 +101,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); +static inline DEFINE_POINTER_ARRAY_CLEAR_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 14df0fc989f65..8e4cbf3e80611 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -235,7 +235,8 @@ static int help(void) { "Credentials", }; - _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_group(groups[i], &tables[i]); From 1c534452e961d4663f96b046f6242f00394f8e75 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 29 Apr 2026 19:18:17 +0200 Subject: [PATCH 1386/2155] dns-question: limit the number of questions per query Let's cap the number of question each query can have to something reasonable - 128 questions per query should be more than enough for any real-world scenario. --- src/shared/dns-question.c | 3 +++ src/shared/dns-question.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/shared/dns-question.c b/src/shared/dns-question.c index ac4cc8e998007..28840d64b948a 100644 --- a/src/shared/dns-question.c +++ b/src/shared/dns-question.c @@ -608,6 +608,9 @@ int dns_json_dispatch_question(const char *name, sd_json_variant *variant, sd_js if (!sd_json_variant_is_array(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + if (sd_json_variant_elements(variant) > DNS_QUESTION_ITEMS_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(E2BIG), "Too many questions in a single query."); + _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; nq = dns_question_new(sd_json_variant_elements(variant)); if (!nq) diff --git a/src/shared/dns-question.h b/src/shared/dns-question.h index 4b0fc68fd648c..85de7ad06d8d7 100644 --- a/src/shared/dns-question.h +++ b/src/shared/dns-question.h @@ -5,6 +5,8 @@ #include "shared-forward.h" +#define DNS_QUESTION_ITEMS_MAX 128U + /* A simple array of resource keys */ typedef enum DnsQuestionFlags { From 7671b43cb88532cce2aa9ad12f777922206d6a42 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 29 Apr 2026 16:50:57 +0200 Subject: [PATCH 1387/2155] sd-json: limit the number of env variables to something reasonable Let's start with 1024, as that should be plenty for all sane use cases. --- src/libsystemd/sd-json/json-util.c | 3 +++ src/libsystemd/sd-json/json-util.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 27306409fe7c4..40102a69989ed 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -653,6 +653,9 @@ int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, s if (!sd_json_variant_is_array(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + if (sd_json_variant_elements(variant) > ENVIRONMENT_ASSIGNMENTS_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(E2BIG), "Too many environment variable assignments."); + sd_json_variant *i; JSON_VARIANT_ARRAY_FOREACH(i, variant) { const char *e; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index cea2d368b43db..34d79d5238aaa 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -9,6 +9,8 @@ #include "sd-forward.h" #include "string-util.h" /* IWYU pragma: keep */ +#define ENVIRONMENT_ASSIGNMENTS_MAX 1024U + #define JSON_VARIANT_REPLACE(v, q) \ do { \ typeof(v)* _v = &(v); \ From e5687f689f20b051420fe0154ea4391af697ed7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 23:36:15 +0200 Subject: [PATCH 1388/2155] report: absorb "facts" into "metrics" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gets rid of the duality in the cmdline interface and various APIs. The general plan is collect both "facts" and "metrics" in a single list. We have various producers which respond on the io.systemd.Facts endpoint. Those will need to be adjusted to respond to io.systemd.Metrics. Cmdline interface: 'metrics' (unchanged) 'describe-metrics' → 'describe' 'facts' → merged into 'metrics' 'describe-facts' → merged into 'describe' --- man/systemd-report.xml | 10 +- src/report/report-upload.c | 10 +- src/report/report.c | 225 +----------------- src/report/report.h | 7 +- .../fake-report-server.py | 4 +- test/units/TEST-74-AUX-UTILS.report.sh | 36 +-- 6 files changed, 27 insertions(+), 265 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index b53a50c2f8681..f14600dfe5d32 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -18,7 +18,7 @@ systemd-report - Generate report of system facts and metrics + Generate report of system metrics @@ -33,7 +33,7 @@ Note: this command is experimental for now. While it is likely to become a regular component of systemd, it might still change in behaviour and interface. - systemd-report requests facts and metrics from the system and writes them to + systemd-report requests metrics from the system and writes them to standard output. @@ -56,14 +56,14 @@ - describe-metrics MATCH + describe MATCH Acquire a list of metric families from all local services providing them, and write them to standard output. This returns primarily static information about metrics, their data types and human readable description, without values. - Match expressions similar to those supported by metrics are supported for - describe-metrics, too. + Match expressions supported by metrics are supported by + describe too. diff --git a/src/report/report-upload.c b/src/report/report-upload.c index c64bf86e13336..486e815e8d857 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -59,18 +59,10 @@ static int build_json_report(Context *context, sd_json_variant **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_PAIR("metrics", SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics))); if (r < 0) return log_error_errno(r, "Failed to build JSON data: %m"); diff --git a/src/report/report.c b/src/report/report.c index fef01c094ef9f..feedcaa43a5b8 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -28,8 +28,8 @@ #include "verbs.h" #include "web-util.h" -#define METRICS_OR_FACTS_MAX 4096U -#define METRICS_OR_FACTS_LINKS_MAX 128U +#define METRICS_MAX 4096U +#define METRICS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ static PagerFlags arg_pager_flags = 0; @@ -87,8 +87,6 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( 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); @@ -249,7 +247,7 @@ static int on_query_reply( goto finish; } - if (context->n_metrics >= METRICS_OR_FACTS_MAX) { + if (context->n_metrics >= METRICS_MAX) { context->n_skipped_metrics++; goto finish; } @@ -436,107 +434,6 @@ static int output_collected_describe(Context *context, Table **ret) { return 0; } -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) - 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); - assert(ret); - - _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 output_collected(Context *context) { int r; @@ -555,13 +452,8 @@ static int output_collected(Context *context) { return log_error_errno(r, "Failed to write JSON: %m"); } - 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."); - } - + if (context->n_metrics == 0 && arg_legend) + log_info("No metrics collected."); return 0; } @@ -577,14 +469,6 @@ static int output_collected(Context *context) { r = output_collected_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(); } @@ -598,12 +482,10 @@ static int output_collected(Context *context) { } if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { - const char *type = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS) ? "facts" : "metrics"; - if (table_isempty(table)) - printf("No %s available.\n", type); + printf("No metrics available.\n"); else - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type); + printf("\n%zu metrics listed.\n", table_get_rows(table) - 1); } return 0; @@ -707,7 +589,7 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { 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, +VERB_FULL(verb_metrics, "describe", "[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; @@ -746,7 +628,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_OR_FACTS_LINKS_MAX) { + if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { n_skipped_sources++; break; } @@ -792,93 +674,6 @@ 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; - - 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; - - 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_OR_FACTS_LINKS_MAX) { - n_skipped_sources++; - break; - } - - _cleanup_free_ char *p = path_join(sources_path, d->d_name); - if (!p) - return log_oom(); - - (void) call_collect(&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"); - - if (arg_url) - r = upload_collected(&context); - else - r = output_collected(&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; -} - 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; @@ -955,7 +750,7 @@ static int help(void) { (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sAcquire metrics and facts from local sources.%s\n" + "\n%sAcquire metrics from local sources.%s\n" "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), diff --git a/src/report/report.h b/src/report/report.h index 4d7b5bdd3f0bb..4adb20349514a 100644 --- a/src/report/report.h +++ b/src/report/report.h @@ -16,19 +16,16 @@ 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. */ +/* The structure for collected "metrics". */ typedef struct Context { Action action; sd_event *event; Set *link_infos; - sd_json_variant **metrics; /* Collected metrics or facts for sorting */ + sd_json_variant **metrics; /* Collected metrics for sorting */ size_t n_metrics, n_skipped_metrics, n_invalid_metrics; struct iovec_wrapper upload_answer; } Context; 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 4875a00bada6a..6beb6383c204f 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 @@ -35,8 +35,8 @@ def do_POST(self): 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") + if "metrics" not in data: + self.send_error(400, "Missing 'metrics' field") return response = json.dumps({"status": "ok"}).encode() diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 53b83c4dd9477..73678fcabf1f8 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -16,9 +16,9 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics "$REPORT" metrics -j "$REPORT" metrics --no-legend -"$REPORT" describe-metrics -"$REPORT" describe-metrics -j -"$REPORT" describe-metrics --no-legend +"$REPORT" describe +"$REPORT" describe -j +"$REPORT" describe --no-legend "$REPORT" list-sources "$REPORT" list-sources -j "$REPORT" list-sources --no-legend @@ -26,9 +26,9 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics io "$REPORT" metrics io.systemd piff "$REPORT" metrics piff -"$REPORT" describe-metrics io -"$REPORT" describe-metrics io.systemd piff -"$REPORT" describe-metrics piff +"$REPORT" describe io +"$REPORT" describe io.systemd piff +"$REPORT" describe piff # test io.systemd.CGroup Metrics systemctl start systemd-report-cgroup.socket @@ -46,26 +46,6 @@ varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics # Make sure the service for "system facts" is enabled systemctl start systemd-report-basic.socket -# 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.Basic io.systemd.Facts.List {} -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) @@ -81,7 +61,6 @@ 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" \ @@ -91,6 +70,5 @@ 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" \ +"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ --extra-header='Authorization: Bearer magic string' From 99ce7a0770bd3eb53ebde3e93c35d8c684eb1abb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 30 Apr 2026 08:52:35 +0200 Subject: [PATCH 1389/2155] options: get rid of "on_error" parameter to FOREACH_OPTION I am really not a fan of full code lines passed to macros as parameters. Let's get rid of the 3rd parameter of FOREACH_OPTION() hence: 1. Let's return errors just as a regular value (though a negative one), that can be handled via a OPTION_ERROR case statement for the switch. This normalizes handling of the error, just like any other event returned by the option parser. 2. In order to avoid exploding the amount of boilerplate in each use (that just propagates the error on OPTION_ERROR), let's then introduce an explicit FOREACH_OPTION_OR_RETURN(), that returns from the calling function on its own (and makes that clear in the name). Together this cleans up, normalizes the logic and shortens the code. --- src/ac-power/ac-power.c | 2 +- src/ask-password/ask-password.c | 2 +- src/battery-check/battery-check.c | 2 +- src/binfmt/binfmt.c | 2 +- src/bless-boot/bless-boot.c | 2 +- src/bless-boot/boot-check-no-failures.c | 2 +- src/bootctl/bootctl.c | 2 +- src/cgls/cgls.c | 2 +- src/cgtop/cgtop.c | 2 +- src/core/executor.c | 2 +- src/creds/creds.c | 2 +- src/cryptenroll/cryptenroll.c | 2 +- src/cryptsetup/cryptsetup.c | 2 +- src/delta/delta.c | 2 +- src/detect-virt/detect-virt.c | 2 +- src/dissect/dissect.c | 2 +- src/escape/escape-tool.c | 2 +- src/factory-reset/factory-reset-tool.c | 2 +- src/firstboot/firstboot.c | 2 +- src/growfs/growfs.c | 2 +- src/hibernate-resume/hibernate-resume.c | 2 +- src/hostname/hostnamectl.c | 2 +- src/hwdb/hwdb.c | 2 +- src/id128/id128.c | 2 +- src/imds/imds-tool.c | 2 +- src/imds/imdsd.c | 2 +- src/import/export.c | 2 +- src/import/import-fs.c | 2 +- src/import/import.c | 2 +- src/import/importctl.c | 2 +- src/import/pull.c | 2 +- src/journal-remote/journal-gatewayd.c | 2 +- src/journal-remote/journal-remote-main.c | 2 +- src/journal/bsod.c | 2 +- src/journal/cat.c | 2 +- src/keyutil/keyutil.c | 2 +- src/libsystemd-network/test-ndisc-send.c | 2 +- src/libsystemd/sd-journal/test-journal-append.c | 2 +- src/libudev/test-libudev.c | 2 +- src/login/inhibit.c | 2 +- src/machine-id-setup/machine-id-setup-main.c | 2 +- src/measure/measure-tool.c | 2 +- src/modules-load/modules-load.c | 2 +- src/mute-console/mute-console.c | 2 +- src/network/generator/network-generator-main.c | 2 +- src/notify/notify.c | 2 +- src/nspawn/nspawn.c | 2 +- src/oom/oomctl.c | 2 +- src/oom/oomd.c | 2 +- src/path/path-tool.c | 2 +- src/pcrextend/pcrextend.c | 2 +- src/pcrlock/pcrlock.c | 2 +- src/ptyfwd/ptyfwd-tool.c | 2 +- src/random-seed/random-seed-tool.c | 2 +- src/repart/repart.c | 2 +- src/report/report-basic-server.c | 2 +- src/report/report.c | 2 +- src/run/run.c | 4 ++-- src/sbsign/sbsign.c | 2 +- src/shared/options.h | 14 ++++++++------ src/shutdown/shutdown.c | 2 +- src/sleep/sleep.c | 2 +- src/socket-activate/socket-activate.c | 2 +- src/socket-proxy/socket-proxyd.c | 2 +- src/ssh-generator/ssh-issue.c | 2 +- src/stdio-bridge/stdio-bridge.c | 2 +- src/storage/storage-block.c | 2 +- src/storage/storage-fs.c | 2 +- src/storage/storagectl.c | 2 +- src/storagetm/storagetm.c | 2 +- src/sysctl/sysctl.c | 2 +- src/sysupdate/sysupdate.c | 2 +- src/sysupdate/updatectl.c | 2 +- src/sysusers/sysusers.c | 2 +- src/test/test-chase-manual.c | 2 +- src/test/test-options.c | 5 ++++- src/timedate/timedatectl.c | 2 +- src/tmpfiles/test-offline-passwd.c | 2 +- src/tmpfiles/tmpfiles.c | 2 +- src/tpm2-setup/tpm2-clear.c | 2 +- src/tpm2-setup/tpm2-setup.c | 2 +- .../tty-ask-password-agent.c | 2 +- src/udev/ata_id/ata_id.c | 2 +- src/udev/cdrom_id/cdrom_id.c | 2 +- src/udev/dmi_memory_id/dmi_memory_id.c | 2 +- src/udev/fido_id/fido_id.c | 2 +- src/udev/iocost/iocost.c | 2 +- src/udev/mtd_probe/mtd_probe.c | 2 +- src/udev/scsi_id/scsi_id.c | 4 ++-- src/udev/udev-builtin-blkid.c | 2 +- src/udev/udev-builtin-hwdb.c | 2 +- src/udev/udev-config.c | 2 +- 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 | 2 +- src/udev/v4l_id/v4l_id.c | 2 +- src/update-done/update-done.c | 2 +- src/validatefs/validatefs.c | 2 +- src/varlinkctl/varlinkctl.c | 2 +- src/vmspawn/vmspawn.c | 2 +- src/vpick/vpick-tool.c | 2 +- 111 files changed, 123 insertions(+), 118 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 530ee82ff0665..2a9c517329321 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -43,7 +43,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 129fbf4d7e753..6a1abf5f999a1 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -75,7 +75,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index 3e957d9fa71df..13dc8960f2efb 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -83,7 +83,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index ed37fba276afb..4e24b35db744b 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -141,7 +141,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 8d2fe21a11f66..e0afb3611c278 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -81,7 +81,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/bless-boot/boot-check-no-failures.c b/src/bless-boot/boot-check-no-failures.c index 37b0f7fd6d2b2..9fa42a7ed6620 100644 --- a/src/bless-boot/boot-check-no-failures.c +++ b/src/bless-boot/boot-check-no-failures.c @@ -46,7 +46,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 967c21458d9ee..6869e838cfc4e 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -420,7 +420,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_GROUP("Block Device Discovery Commands"): {} diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index 9ed57c35cdf4f..cdb47ba8bdc57 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -72,7 +72,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index d1d20992bd159..dfee990a0f831 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -722,7 +722,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/core/executor.c b/src/core/executor.c index 20bc65b63e6de..00761c6e3f7a6 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -64,7 +64,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/creds/creds.c b/src/creds/creds.c index e14a9a921cda7..95af91c120db7 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -826,7 +826,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 6561d86107843..bcc4b6cca66cd 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -279,7 +279,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 43f2873da262c..2130c54047c04 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2507,7 +2507,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/delta/delta.c b/src/delta/delta.c index 92b77f9ddce5b..27dfc105ee7d6 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -520,7 +520,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index f88528fccf992..be39634583f2c 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -55,7 +55,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 338aed8391d97..280d1ada5fdbf 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -232,7 +232,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_NO_PAGER: diff --git a/src/escape/escape-tool.c b/src/escape/escape-tool.c index 98f0b9a0146a0..09e0338c348fd 100644 --- a/src/escape/escape-tool.c +++ b/src/escape/escape-tool.c @@ -58,7 +58,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index 975c391fc8fae..e26e948e93416 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -72,7 +72,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 721fbba21e102..3d768b491f83a 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1270,7 +1270,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index efb94e3765053..30d371200d47f 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -162,7 +162,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 5f42097194e18..d2dccd59bda8c 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -57,7 +57,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 9ad2a0b4ec05e..2989840b364d7 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -767,7 +767,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 286ea000dbeec..5ad3bac3211ee 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -83,7 +83,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/id128/id128.c b/src/id128/id128.c index fbcacdbe4608b..ceac8a832e5c1 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -239,7 +239,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index 7752e1f769cfe..06bc6c4487d73 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -84,7 +84,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index c803b27829aba..a0c54ad84d7af 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -2251,7 +2251,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/import/export.c b/src/import/export.c index 5b233e71a5bfd..a77333643c6bc 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -240,7 +240,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 878d0c5b8f14a..513a2c62d3960 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -316,7 +316,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/import/import.c b/src/import/import.c index 43740aeac7d46..798b6b743a21c 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -319,7 +319,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/import/importctl.c b/src/import/importctl.c index 65fff5f5f3a43..d4a6483f36d7c 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1115,7 +1115,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/import/pull.c b/src/import/pull.c index 0cc23dc6ed4b2..6a1f913ff8a5c 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -366,7 +366,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index ffef7edea5ec8..e70fc4f6dbf37 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -1121,7 +1121,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 5709f87f74617..614ec61be907d 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -907,7 +907,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/journal/bsod.c b/src/journal/bsod.c index 1701605590209..e380e08b1c20c 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -250,7 +250,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/journal/cat.c b/src/journal/cat.c index f8b5e0df31727..b2b1689ff26d5 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -62,7 +62,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 474f42fec72ec..2a66fabb19542 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -90,7 +90,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index de04198d370c0..87b8abefd58f0 100644 --- a/src/libsystemd-network/test-ndisc-send.c +++ b/src/libsystemd-network/test-ndisc-send.c @@ -80,7 +80,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_VERSION: diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 75a1fce6fc98c..c71240660dcd7 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -153,7 +153,7 @@ int main(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: { diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index a653f0c6c8fdd..06feb1ffbc61a 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -430,7 +430,7 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 4abfc1c6d3acd..78c784c30fad8 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -203,7 +203,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 9dd389dbaa4f9..2363427b5f54e 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index a92a418f61fce..eeb001f3fed4a 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -139,7 +139,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index f6806d604ab55..0917f800a1a84 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -361,7 +361,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index be6b5fac09166..d5788de09b3b9 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -63,7 +63,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/network/generator/network-generator-main.c b/src/network/generator/network-generator-main.c index 721d36b831945..df9ce9265dbbb 100644 --- a/src/network/generator/network-generator-main.c +++ b/src/network/generator/network-generator-main.c @@ -174,7 +174,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/notify/notify.c b/src/notify/notify.c index 00f915dc7bff1..6c50e4c57c394 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -155,7 +155,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index f96a6b08b981c..efe927f36e9b6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -605,7 +605,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) { + FOREACH_OPTION_OR_RETURN(c, &opts) { switch (c) { OPTION_COMMON_HELP: diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index b73e2eb5abfe5..82ffe0e8379fd 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -93,7 +93,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/oom/oomd.c b/src/oom/oomd.c index 2250d7ec7f189..62eecfc065c65 100644 --- a/src/oom/oomd.c +++ b/src/oom/oomd.c @@ -53,7 +53,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 22544b9463854..29696501d03a0 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -206,7 +206,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 5b846b9d3a9dc..f452363209d66 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -84,7 +84,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 752f67cbdb990..09f49b2ed250e 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5193,7 +5193,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { bool auto_location = true; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index 6d98a8e7ef09e..e7b531c873088 100644 --- a/src/ptyfwd/ptyfwd-tool.c +++ b/src/ptyfwd/ptyfwd-tool.c @@ -65,7 +65,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index 2eabcea176c2a..f573e84412ffb 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -352,7 +352,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/repart/repart.c b/src/repart/repart.c index 26588c6242b4d..ad19f0ab1ec7a 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9689,7 +9689,7 @@ static int parse_argv(int argc, char *argv[]) { bool auto_public_key_pcr_mask = true, auto_pcrlock = true; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_GROUP("Options"): {} diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index 1e2eca31eae68..bca943fd7faee 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -59,7 +59,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/report/report.c b/src/report/report.c index fef01c094ef9f..390871e942863 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -987,7 +987,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/run/run.c b/src/run/run.c index 9d1042e845a33..46b8014e580c5 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -255,7 +255,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "systemd-run" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("systemd-run"): {} @@ -783,7 +783,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "run0" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("run0"): {} diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index 7d866fde87555..f5a88b2849fe2 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -96,7 +96,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/shared/options.h b/src/shared/options.h index 5803eb120ef67..1f28dab8ad51f 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -216,13 +216,15 @@ int option_parse( const Option options_end[], OptionParser *state); -/* Iterate over options. */ -#define FOREACH_OPTION(c, state, on_error) \ +/* Iterate over options. Don't forget to handle errors (negative c)! */ +#define FOREACH_OPTION(c, state) \ + for (int c; (c = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, state)) != 0; ) + +#define FOREACH_OPTION_OR_RETURN(c, state) \ for (int c; (c = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, state)) != 0; ) \ - if (c < 0) { \ - on_error; \ - break; \ - } else + if (c < 0) \ + return c; \ + else /* Those helpers are used *during* option parsing and allow looking at or taking the next item in * the argv array, either an option or a positional parameter. */ diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index c572138596d38..131550c46b20d 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -66,7 +66,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_LOG_LEVEL: diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 3b2f9d698bb84..53f306a8faefc 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -731,7 +731,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 03cf327b6259e..768a2a3ea7235 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -358,7 +358,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index ea68009b35802..77dc903535633 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -423,7 +423,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index ee128b5e1811c..2028d3f942393 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -163,7 +163,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { const char *verb = NULL; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 4be5205d59894..01686d2cd6c0b 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -49,7 +49,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/storage/storage-block.c b/src/storage/storage-block.c index 4c21795c360ad..e5454a29c28a0 100644 --- a/src/storage/storage-block.c +++ b/src/storage/storage-block.c @@ -408,7 +408,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/storage/storage-fs.c b/src/storage/storage-fs.c index 47ec9829c494d..c01e91a4cefe6 100644 --- a/src/storage/storage-fs.c +++ b/src/storage/storage-fs.c @@ -768,7 +768,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index a21072e78f204..2bc7b7c2a3e40 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -406,7 +406,7 @@ static int parse_argv(int argc, char *argv[], char ***args) { assert(argv); OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index 5129887d795fe..384e88f7b88bc 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -80,7 +80,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index 6a9e33e6e6f7b..e124b56fc9c12 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -362,7 +362,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 648dd093e6160..ff8829115148e 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1865,7 +1865,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 65d2c7675ed45..f09c18cd173bb 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1689,7 +1689,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"): diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 38fe4f4515161..05a3e2db509e4 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -2103,7 +2103,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_CAT_CONFIG: diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index daa8713f48009..410522ceb161f 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -39,7 +39,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/test/test-options.c b/src/test/test-options.c index efa3a73d69edd..d00262fa34bb2 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -842,7 +842,10 @@ static void test_macros_parse_one( OptionParser opts = { argc, argv, mode, namespace }; - FOREACH_OPTION(c, &opts, assert_not_reached()) { + FOREACH_OPTION(c, &opts) { + + assert(c >= 0); + log_debug("%c %s: %s=%s", opts.opt->short_code != 0 ? opts.opt->short_code : ' ', opts.opt->long_code ?: "", diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 211f7d7a6d280..c35b090035eac 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -949,7 +949,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/tmpfiles/test-offline-passwd.c b/src/tmpfiles/test-offline-passwd.c index 21b2697ceeab6..f357ef8865d8d 100644 --- a/src/tmpfiles/test-offline-passwd.c +++ b/src/tmpfiles/test-offline-passwd.c @@ -45,7 +45,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION('r', "root", "PATH", "Operate on an alternate filesystem root"): diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 0cc06ca8ec0aa..44843f3ca77ec 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -4189,7 +4189,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_LONG("create", NULL, "Create and adjust files and directories"): diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c index b65905c03dbcd..19186ecc02fd8 100644 --- a/src/tpm2-setup/tpm2-clear.c +++ b/src/tpm2-setup/tpm2-clear.c @@ -52,7 +52,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index b8e585225be2a..bb08e31a81c87 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: 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 cd49503156db7..d675e4269ac16 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -475,7 +475,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index ea28ad027d313..c2fabdcdb844b 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -379,7 +379,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index b78096bde6362..27423e985155e 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -920,7 +920,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index 269ea15252101..a1708c128c928 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -664,7 +664,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index 6b31f49a48076..a19c7eebec6e7 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -49,7 +49,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index 3b926fa4a24f2..eadab1cb8a091 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -86,7 +86,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index 3e5f162343dbb..fe9924f1b6e28 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -56,7 +56,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index f272648c420c9..d7970722848c8 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -228,7 +228,7 @@ static int set_options(int argc, char **argv, char *maj_min_dev) { OptionParser opts = { argc, argv }; int r; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: @@ -319,7 +319,7 @@ static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum /* We reuse the option parser, but only a subset of the options is supported here. * If any others are encountered, return an error. */ - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) if (opts.opt->short_code == 'b') *good_bad = 0; else if (opts.opt->short_code == 'g') diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 16eaced0dcf46..4cd22a889fcf7 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -517,7 +517,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udev-builtin-blkid" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udev-builtin-blkid"): {} diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 4817c3af24e20..dececd9c0377c 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -138,7 +138,7 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udev-builtin-hwdb" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udev-builtin-hwdb"): {} diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index e234d6fe6d994..541ba16dd906b 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -179,7 +179,7 @@ static int parse_argv(int argc, char *argv[], UdevConfig *config) { OptionParser opts = { argc, argv, OPTION_PARSER_NORMAL, "udevd" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevd"): {} diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index 48ca72041627f..62d30d0234d24 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -46,7 +46,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, .namespace = "udevadm-cat" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-cat"): {} diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index ed586d5542d1f..a6ffe83cecaf6 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -74,7 +74,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-control" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-control"): {} diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index f4060673ebfe7..b029db2262a04 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -42,7 +42,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-hwdb" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-hwdb"): {} diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 3795856592c1d..a5cbedc8deeda 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -985,7 +985,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-info" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-info"): {} diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index cebce08007eb0..c1c3211d34992 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -62,7 +62,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm-lock" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-lock"): {} diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 0c165241a2e3d..c7d1f40fc49b6 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -126,7 +126,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-monitor" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-monitor"): {} diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 1292462d28c25..211a8ff1fbf8c 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -56,7 +56,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-settle" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-settle"): {} diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index 31ac569957017..9c0082800f37a 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -42,7 +42,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-test-builtin" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-test-builtin"): {} diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index a7841333016f9..ac368e0f00eec 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -61,7 +61,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-test" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-test"): {} diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index 62ccba37c5b8d..583d85be0b8d8 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -348,7 +348,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-trigger" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-trigger"): {} diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 1ecc1fbee9c78..f4388f843adc6 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -51,7 +51,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, .namespace = "udevadm-verify" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-verify"): {} diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index a361bac61a3a7..6017401440689 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -324,7 +324,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, .namespace = "udevadm-wait" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm-wait"): {} diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 856d1fc4c23e2..47d4335baec7f 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -71,7 +71,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm" }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_NAMESPACE("udevadm"): {} diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index 93ca2d3b997fe..1a53e1092fb7a 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -43,7 +43,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index b55c9941a9de3..67ce353e114d2 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -98,7 +98,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 58f8feb805dca..506b8198709d5 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -66,7 +66,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index fbf4b217ff691..fbd5e2499a5d5 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -126,7 +126,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8e4cbf3e80611..81c035c250d62 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -328,7 +328,7 @@ static int parse_argv(int argc, char *argv[]) { OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index f18edb263f8e9..f0b5ef44dfb67 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -101,7 +101,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OptionParser opts = { argc, argv }; - FOREACH_OPTION(c, &opts, /* on_error= */ return c) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION('B', "basename", "BASENAME", "Look for specified basename"): From 4ce8cd17da3c5688a36106a1af48a16dd13daae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 09:31:46 +0200 Subject: [PATCH 1390/2155] fundamental: drop now-unused macro Followup for 9d2f5b4611a47b9e5a31296cea70c2d8c6c86bbb. --- src/fundamental/cleanup-fundamental.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index 8d499e5c3498b..b9f9c0724546b 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -45,14 +45,6 @@ #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); \ - } - /* 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. */ From f78ba86d262adee5720a704fd6049db6354d9048 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:17:22 -0700 Subject: [PATCH 1391/2155] report: report user and system CPU time per cgroup Extend io.systemd.CGroup.CpuUsage from a single per-unit nanosecond counter to three rows distinguished by a "type" field of "total", "user", or "system". The values come from cpu.stat's usage_usec, user_usec and system_usec keys, read in a single keyed-attribute fetch and cached on each CGroupInfo so each scrape only opens cpu.stat once per cgroup. --- src/report/report-cgroup.c | 106 ++++++++++++++++++++++--- test/units/TEST-74-AUX-UTILS.report.sh | 7 ++ 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c index c3dabe41b1016..9a52c03d17741 100644 --- a/src/report/report-cgroup.c +++ b/src/report/report-cgroup.c @@ -22,6 +22,10 @@ typedef struct CGroupInfo { uint64_t io_rbytes; uint64_t io_rios; int io_stat_cached; /* 0 = not attempted, > 0 = cached, < 0 = -errno */ + uint64_t cpu_total_nsec; + uint64_t cpu_user_nsec; + uint64_t cpu_system_nsec; + int cpu_stat_cached; /* 0 = not attempted, > 0 = cached, < 0 = -errno */ } CGroupInfo; static CGroupInfo *cgroup_info_free(CGroupInfo *info) { @@ -154,6 +158,89 @@ static int walk_cgroups(CGroupContext *ctx, CGroupInfo ***ret, size_t *ret_n) { return 0; } +/* Parse cpu.stat for a cgroup once, extracting usage_usec, user_usec and system_usec + * in a single read so each scrape only opens the file once per cgroup. */ +static int cpu_stat_parse( + const char *cgroup_path, + uint64_t *ret_total_nsec, + uint64_t *ret_user_nsec, + uint64_t *ret_system_nsec) { + + char *values[3] = {}; + uint64_t total_us, user_us, system_us; + int r; + + assert(cgroup_path); + assert(ret_total_nsec); + assert(ret_user_nsec); + assert(ret_system_nsec); + + r = cg_get_keyed_attribute( + cgroup_path, + "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), + values); + if (r < 0) + return r; + + r = safe_atou64(values[0], &total_us); + if (r >= 0) + r = safe_atou64(values[1], &user_us); + if (r >= 0) + r = safe_atou64(values[2], &system_us); + + free_many_charp(values, ELEMENTSOF(values)); + if (r < 0) + return r; + + *ret_total_nsec = total_us * NSEC_PER_USEC; + *ret_user_nsec = user_us * NSEC_PER_USEC; + *ret_system_nsec = system_us * NSEC_PER_USEC; + return 0; +} + +static int ensure_cpu_stat_cached(CGroupInfo *info) { + int r; + + assert(info); + + if (info->cpu_stat_cached > 0) + return 0; + if (info->cpu_stat_cached < 0) + return info->cpu_stat_cached; + + r = cpu_stat_parse(info->path, &info->cpu_total_nsec, &info->cpu_user_nsec, &info->cpu_system_nsec); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to parse cpu.stat for '%s': %m", info->path); + info->cpu_stat_cached = r; + return r; + } + + info->cpu_stat_cached = 1; + return 0; +} + +static int cpu_usage_send_one( + MetricFamilyContext *context, + const char *unit, + uint64_t value_nsec, + const char *type) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + int r; + + assert(context); + assert(unit); + assert(type); + + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", type)); + if (r < 0) + return r; + + return metric_build_send_unsigned(context, unit, value_nsec, fields); +} + static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) { CGroupContext *ctx = ASSERT_PTR(userdata); CGroupInfo **cgroups; @@ -167,17 +254,18 @@ static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) { return 0; /* Skip metric on failure */ FOREACH_ARRAY(c, cgroups, n_cgroups) { - uint64_t us; + if (ensure_cpu_stat_cached(*c) < 0) + continue; - r = cg_get_keyed_attribute_uint64((*c)->path, "cpu.stat", "usage_usec", &us); + r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_total_nsec, "total"); if (r < 0) - continue; + return r; - r = metric_build_send_unsigned( - context, - (*c)->unit, - us * NSEC_PER_USEC, - /* fields= */ NULL); + r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_user_nsec, "user"); + if (r < 0) + return r; + + r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_system_nsec, "system"); if (r < 0) return r; } @@ -451,7 +539,7 @@ 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", + .description = "Per unit metric: CPU usage in nanoseconds (type=total|user|system)", .type = METRIC_FAMILY_TYPE_COUNTER, .generate = cpu_usage_build_json, }, diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 53b83c4dd9477..8bca8447f3e3f 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -37,6 +37,13 @@ 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 {} +# CpuUsage emits one row per (cgroup, type) where type is total, user, or system. +# Confirm all three are present. +cgroup_metrics=$(varlinkctl --more --json=short call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.List {}) +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"total"' >/dev/null +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"user"' >/dev/null +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"system"' >/dev/null + # test io.systemd.Network Metrics varlinkctl info /run/systemd/report/io.systemd.Network varlinkctl list-methods /run/systemd/report/io.systemd.Network From 630f5a1fc4d8355caf68b82dc042ff02f56080ab Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 30 Apr 2026 18:19:49 +0100 Subject: [PATCH 1392/2155] mkosi: update debian commit reference to 1302f123d9ab65bbaff5d95935eabfd659456550 * 1302f123d9 Restrict wildcard for new files * a6d0098d10 Install new files for upstream build * ce07fd7616 d/t/boot-and-services: use coreutils tunable in apparmor test (LP: #2125614) --- 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 f46a0a0372322..f72e35d6584b0 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=94af257c72ac3e9bf20e324ff31c3bd5d8197f0e + GIT_COMMIT=1302f123d9ab65bbaff5d95935eabfd659456550 PKG_SUBDIR=debian From acdaececf08fa7653d3d04885ae9eea18127d206 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:19:55 +0200 Subject: [PATCH 1393/2155] bootspec: add boot_config_selected_entry() helper --- src/shared/bootspec.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 951a81f08c6c4..afc5a576c9048 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -116,6 +116,16 @@ static inline const BootEntry* boot_config_default_entry(const BootConfig *confi return config->entries + config->default_entry; } +static inline const BootEntry* boot_config_selected_entry(const BootConfig *config) { + assert(config); + + if (config->selected_entry < 0) + return NULL; + + assert((size_t) config->selected_entry < config->n_entries); + return config->entries + config->selected_entry; +} + void boot_config_free(BootConfig *config); int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path); From 0a46727ce7bdb3a99e43bcb321aa7df500a8f24a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 13 Mar 2026 22:52:18 +0100 Subject: [PATCH 1394/2155] bootspec: make pe_find_uki_sections() non-static --- src/shared/bootspec.c | 22 ++++++++++++++-------- src/shared/bootspec.h | 2 ++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 3338d75f660df..ebd63788c0ad1 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -963,7 +963,7 @@ static int trim_cmdline(char **cmdline) { * the ones we do care about and we are willing to load into memory have this size limit.) */ #define PE_SECTION_SIZE_MAX (4U*1024U*1024U) -static int pe_find_uki_sections( +int pe_find_uki_sections( int fd, const char *path, unsigned profile, @@ -979,9 +979,6 @@ static int pe_find_uki_sections( assert(fd >= 0); assert(path); assert(profile != UINT_MAX); - assert(ret_osrelease); - assert(ret_profile); - assert(ret_cmdline); r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) @@ -1038,13 +1035,22 @@ static int pe_find_uki_sections( if (trim_cmdline(&cmdline_text) < 0) return log_oom(); - *ret_osrelease = TAKE_PTR(osrelease_text); - *ret_profile = TAKE_PTR(profile_text); - *ret_cmdline = TAKE_PTR(cmdline_text); + if (ret_osrelease) + *ret_osrelease = TAKE_PTR(osrelease_text); + if (ret_profile) + *ret_profile = TAKE_PTR(profile_text); + if (ret_cmdline) + *ret_cmdline = TAKE_PTR(cmdline_text); return 1; nothing: - *ret_osrelease = *ret_profile = *ret_cmdline = NULL; + if (ret_osrelease) + *ret_osrelease = NULL; + if (ret_profile) + *ret_profile = NULL; + if (ret_cmdline) + *ret_cmdline = NULL; + return 0; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index afc5a576c9048..01abe05e72dd9 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -162,3 +162,5 @@ int show_boot_entries( int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done); int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret); + +int pe_find_uki_sections(int fd, const char *path, unsigned profile, char **ret_osrelease, char **ret_profile, char **ret_cmdline); From d292874bf6845605b733ad8e6926a5bee23ef5d3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 10:21:11 +0100 Subject: [PATCH 1395/2155] bootspec: add bootspec_extract_osrelease() helper --- src/shared/bootspec.c | 145 ++++++++++++++++++++++++++++++++++-------- src/shared/bootspec.h | 2 + 2 files changed, 119 insertions(+), 28 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index ebd63788c0ad1..c0111d1f9c433 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -31,6 +31,7 @@ #include "string-util.h" #include "strv.h" #include "uki.h" +#include "utf8.h" static const char* const boot_entry_type_description_table[_BOOT_ENTRY_TYPE_MAX] = { [BOOT_ENTRY_TYPE1] = "Boot Loader Specification Type #1 (.conf)", @@ -711,56 +712,143 @@ static int boot_entries_find_type1( return 0; } -static int boot_entry_load_unified( - const char *root, - const BootEntrySource source, - const char *path, - unsigned profile, - const char *osrelease_text, - const char *profile_text, - const char *cmdline_text, - BootEntry *ret) { +static void mangle_osrelease_string(char **s, const char *field) { + assert(s); + assert(field); - _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; - int r; + if (!isempty(*s) && !string_has_cc(*s, /* ok= */ NULL) && utf8_is_valid(*s)) + return; - assert(root); - assert(path); - assert(osrelease_text); - assert(ret); + if (*s) { + log_debug("OS release field '%s' is not clean, suppressing.", field); + *s = mfree(*s); + } +} - k = path_startswith(path, root); - if (!k) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); +int bootspec_extract_osrelease( + const char *text, + char **ret_good_name, + char **ret_good_version, + char **ret_good_sort_key, + char **ret_os_id, + char **ret_os_version_id, + char **ret_image_id, + char **ret_image_version) { + + int r; + + assert(text); - r = parse_env_data(osrelease_text, /* size= */ SIZE_MAX, - ".osrel", + _cleanup_free_ char *os_pretty_name = NULL, *image_id = NULL, *os_name = NULL, *os_id = NULL, + *image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; + r = parse_env_data(text, /* size= */ SIZE_MAX, + "os-release", "PRETTY_NAME", &os_pretty_name, - "IMAGE_ID", &os_image_id, + "IMAGE_ID", &image_id, "NAME", &os_name, "ID", &os_id, - "IMAGE_VERSION", &os_image_version, + "IMAGE_VERSION", &image_version, "VERSION", &os_version, "VERSION_ID", &os_version_id, "BUILD_ID", &os_build_id); if (r < 0) - return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path); + return r; + + mangle_osrelease_string(&os_pretty_name, "PRETTY_NAME"); + mangle_osrelease_string(&image_id, "IMAGE_ID"); + mangle_osrelease_string(&os_name, "NAME"); + mangle_osrelease_string(&os_id, "ID"); + mangle_osrelease_string(&image_version, "IMAGE_VERSION"); + mangle_osrelease_string(&os_version, "VERSION"); + mangle_osrelease_string(&os_version_id, "VERSION_ID"); + mangle_osrelease_string(&os_build_id, "BUILD_ID"); + const char *good_name, *good_version, *good_sort_key; if (!bootspec_pick_name_version_sort_key( os_pretty_name, - os_image_id, + image_id, os_name, os_id, - os_image_version, + image_version, os_version, os_version_id, os_build_id, &good_name, &good_version, &good_sort_key)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path); + return -EBADMSG; + + _cleanup_free_ char *copy_good_name = NULL, *copy_good_version = NULL, *copy_good_sort_key = NULL; + if (ret_good_name) { + copy_good_name = strdup(good_name); + if (!copy_good_name) + return -ENOMEM; + } + + if (ret_good_version && good_version) { + copy_good_version = strdup(good_version); + if (!copy_good_version) + return -ENOMEM; + } + + if (ret_good_sort_key && good_sort_key) { + copy_good_sort_key = strdup(good_sort_key); + if (!copy_good_sort_key) + return -ENOMEM; + } + + if (ret_good_name) + *ret_good_name = TAKE_PTR(copy_good_name); + if (ret_good_version) + *ret_good_version = TAKE_PTR(copy_good_version); + if (ret_good_sort_key) + *ret_good_sort_key = TAKE_PTR(copy_good_sort_key); + + if (ret_os_id) + *ret_os_id = TAKE_PTR(os_id); + if (ret_os_version_id) + *ret_os_version_id = TAKE_PTR(os_version_id); + if (ret_image_id) + *ret_image_id = TAKE_PTR(image_id); + if (ret_image_version) + *ret_image_version = TAKE_PTR(image_version); + + return 0; +} + +static int boot_entry_load_unified( + const char *root, + const BootEntrySource source, + const char *path, + unsigned profile, + const char *osrelease_text, + const char *profile_text, + const char *cmdline_text, + BootEntry *ret) { + + int r; + + assert(root); + assert(path); + assert(osrelease_text); + assert(ret); + + const char *k = path_startswith(path, root); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); + + _cleanup_free_ char *good_name = NULL, *good_version = NULL, *good_sort_key = NULL, *os_id = NULL, *os_version_id = NULL; + r = bootspec_extract_osrelease( + osrelease_text, + &good_name, + &good_version, + &good_sort_key, + &os_id, + &os_version_id, + /* ret_image_id= */ NULL, + /* ret_image_version= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image %s, refusing: %m", path); _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; if (profile_text) { @@ -773,6 +861,7 @@ static int boot_entry_load_unified( return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path); } + _cleanup_free_ char *fname = NULL; r = path_extract_filename(path, &fname); if (r < 0) return log_error_errno(r, "Failed to extract file name from '%s': %m", path); diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 01abe05e72dd9..d5f6930be99d1 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -164,3 +164,5 @@ int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret); int pe_find_uki_sections(int fd, const char *path, unsigned profile, char **ret_osrelease, char **ret_profile, char **ret_cmdline); + +int bootspec_extract_osrelease(const char *text, char **ret_good_name, char **ret_good_version, char **ret_good_sort_key, char **ret_os_id, char **ret_os_version_id, char **ret_image_id, char **ret_image_version); From 4d0f6ac5931c051871e46e98a2ff7eb37136ea57 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:09:48 +0200 Subject: [PATCH 1396/2155] bootctl: add helpers that format a type1 menu entry filename from a commit nr --- src/bootctl/bootspec-util.c | 214 +++++++++++++++++++++++++++++++ src/bootctl/bootspec-util.h | 12 ++ src/bootctl/meson.build | 6 + src/bootctl/test-bootspec-util.c | 51 ++++++++ 4 files changed, 283 insertions(+) create mode 100644 src/bootctl/test-bootspec-util.c diff --git a/src/bootctl/bootspec-util.c b/src/bootctl/bootspec-util.c index b96687430ca32..5f9842c9d80a3 100644 --- a/src/bootctl/bootspec-util.c +++ b/src/bootctl/bootspec-util.c @@ -1,11 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "alloc-util.h" +#include "boot-entry.h" #include "bootspec-util.h" #include "devnum-util.h" #include "efi-loader.h" #include "errno-util.h" #include "log.h" +#include "parse-util.h" +#include "path-util.h" +#include "stdio-util.h" +#include "string-util.h" #include "strv.h" +#include "utf8.h" int boot_config_load_and_select( BootConfig *config, @@ -39,3 +46,210 @@ int boot_config_load_and_select( return boot_config_select_special_entries(config, /* skip_efivars= */ !!root); } + +int boot_entry_make_commit_filename( + const char *entry_token, + uint64_t entry_commit, + const char *version, + unsigned profile_nr, + unsigned tries_left, + char **ret) { + + assert(entry_token); + assert(ret); + + /* Generate a new entry filename from the entry token, the commit number, and (optionally) the + * image/OS version, (if non-zero) the profile number, and (unless UINT_MAX) the number of tries + * left. */ + + if (!boot_entry_token_valid(entry_token)) + return -EINVAL; + if (!entry_commit_valid(entry_commit)) + return -EINVAL; + + _cleanup_free_ char *filename = asprintf_safe("%s-commit_%" PRIu64, entry_token, entry_commit); + if (!filename) + return -ENOMEM; + if (version && !strextend(&filename, ".", version)) + return -ENOMEM; + if (profile_nr > 0 && strextendf(&filename, "@%u", profile_nr) < 0) + return -ENOMEM; + if (tries_left != UINT_MAX && strextendf(&filename, "+%u", tries_left) < 0) + return -ENOMEM; + if (!strextend(&filename, ".conf")) + return -ENOMEM; + + if (!filename_is_valid(filename) || string_has_cc(filename, /* ok= */ NULL) || !utf8_is_valid(filename)) + return -EINVAL; + + *ret = TAKE_PTR(filename); + return 0; +} + +int boot_entry_parse_commit_filename( + const char *filename, + char **ret_entry_token, + uint64_t *ret_entry_commit) { + + int r; + + assert(filename); + + if (!filename_is_valid(filename)) + return -EINVAL; + + _cleanup_free_ char *stripped = NULL; + r = boot_filename_extract_tries(filename, &stripped, /* ret_tries_left= */ NULL, /* ret_tries_done= */ NULL); + if (r < 0) + return r; + + const char *a = strrstr_no_case(stripped, "-commit_"); + if (!a) + return -EBADMSG; + + const char *c = endswith_no_case(stripped, ".conf"); + if (!c) + return -EBADMSG; + + assert(a < c); + + _cleanup_free_ char *entry_token = strndup(stripped, a - stripped); + if (!entry_token) + return -ENOMEM; + + if (!boot_entry_token_valid(entry_token)) + return -EBADMSG; + + const char *b = a + STRLEN("-commit_"); + size_t n = strspn(b, DIGITS); + if (n <= 0 || !IN_SET(b[n], '+', '.', '@')) + return -EBADMSG; + + _cleanup_free_ char *entry_commit_string = strndup(b, n); + if (!entry_commit_string) + return -ENOMEM; + + uint64_t entry_commit; + r = safe_atou64_full(entry_commit_string, 10, &entry_commit); + if (r < 0) + return r; + if (!entry_commit_valid(entry_commit)) + return -EBADMSG; + + if (ret_entry_token) + *ret_entry_token = TAKE_PTR(entry_token); + if (ret_entry_commit) + *ret_entry_commit = entry_commit; + + return 0; +} + +int boot_entry_parse_commit( + BootEntry *entry, + char **ret_entry_token, + uint64_t *ret_entry_commit) { + + int r; + + assert(entry); + + if (entry->type != BOOT_ENTRY_TYPE1) + return -EADDRNOTAVAIL; + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(entry->path, &fn); + if (r < 0) + return r; + + return boot_entry_parse_commit_filename(fn, ret_entry_token, ret_entry_commit); +} + +int boot_config_find_oldest_commit( + BootConfig *config, + const char *entry_token, + char ***ret_ids) { + + int r; + + assert(config); + assert(entry_token); + assert(ret_ids); + + uint64_t commit_oldest = UINT64_MAX, commit_2nd_oldest = UINT64_MAX, commit_blocked = UINT64_MAX; + + /* First, determine which commit is the oldest (that isn't the current one), and hence the candidate + * to be removed */ + FOREACH_ARRAY(b, config->entries, config->n_entries) { + _cleanup_free_ char *et = NULL; + uint64_t ec; + + r = boot_entry_parse_commit(b, &et, &ec); + if (r == -EADDRNOTAVAIL) + continue; + if (r < 0) { + log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id)); + continue; + } + + if (!streq(et, entry_token)) /* Not ours? */ + continue; + + if (ec < commit_oldest) { + commit_2nd_oldest = commit_oldest; + commit_oldest = ec; + } else if (ec > commit_oldest && ec < commit_2nd_oldest) + commit_2nd_oldest = ec; + + if (boot_config_selected_entry(config) == b) { + assert(commit_blocked == UINT64_MAX); + commit_blocked = ec; + } + } + + uint64_t commit_picked; + if (commit_oldest == UINT64_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "No matching entry found while determining oldest entry."); + if (commit_oldest != commit_blocked) + commit_picked = commit_oldest; + else { + if (commit_2nd_oldest == UINT64_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Only matching entry found while determining oldest entry is current one, skipping it."); + + assert(commit_2nd_oldest != commit_blocked); + commit_picked = commit_2nd_oldest; + } + + log_debug("Determined commit %" PRIu64 " to be oldest.", commit_picked); + + /* Second loop: actually remove all entries matching this commit (which can be multiple, since UKIs + * have profiles) */ + _cleanup_(strv_freep) char **l = NULL; + FOREACH_ARRAY(b, config->entries, config->n_entries) { + _cleanup_free_ char *et = NULL; + uint64_t ec; + + r = boot_entry_parse_commit(b, &et, &ec); + if (r == -EADDRNOTAVAIL) + continue; + if (r < 0) { + log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id)); + continue; + } + + if (!streq(et, entry_token)) /* Not ours? */ + continue; + + if (ec != commit_picked) + continue; + + r = strv_extend(&l, b->id); + if (r < 0) + return r; + } + + /* The list cannot be empty, the first loop above and the 2nd loop must have found the same matching + * entries, and if the first loop didn't find any we'd not come this far. */ + assert(!strv_isempty(l)); + *ret_ids = TAKE_PTR(l); + return 0; +} diff --git a/src/bootctl/bootspec-util.h b/src/bootctl/bootspec-util.h index 51dac12b9f44b..0824c8040fb64 100644 --- a/src/bootctl/bootspec-util.h +++ b/src/bootctl/bootspec-util.h @@ -4,3 +4,15 @@ #include "bootspec.h" 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); + +static inline bool entry_commit_valid(uint64_t commit) { + return commit > 0 && commit < UINT64_MAX; +} + +int boot_entry_make_commit_filename(const char *entry_token, uint64_t entry_commit, const char *version, unsigned profile_nr, unsigned tries_left, char **ret); + +int boot_entry_parse_commit_filename(const char *filename, char **ret_entry_token, uint64_t *ret_entry_commit); + +int boot_entry_parse_commit(BootEntry *entry, char **ret_entry_token, uint64_t *ret_entry_commit); + +int boot_config_find_oldest_commit(BootConfig *config, const char *entry_token, char ***ret_ids); diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index f8349df7168e3..ff33cde3f615b 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -25,4 +25,10 @@ executables += [ 'link_with' : boot_link_with, 'dependencies' : [libopenssl_cflags], }, + test_template + { + 'sources' : files( + 'test-bootspec-util.c', + 'bootspec-util.c', + ), + }, ] diff --git a/src/bootctl/test-bootspec-util.c b/src/bootctl/test-bootspec-util.c new file mode 100644 index 0000000000000..1fa891469f460 --- /dev/null +++ b/src/bootctl/test-bootspec-util.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "bootspec-util.h" +#include "tests.h" + +static void test_one( + const char *entry_token, + uint64_t entry_commit, + const char *version, + unsigned profile_nr, + unsigned tries_left, + const char *expected) { + + _cleanup_free_ char *fn = NULL; + ASSERT_OK(boot_entry_make_commit_filename(entry_token, entry_commit, version, profile_nr, tries_left, &fn)); + ASSERT_STREQ(fn, expected); + + _cleanup_free_ char *token = NULL; + uint64_t commit = 0; + ASSERT_OK(boot_entry_parse_commit_filename(fn, &token, &commit)); + ASSERT_STREQ(token, entry_token); + ASSERT_EQ(commit, entry_commit); +} + +TEST(boot_entry_commit_filename) { + test_one("foo", 1, NULL, 0, UINT_MAX, "foo-commit_1.conf"); + test_one("foo", 42, "1.0", 0, UINT_MAX, "foo-commit_42.1.0.conf"); + test_one("foo", 42, "1.0", 3, UINT_MAX, "foo-commit_42.1.0@3.conf"); + test_one("foo", 42, "1.0", 3, 5, "foo-commit_42.1.0@3+5.conf"); + test_one("foo", 42, NULL, 3, UINT_MAX, "foo-commit_42@3.conf"); + test_one("foo", 42, NULL, 3, 7, "foo-commit_42@3+7.conf"); + test_one("foo", 42, NULL, 0, 9, "foo-commit_42+9.conf"); + test_one("my-token", 123456, "v2", 0, UINT_MAX, "my-token-commit_123456.v2.conf"); + + /* Invalid inputs for make */ + _cleanup_free_ char *fn = NULL; + ASSERT_ERROR(boot_entry_make_commit_filename("foo/bar", 1, NULL, 0, UINT_MAX, &fn), EINVAL); + ASSERT_ERROR(boot_entry_make_commit_filename("foo", 0, NULL, 0, UINT_MAX, &fn), EINVAL); + ASSERT_ERROR(boot_entry_make_commit_filename("foo", UINT64_MAX, NULL, 0, UINT_MAX, &fn), EINVAL); + + /* Invalid inputs for parse */ + _cleanup_free_ char *token = NULL; + uint64_t commit = 0; + ASSERT_ERROR(boot_entry_parse_commit_filename("foo.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_abc.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_0.conf", &token, &commit), EBADMSG); +} + +DEFINE_TEST_MAIN(LOG_INFO); From e68bf712be1b348ae8c13fe0e04cc1f42d57ca9e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 10 Apr 2026 14:48:25 +0200 Subject: [PATCH 1397/2155] bootctl: rework/modernize "unlink" and add Varlink API for it Among other things this changes tracking of the location of resources during GC from using the BootEntrySource enum rather than a path, since we have that and it is more efficient and easier to grok. --- man/bootctl.xml | 25 +- shell-completion/bash/bootctl | 10 +- shell-completion/zsh/_bootctl | 21 + src/bootctl/bootctl-cleanup.c | 7 +- src/bootctl/bootctl-unlink.c | 623 ++++++++++++++++---- src/bootctl/bootctl-unlink.h | 6 +- src/bootctl/bootctl.c | 15 +- src/bootctl/bootctl.h | 1 + src/shared/shared-forward.h | 2 + src/shared/varlink-io.systemd.BootControl.c | 22 +- 10 files changed, 609 insertions(+), 123 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index 558891eaa1169..fb5f4b2b2a9eb 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -102,11 +102,17 @@ - ID + ID - Removes a boot loader entry including the files it refers to. Takes a single boot - loader entry ID string or a glob pattern as argument. Referenced files such as kernel or initrd are - only removed if no other entry refers to them. + Removes a boot loader entry including the files it refers to. Takes an optional boot + loader entry ID string or a glob pattern as argument. Referenced files such as the kernel, initrds, + system extensions (sysexts), configuration extensions (confexts) or credential files are only removed + if no other entry refers to them. + + If no ID argument is specified, the option + must be specified, in which case the boot loader entry with the lowest version is removed (for + robustness reasons the currently booted menu entry is never removed, nor is the last existing boot + loader entry). @@ -560,6 +566,17 @@ + + + + When used with unlink, selects the oldest installed boot loader + entry matching the boot entry token for removal (rather than passing an explicit entry ID). This is + useful for pruning older installed boot loader entries. Note that the currently booted entry is never + removed, nor is the last remaining one. + + + + diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index d7714731c2aac..792fc0c0acc83 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -40,8 +40,10 @@ _bootctl() { --dry-run' [ARG]='--esp-path --boot-path --root --image --image-policy --install-source --variables --random-seed --make-entry-directory --entry-token --json - --efi-boot-option-description --secure-boot-auto-enroll --private-key - --private-key-source --certificate --certificate-source' + --efi-boot-option-description --efi-boot-option-description-with-device + --secure-boot-auto-enroll --private-key + --private-key-source --certificate --certificate-source + --oldest' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -67,7 +69,7 @@ _bootctl() { --install-source) comps="image host auto" ;; - --random-seed|--variables|--secure-boot-auto-enroll) + --random-seed|--variables|--secure-boot-auto-enroll|--oldest|--efi-boot-option-description-with-device) comps="yes no" ;; --json) @@ -85,7 +87,7 @@ _bootctl() { local -A VERBS=( [STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup' - [BOOTENTRY]='set-default set-oneshot set-sysfail unlink' + [BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink' [BOOLEAN]='reboot-to-firmware' [FILE]='kernel-identify kernel-inspect' ) diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index f7ed2a8e4148a..c23c1c888dae5 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -24,10 +24,26 @@ _bootctl_set-oneshot() { _bootctl_comp_ids } +_bootctl_set-sysfail() { + _bootctl_comp_ids +} + +_bootctl_set-preferred() { + _bootctl_comp_ids +} + _bootctl_unlink() { _bootctl_comp_ids } +_bootctl_kernel-identify() { + _files +} + +_bootctl_kernel-inspect() { + _files +} + _bootctl_reboot-to-firmware() { local -a _completions _completions=( yes no ) @@ -49,10 +65,14 @@ _bootctl_reboot-to-firmware() { "list:List boot loader entries" "set-default:Set the default boot loader entry" "set-oneshot:Set the default boot loader entry only for the next boot" + "set-sysfail:Set boot loader entry used in case of a system failure" + "set-preferred:Set the preferred boot loader entry" "set-timeout:Set the menu timeout" "set-timeout-oneshot:Set the menu timeout for the next boot only" "unlink:Remove boot loader entry" "cleanup:Remove files in ESP not referenced in any boot entry" + "kernel-identify:Identify kernel image type" + "kernel-inspect:Print details about the kernel image" ) if (( CURRENT == 1 )); then _describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@" @@ -79,6 +99,7 @@ _arguments \ '--no-pager[Do not pipe output into a pager]' \ '--graceful[Do not fail when locating ESP or writing fails]' \ '--dry-run[Dry run (unlink and cleanup)]' \ + '--oldest=[Delete oldest boot menu entry]:options:(yes no)' \ '--root=[Operate under the specified directory]:PATH' \ '--image=[Operate on the specified image]:PATH' \ '--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \ diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 1e8819bea1813..011567d187be6 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -49,6 +49,7 @@ static int list_remove_orphaned_file( static int cleanup_orphaned_files( const BootConfig *config, + BootEntrySource source, const char *root) { _cleanup_hashmap_free_ Hashmap *known_files = NULL; @@ -65,7 +66,7 @@ static int cleanup_orphaned_files( if (r < 0) return r; - r = boot_config_count_known_files(config, root, &known_files); + r = boot_config_count_known_files(config, source, &known_files); if (r < 0) return log_error_errno(r, "Failed to count files in %s: %m", root); @@ -116,10 +117,10 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; r = 0; - RET_GATHER(r, cleanup_orphaned_files(&config, arg_esp_path)); + RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_ESP, arg_esp_path)); if (arg_xbootldr_path && xbootldr_devid != esp_devid) - RET_GATHER(r, cleanup_orphaned_files(&config, arg_xbootldr_path)); + RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_XBOOTLDR, arg_xbootldr_path)); return r; } diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index 0d0e7ad076b60..80e74926c6c76 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -3,20 +3,70 @@ #include #include +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + #include "alloc-util.h" +#include "boot-entry.h" #include "bootctl.h" #include "bootctl-unlink.h" #include "bootspec.h" #include "bootspec-util.h" #include "chase.h" +#include "efi-loader.h" #include "errno-util.h" +#include "fd-util.h" +#include "find-esp.h" #include "hashmap.h" +#include "id128-util.h" +#include "json-util.h" #include "log.h" #include "path-util.h" +#include "stat-util.h" +#include "string-util.h" #include "strv.h" +typedef struct UnlinkContext { + char *root; + int root_fd; + + sd_id128_t machine_id; + BootEntryTokenType entry_token_type; + char *entry_token; + + char *esp_path; + dev_t esp_devid; + int esp_fd; + + char *xbootldr_path; + dev_t xbootldr_devid; + int xbootldr_fd; +} UnlinkContext; + +#define UNLINK_CONTEXT_NULL \ + (UnlinkContext) { \ + .root_fd = -EBADF, \ + .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \ + .esp_fd = -EBADF, \ + .xbootldr_fd = -EBADF, \ + } + +static void unlink_context_done(UnlinkContext *c) { + assert(c); + + c->root = mfree(c->root); + c->root_fd = safe_close(c->root_fd); + + c->entry_token = mfree(c->entry_token); + + c->esp_path = mfree(c->esp_path); + c->esp_fd = safe_close(c->esp_fd); + c->xbootldr_path = mfree(c->xbootldr_path); + c->xbootldr_fd = safe_close(c->xbootldr_fd); +} + static int ref_file(Hashmap **known_files, const char *fn, int increment) { - char *k = NULL; int n, r; assert(known_files); @@ -26,13 +76,15 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { if (!fn) return 0; + char *k = NULL; n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k)); - n += increment; + if (!INC_SAFE(&n, increment)) + return -EOVERFLOW; assert(n >= 0); if (n == 0) { - (void) hashmap_remove(*known_files, fn); + (void) hashmap_remove(*known_files, k); free(k); } else if (!k) { _cleanup_free_ char *t = NULL; @@ -40,12 +92,14 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { t = strdup(fn); if (!t) return -ENOMEM; + r = hashmap_ensure_put(known_files, &path_hash_ops_free, t, INT_TO_PTR(n)); if (r < 0) return r; + TAKE_PTR(t); } else { - r = hashmap_update(*known_files, fn, INT_TO_PTR(n)); + r = hashmap_update(*known_files, k, INT_TO_PTR(n)); if (r < 0) return r; } @@ -53,195 +107,548 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { return n; } +static int boot_entry_ref_files( + const BootEntry *e, + Hashmap **known_files, + int increment) { + + int r; + + assert(e); + assert(known_files); + assert(increment != 0); + + r = ref_file(known_files, e->kernel, increment); + if (r < 0) + return r; + + r = ref_file(known_files, e->efi, increment); + if (r < 0) + return r; + + r = ref_file(known_files, e->uki, increment); + if (r < 0) + return r; + + STRV_FOREACH(s, e->initrd) { + r = ref_file(known_files, *s, increment); + if (r < 0) + return r; + } + + r = ref_file(known_files, e->device_tree, increment); + if (r < 0) + return r; + + STRV_FOREACH(s, e->device_tree_overlay) { + r = ref_file(known_files, *s, increment); + if (r < 0) + return r; + } + + return 0; +} + int boot_config_count_known_files( const BootConfig *config, - const char* root, + BootEntrySource source, Hashmap **ret_known_files) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; int r; assert(config); assert(ret_known_files); - for (size_t i = 0; i < config->n_entries; i++) { - const BootEntry *e = config->entries + i; + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + FOREACH_ARRAY(e, config->entries, config->n_entries) { - if (!path_equal(e->root, root)) + if (e->source != source) continue; - r = ref_file(&known_files, e->kernel, +1); - if (r < 0) - return r; - r = ref_file(&known_files, e->efi, +1); - if (r < 0) - return r; - r = ref_file(&known_files, e->uki, +1); - if (r < 0) - return r; - STRV_FOREACH(s, e->initrd) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } - r = ref_file(&known_files, e->device_tree, +1); + r = boot_entry_ref_files(e, &known_files, +1); if (r < 0) return r; - STRV_FOREACH(s, e->device_tree_overlay) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } } *ret_known_files = TAKE_PTR(known_files); - return 0; } -static void deref_unlink_file(Hashmap **known_files, const char *fn, const char *root) { - _cleanup_free_ char *path = NULL; +static int unref_unlink_file( + Hashmap **known_files, + const char *root, + int root_fd, + const char *path, + bool dry_run) { + int r; assert(known_files); /* just gracefully ignore this. This way the caller doesn't have to verify whether the bootloader entry is relevant */ - if (!fn || !root) - return; + if (root_fd < 0 || !root || !path) + return 0; - r = ref_file(known_files, fn, -1); + r = ref_file(known_files, path, -1); if (r < 0) - return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn); + return log_error_errno(r, "Failed to unref '%s': %m", path); if (r > 0) - return; + return 0; - if (arg_dry_run) { - r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, F_OK, &path); - if (r < 0) - log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn); - else - log_info("Would remove \"%s\"", path); - return; + if (dry_run) { + _cleanup_free_ char *resolved = NULL; + r = chase_and_accessat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + path, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_REGULAR, + F_OK, + &resolved); + if (r < 0) { + log_warning_errno(r, "Unable to determine whether '%s' exists, ignoring: %m", path); + return 0; + } + + log_info("Would remove '%s'", resolved); + return 1; } - r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, &path); - if (r >= 0) - log_info("Removed \"%s\"", path); - else if (r != -ENOENT) - return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn); - - _cleanup_free_ char *d = NULL; - if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) { - r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, AT_REMOVEDIR, NULL); - if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT)) - log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d); + _cleanup_free_ char *resolved = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + path, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + /* unlink_flags= */ 0, + &resolved); + if (r == -ENOENT) + log_debug("Resource '%s' is already removed, skipping.", path); + else if (r < 0) { + log_warning_errno(r, "Failed to remove '%s', ignoring: %m", path); + return 0; + } else + log_info("Removed '%s'", resolved); + + _cleanup_free_ char *parent = NULL; + r = path_extract_directory(path, &parent); + if (r < 0) + log_debug_errno(r, "Failed to extract parent directory of '%s', ignoring.", path); + else { + _cleanup_free_ char *resolved_parent = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + parent, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + AT_REMOVEDIR, + &resolved_parent); + if (IN_SET(r, -ENOTEMPTY, -ENOENT)) + log_debug_errno(r, "Failed to remove directory '%s', ignoring: %m", parent); + else if (r < 0) + log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", parent); + else + log_info("Removed '%s'.", resolved_parent); } + + return 1; } -static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) { +static ssize_t boot_config_find_in( + const BootConfig *config, + BootEntrySource source, + const char *id) { + assert(config); + assert(source >= 0); + assert(source < _BOOT_ENTRY_SOURCE_MAX); - if (!root || !id) + if (!id) return -ENOENT; for (size_t i = 0; i < config->n_entries; i++) - if (path_equal(config->entries[i].root, root) && + if (config->entries[i].source == source && fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0) - return i; + return (ssize_t) i; return -ENOENT; } -static int unlink_entry(const BootConfig *config, const char *root, const char *id) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; - const BootEntry *e = NULL; - int r; - - assert(config); +int boot_entry_unlink( + const BootEntry *e, + const char *root, + int root_fd, + Hashmap *known_files, + bool dry_run) { - r = boot_config_count_known_files(config, root, &known_files); - if (r < 0) - return log_error_errno(r, "Failed to count files in %s: %m", root); - - r = boot_config_find_in(config, root, id); - if (r < 0) - return 0; /* There is nothing to remove. */ - - if (r == config->default_entry) - log_warning("%s is the default boot entry", id); - if (r == config->selected_entry) - log_warning("%s is the selected boot entry", id); + int r; - e = &config->entries[r]; + assert(e); + assert(root_fd >= 0); - deref_unlink_file(&known_files, e->kernel, e->root); - deref_unlink_file(&known_files, e->efi, e->root); - deref_unlink_file(&known_files, e->uki, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, e->kernel, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->efi, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->uki, dry_run); STRV_FOREACH(s, e->initrd) - deref_unlink_file(&known_files, *s, e->root); - deref_unlink_file(&known_files, e->device_tree, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->device_tree, dry_run); STRV_FOREACH(s, e->device_tree_overlay) - deref_unlink_file(&known_files, *s, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run); - if (arg_dry_run) + if (dry_run) log_info("Would remove \"%s\"", e->path); else { - r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, NULL); + const char *p = path_startswith(e->path, root); + if (!p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File '%s' is not inside root '%s', refusing.", e->path, root); + + _cleanup_free_ char *resolved = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + p, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + /* unlink_flags= */ 0, + &resolved); if (r == -ENOENT) return 0; /* Already removed? */ if (r < 0) return log_error_errno(r, "Failed to remove \"%s\": %m", e->path); - log_info("Removed %s", e->path); + log_info("Removed '%s'.", resolved); } return 0; } -int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { - dev_t esp_devid = 0, xbootldr_devid = 0; +static int unlink_entry( + const BootConfig *config, + const char *root, + int root_fd, + BootEntrySource source, + char **ids, + bool dry_run) { + + size_t n_removed = 0; + int r; + + assert(config); + + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + r = boot_config_count_known_files(config, source, &known_files); + if (r < 0) + return log_error_errno(r, "Failed to count files in %s: %m", root); + + int ret = 0; + STRV_FOREACH(id, ids) { + log_debug("Unlinking '%s'", *id); + ssize_t idx = boot_config_find_in(config, source, *id); + if (idx < 0) + continue; /* There is nothing to remove. */ + + log_debug("Index %zi", idx); + + if (idx == config->default_entry) + log_warning("%s is the default boot entry", *id); + if (idx == config->selected_entry) + log_warning("%s is the selected boot entry", *id); + + r = boot_entry_unlink(config->entries + idx, root, root_fd, known_files, dry_run); + if (r < 0) + RET_GATHER(ret, r); + else + n_removed++; + } + + if (n_removed == 0) + log_info("No matching entries found or removed."); + + return ret; +} + +static int unlink_context_from_cmdline(UnlinkContext *ret) { int r; + assert(ret); + + _cleanup_(unlink_context_done) UnlinkContext b = UNLINK_CONTEXT_NULL; + b.entry_token_type = arg_entry_token_type; + + if (strdup_to(&b.entry_token, arg_entry_token) < 0) + return log_oom(); + + if (arg_root) { + b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (b.root_fd < 0) + return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root); + + if (strdup_to(&b.root, arg_root) < 0) + return log_oom(); + } else + b.root_fd = XAT_FDROOT; + r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, - /* ret_fd= */ NULL, + &b.esp_fd, /* 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; + &b.esp_devid); + if (r < 0 && r != -ENOKEY) + return r; /* About all other errors acquire_esp() logs on its own */ + if (r > 0) { + if (arg_root) { + const char *e = path_startswith(arg_esp_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root); + + r = strdup_to(&b.esp_path, e); + } else + r = strdup_to(&b.esp_path, arg_esp_path); + if (r < 0) + return log_oom(); + } r = acquire_xbootldr( /* unprivileged_mode= */ false, - /* ret_fd= */ NULL, + &b.xbootldr_fd, /* ret_uuid= */ NULL, - &xbootldr_devid); - if (r == -EACCES) - return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); - if (r < 0) + &b.xbootldr_devid); + if (r < 0 && r != -ENOKEY) return r; + if (r > 0) { + if (arg_root) { + const char *e = path_startswith(arg_xbootldr_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root); + + r = strdup_to(&b.xbootldr_path, e); + } else + r = strdup_to(&b.xbootldr_path, arg_xbootldr_path); + if (r < 0) + return log_oom(); + } + + /* Only if we found neither ESP nor XBOOTLDR let's fail. */ + if (!b.xbootldr_path && !b.esp_path) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Neither ESP nor XBOOTLDR found, refusing."); + + *ret = TAKE_GENERIC(b, UnlinkContext, UNLINK_CONTEXT_NULL); + return 0; +} + +static int run_unlink( + UnlinkContext *c, + char **_ids, + bool dry_run) { + + int r; + assert(c); + + _cleanup_free_ char *x = NULL, *y = NULL; + if (c->root && c->esp_path) { + x = path_join(c->root, c->esp_path); + if (!x) + return log_oom(); + } + + if (c->root && c->xbootldr_path) { + y = path_join(c->root, c->xbootldr_path); + if (!y) + return log_oom(); + } _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, - xbootldr_devid); + c->root, + x ?: c->esp_path, + c->esp_devid, + y ?: c->xbootldr_path, + c->xbootldr_devid); if (r < 0) return r; + _cleanup_(strv_freep) char **ids = NULL; + if (strv_isempty(_ids)) { + r = id128_get_machine_at(c->root_fd, &c->machine_id); + if (r < 0 && !ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return log_error_errno(r, "Failed to get machine-id: %m"); + + const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT"); + r = boot_entry_token_ensure_at( + e ? XAT_FDROOT : c->root_fd, + e, + c->machine_id, + /* machine_id_is_random= */ false, + &c->entry_token_type, + &c->entry_token); + if (r < 0) + return r; + + r = boot_config_find_oldest_commit( + &config, + c->entry_token, + &ids); + if (r == -ENXIO) + return log_error_errno(r, "No suitable boot menu entry to delete found."); + if (r == -EBUSY) + return log_error_errno(r, "Refusing to remove currently booted boot menu entry."); + if (r < 0) + return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m"); + + STRV_FOREACH(id, ids) + log_info("Will unlink '%s'.", *id); + } else { + ids = strv_copy(_ids); + if (!ids) + return log_oom(); + } + + strv_sort_uniq(ids); + r = 0; - RET_GATHER(r, unlink_entry(&config, arg_esp_path, argv[1])); + if (c->esp_path) + RET_GATHER(r, unlink_entry(&config, x ?: c->esp_path, c->esp_fd, BOOT_ENTRY_ESP, ids, dry_run)); - if (arg_xbootldr_path && xbootldr_devid != esp_devid) - RET_GATHER(r, unlink_entry(&config, arg_xbootldr_path, argv[1])); + if (c->xbootldr_path && c->xbootldr_devid != c->esp_devid) + RET_GATHER(r, unlink_entry(&config, y ?: c->xbootldr_path, c->xbootldr_fd, BOOT_ENTRY_XBOOTLDR, ids, dry_run)); return r; } + +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + assert(argc < 3); + + if (arg_oldest != isempty(argv[1])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Exactly one of an entry ID or --oldest= must be specified."); + + const char *id = empty_to_null(argv[1]); + + _cleanup_(unlink_context_done) UnlinkContext c = UNLINK_CONTEXT_NULL; + r = unlink_context_from_cmdline(&c); + if (r < 0) + return r; + + return run_unlink(&c, STRV_MAKE(id), arg_dry_run); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string); + +typedef struct UnlinkParameters { + UnlinkContext context; + unsigned root_fd_index; + const char *id; + bool oldest; +} UnlinkParameters; + +static void unlink_parameters_done(UnlinkParameters *p) { + assert(p); + + unlink_context_done(&p->context); +} + +int vl_method_unlink( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(unlink_parameters_done) UnlinkParameters p = { + .context = UNLINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 }, + { "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 }, + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 }, + { "oldest", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, oldest), 0 }, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Only allow oldest *or* id to be set */ + if (p.oldest == !!p.id) + return sd_varlink_error_invalid_parameter_name(link, "id"); + if (p.id && !efi_loader_entry_name_valid(p.id)) + return sd_varlink_error_invalid_parameter_name(link, "id"); + + if (p.root_fd_index != UINT_MAX) { + p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index); + if (p.context.root_fd < 0) + return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m"); + + r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor"); + + r = fd_verify_directory(p.context.root_fd); + if (r < 0) + return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m"); + + if (!p.context.root) { + r = fd_get_path(p.context.root_fd, &p.context.root); + if (r < 0) + return log_debug_errno(r, "Failed to get path of file descriptor: %m"); + + if (empty_or_root(p.context.root)) + p.context.root = mfree(p.context.root); + } + } else if (p.context.root) { + p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (p.context.root_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root); + } else + p.context.root_fd = XAT_FDROOT; + + if (p.context.entry_token_type < 0) + p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + + r = find_esp_and_warn_at_full( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.esp_path, + &p.context.esp_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &p.context.esp_devid); + if (r < 0 && r != -ENOKEY) + return r; + r = find_xbootldr_and_warn_at_full( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.xbootldr_path, + &p.context.xbootldr_fd, + /* ret_uuid= */ NULL, + &p.context.xbootldr_devid); + if (r < 0 && r != -ENOKEY) + return r; + + /* Only if we found neither ESP nor XBOOTLDR let's fail. */ + if (!p.context.xbootldr_path && !p.context.esp_path) + return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL); + + r = run_unlink(&p.context, STRV_MAKE(p.id), /* dry_run= */ false); + if (r == -EUNATCH) /* no boot entry token is set */ + return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} diff --git a/src/bootctl/bootctl-unlink.h b/src/bootctl/bootctl-unlink.h index 5c33088859437..728c775d26e8e 100644 --- a/src/bootctl/bootctl-unlink.h +++ b/src/bootctl/bootctl-unlink.h @@ -5,4 +5,8 @@ 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); +int vl_method_unlink(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int boot_config_count_known_files(const BootConfig *config, BootEntrySource source, Hashmap **ret_known_files); + +int boot_entry_unlink(const BootEntry *e, const char *root, int root_fd, Hashmap *known_files, bool dry_run); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 6869e838cfc4e..67a20814daf9e 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -34,6 +34,7 @@ #include "options.h" #include "pager.h" #include "parse-argument.h" +#include "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "string-table.h" @@ -80,6 +81,7 @@ char *arg_certificate_source = NULL; char *arg_private_key = NULL; KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; char *arg_private_key_source = NULL; +bool arg_oldest = false; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -361,7 +363,7 @@ 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, +VERB_SCOPE(, verb_unlink, "unlink", "ID", VERB_ANY, 2, 0, "Remove boot loader entry"); VERB_SCOPE_NOARG(, verb_cleanup, "cleanup", @@ -631,6 +633,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return r; break; + + OPTION_LONG("oldest", "BOOL", + "Delete oldest boot menu entry"): + r = parse_boolean_argument("--oldest=", opts.arg, &arg_oldest); + if (r < 0) + return r; + + break; } char **args = option_parser_get_args(&opts); @@ -700,7 +710,8 @@ static int vl_server(void) { "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware, - "io.systemd.BootControl.Install", vl_method_install); + "io.systemd.BootControl.Install", vl_method_install, + "io.systemd.BootControl.Unlink", vl_method_unlink); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index d0daab9dd12b3..ea097ba329753 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -51,6 +51,7 @@ extern char *arg_certificate_source; extern char *arg_private_key; extern KeySourceType arg_private_key_source_type; extern char *arg_private_key_source; +extern bool arg_oldest; static inline const char* arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index 1207fe8a25826..e850d8982bd30 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -14,6 +14,7 @@ struct in_addr_full; typedef enum AskPasswordFlags AskPasswordFlags; typedef enum BootEntryTokenType BootEntryTokenType; +typedef enum BootEntrySource BootEntrySource; typedef enum BusPrintPropertyFlags BusPrintPropertyFlags; typedef enum BusTransport BusTransport; typedef enum CatFlags CatFlags; @@ -48,6 +49,7 @@ typedef enum UserStorage UserStorage; typedef struct AskPasswordRequest AskPasswordRequest; typedef struct Bitmap Bitmap; typedef struct BootConfig BootConfig; +typedef struct BootEntry BootEntry; typedef struct BPFProgram BPFProgram; typedef struct BusObjectImplementation BusObjectImplementation; typedef struct CalendarSpec CalendarSpec; diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index 70203300ffe5e..42f7b53492e45 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -134,6 +134,19 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"), SD_VARLINK_DEFINE_INPUT(touchVariables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Unlink, + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), + SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The ID of the boot loader entry to remove."), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, remove the oldest entry."), + SD_VARLINK_DEFINE_INPUT(oldest, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR( RebootToFirmwareNotSupported); @@ -143,6 +156,9 @@ static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR( NoESPFound); +static SD_VARLINK_DEFINE_ERROR( + NoDollarBootFound); + static SD_VARLINK_DEFINE_ERROR( BootEntryTokenUnavailable); @@ -170,11 +186,15 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_BootEntryTokenType, SD_VARLINK_SYMBOL_COMMENT("Install the boot loader on the ESP."), &vl_method_Install, + SD_VARLINK_SYMBOL_COMMENT("Unlink a boot menu item"), + &vl_method_Unlink, SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."), &vl_error_RebootToFirmwareNotSupported, SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."), &vl_error_NoSuchBootEntry, SD_VARLINK_SYMBOL_COMMENT("No EFI System Partition (ESP) found."), &vl_error_NoESPFound, - SD_VARLINK_SYMBOL_COMMENT("The select boot entry token could not be determined."), + SD_VARLINK_SYMBOL_COMMENT("Neither ESP nor XBOOTLDR found, hence no $BOOT location identified."), + &vl_error_NoDollarBootFound, + SD_VARLINK_SYMBOL_COMMENT("The selected boot entry token could not be determined."), &vl_error_BootEntryTokenUnavailable); From e48d8a1ea0d3a37939fc2f008a68fe173e219a4b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Feb 2026 23:51:42 +0100 Subject: [PATCH 1398/2155] bootctl: add "link" command --- man/bootctl.xml | 101 ++ shell-completion/bash/bootctl | 7 +- shell-completion/zsh/_bootctl | 5 + src/bootctl/bootctl-link.c | 1206 +++++++++++++++++++ src/bootctl/bootctl-link.h | 8 + src/bootctl/bootctl.c | 122 ++ src/bootctl/bootctl.h | 10 + src/bootctl/meson.build | 1 + src/shared/efi-loader.c | 11 + src/shared/efi-loader.h | 2 + src/shared/varlink-io.systemd.BootControl.c | 47 +- 11 files changed, 1515 insertions(+), 5 deletions(-) create mode 100644 src/bootctl/bootctl-link.c create mode 100644 src/bootctl/bootctl-link.h diff --git a/man/bootctl.xml b/man/bootctl.xml index fb5f4b2b2a9eb..39ddfdd58c06a 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -117,6 +117,36 @@ + + KERNEL + + Creates one or more Type #1 boot loader entries pointing to the specified UKI. Takes + the path to a Unified Kernel Image (UKI) as argument. The UKI is copied into the ESP (or XBOOTLDR + partition if present) below the configured entry token directory, and one or more Boot Loader + Specification Type #1 entries are generated referring to it (one per UKI profile, if multiple + profiles are embedded). + + The title, version, commit number and initial try counter of the generated entries + may be overridden with , , + and . Additional sidecar resources + (system extension images, configuration extension images, credential files) to pass to the UKI at + boot may be specified with . + + If the ESP/XBOOTLDR do not have enough free space for the new boot loader entry and its + referenced resources the oldest existing boot loader entry matching the selected entry token is + removed (along with any resources referenced by it that are no longer referenced by any other + entry). This step is repeated until the new boot loader entry fits. For robustness reasons the + currently booted boot loader entry is never removed, nor is the last existing boot loader + entry. + + By default, the operation refuses to proceed if the resulting ESP/XBOOTLDR free space would + drop below a safety threshold after automatic removal of older entries completes; use + to adjust. + + + + @@ -577,6 +607,77 @@ + + + + When used with link, controls the minimum amount of free space + (in bytes) that must remain on the target partition (ESP or XBOOTLDR) after the new entry has been + materialized. The operation fails if installing the entry would drop the free space below this + threshold. Accepts the usual size suffixes (K, M, G, …). If empty, the built-in default is + restored. If set to zero no minimum amount of free space is kept. + + + + + + + + When used with link, specifies the title of the generated boot + loader entry (the title field of the Type #1 entry). If not specified, a title is + derived from the UKI's embedded metadata. + + + + + + + + When used with link, specifies the version string of the + generated boot loader entry (the version field of the Type #1 entry). If not + specified, the version is derived from the UKI's embedded metadata. Used by the boot loader to sort + and select entries. + + + + + + + + When used with link, specifies the commit number for the generated + boot loader entry. + + + + + + + + + When used with link, registers an additional sidecar resource + file that shall be passed to the UKI at boot. This may be a system extension image + (*.sysext.raw), configuration extension image + (*.confext.raw), or credential file + (*.cred). The file is copied into the ESP/XBOOTLDR alongside the UKI and the + boot loader will load and pass it to the kernel via initrd. This option may be used multiple times + to register more than one extra resource. If passed an empty argument, all previously specified + extras are cleared. + + + + + + + + When used with link, initializes the boot counting + tries-left counter for the generated entry. If set, the resulting boot entry file + is named according to the boot counting scheme described in the Automatic Boot Assessment documentation, + so that the boot loader decreases the counter on each attempted boot and eventually marks the entry + as bad. If not specified, boot counting is not enabled for the generated entry. + + + + diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index 792fc0c0acc83..3b7290c230c14 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -43,7 +43,8 @@ _bootctl() { --efi-boot-option-description --efi-boot-option-description-with-device --secure-boot-auto-enroll --private-key --private-key-source --certificate --certificate-source - --oldest' + --oldest --keep-free --entry-title --entry-version --entry-commit + -X --extra --tries-left' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -62,7 +63,7 @@ _bootctl() { --entry-token) comps="machine-id os-id os-image-id auto literal:" ;; - --image|--root) + --image|--root|-X|--extra) compopt -o nospace comps=$( compgen -A file -- "$cur" ) ;; @@ -89,7 +90,7 @@ _bootctl() { [STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup' [BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink' [BOOLEAN]='reboot-to-firmware' - [FILE]='kernel-identify kernel-inspect' + [FILE]='kernel-identify kernel-inspect link' ) for ((i=0; i < COMP_CWORD; i++)); do diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index c23c1c888dae5..423828ba33243 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -36,6 +36,10 @@ _bootctl_unlink() { _bootctl_comp_ids } +_bootctl_link() { + _files +} + _bootctl_kernel-identify() { _files } @@ -70,6 +74,7 @@ _bootctl_reboot-to-firmware() { "set-timeout:Set the menu timeout" "set-timeout-oneshot:Set the menu timeout for the next boot only" "unlink:Remove boot loader entry" + "link:Add boot loader entry" "cleanup:Remove files in ESP not referenced in any boot entry" "kernel-identify:Identify kernel image type" "kernel-inspect:Print details about the kernel image" diff --git a/src/bootctl/bootctl-link.c b/src/bootctl/bootctl-link.c new file mode 100644 index 0000000000000..6358189e7d2f3 --- /dev/null +++ b/src/bootctl/bootctl-link.c @@ -0,0 +1,1206 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "boot-entry.h" +#include "bootctl.h" +#include "bootctl-link.h" +#include "bootctl-unlink.h" +#include "bootspec.h" +#include "bootspec-util.h" +#include "chase.h" +#include "copy.h" +#include "dirent-util.h" +#include "efi-loader.h" +#include "env-file.h" +#include "errno-util.h" +#include "fd-util.h" +#include "find-esp.h" +#include "format-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "id128-util.h" +#include "io-util.h" +#include "json-util.h" +#include "kernel-image.h" +#include "log.h" +#include "parse-argument.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "uki.h" +#include "utf8.h" + +/* Keeps track of an "extra" file to associate with the type 1 entries to generate */ +typedef struct ExtraFile { + /* The source and the temporary file we copy it into */ + int source_fd, temp_fd; + char *filename, *temp_filename; +} ExtraFile; + +#define EXTRA_FILE_NULL \ + (const ExtraFile) { \ + .source_fd = -EBADF, \ + .temp_fd = -EBADF, \ + } + +/* Keeps track of a specific UKI profile we need to generate a type entry for */ +typedef struct Profile { + /* The final and the temporary file for the .conf entry file, while we write it */ + char *entry_filename, *entry_temp_filename; + int entry_temp_fd; +} Profile; + +typedef struct LinkContext { + char *root; + int root_fd; + + sd_id128_t machine_id; + BootEntryTokenType entry_token_type; + char *entry_token; + + char *entry_title; + char *entry_version; + uint64_t entry_commit; + + BootEntrySource dollar_boot_source; + char *dollar_boot_path; + int dollar_boot_fd; + int entry_token_dir_fd; + int loader_entries_dir_fd; + + /* The UKI source and temporary target while we write it. Note that for now we exclusively support + * UKIs, but let's keep things somewhat generic to keep options open for the future. */ + char *kernel_filename, *kernel_temp_filename; + int kernel_fd, kernel_temp_fd; + + ExtraFile *extra; + size_t n_extra; + + Profile *profiles; + size_t n_profiles; + + unsigned tries_left; + + uint64_t keep_free; + + char **linked_ids; +} LinkContext; + +#define LINK_CONTEXT_NULL \ + (LinkContext) { \ + .root_fd = -EBADF, \ + .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \ + .dollar_boot_fd = -EBADF, \ + .loader_entries_dir_fd = -EBADF, \ + .entry_token_dir_fd = -EBADF, \ + .kernel_fd = -EBADF, \ + .kernel_temp_fd = -EBADF, \ + .tries_left = UINT_MAX, \ + .keep_free = UINT64_MAX, \ + } + +static void extra_file_done(ExtraFile *x) { + assert(x); + + x->source_fd = safe_close(x->source_fd); + x->temp_fd = safe_close(x->temp_fd); + x->filename = mfree(x->filename); + x->temp_filename = mfree(x->temp_filename); +} + +static void profile_done(Profile *p) { + assert(p); + + p->entry_filename = mfree(p->entry_filename); + p->entry_temp_filename = mfree(p->entry_temp_filename); + p->entry_temp_fd = safe_close(p->entry_temp_fd); +} + +static void link_context_unlink_temporary(LinkContext *c) { + assert(c); + + if (c->kernel_temp_filename) { + if (c->entry_token_dir_fd >= 0) + (void) unlinkat(c->entry_token_dir_fd, c->kernel_temp_filename, /* flags= */ 0); + + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + } + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + if (!x->temp_filename) + continue; + + if (c->entry_token_dir_fd >= 0) + (void) unlinkat(c->entry_token_dir_fd, x->temp_filename, /* flags= */ 0); + + x->temp_fd = safe_close(x->temp_fd); + x->temp_filename = mfree(x->temp_filename); + } + + FOREACH_ARRAY(p, c->profiles, c->n_profiles) { + if (!p->entry_temp_filename) + continue; + + if (c->loader_entries_dir_fd >= 0) + (void) unlinkat(c->loader_entries_dir_fd, p->entry_temp_filename, /* flags= */ 0); + + p->entry_temp_fd = safe_close(p->entry_temp_fd); + p->entry_temp_filename = mfree(p->entry_temp_filename); + } +} + +static void link_context_clear_profiles(LinkContext *c) { + assert(c); + + FOREACH_ARRAY(p, c->profiles, c->n_profiles) + profile_done(p); + + c->profiles = mfree(c->profiles); + c->n_profiles = 0; +} + +static void link_context_done(LinkContext *c) { + assert(c); + + link_context_unlink_temporary(c); + + FOREACH_ARRAY(x, c->extra, c->n_extra) + extra_file_done(x); + + c->extra = mfree(c->extra); + c->n_extra = 0; + + link_context_clear_profiles(c); + + c->kernel_filename = mfree(c->kernel_filename); + c->kernel_fd = safe_close(c->kernel_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + + c->root = mfree(c->root); + c->root_fd = safe_close(c->root_fd); + + c->entry_token = mfree(c->entry_token); + c->entry_title = mfree(c->entry_title); + c->entry_version = mfree(c->entry_version); + + c->dollar_boot_path = mfree(c->dollar_boot_path); + c->dollar_boot_fd = safe_close(c->dollar_boot_fd); + c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd); + c->loader_entries_dir_fd = safe_close(c->loader_entries_dir_fd); + + c->linked_ids = strv_free(c->linked_ids); +} + +static int link_context_from_cmdline(LinkContext *ret, const char *kernel) { + int r; + + assert(ret); + assert(kernel); + + _cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL; + b.entry_token_type = arg_entry_token_type; + b.tries_left = arg_tries_left; + b.entry_commit = arg_entry_commit; + b.keep_free = arg_keep_free; + + if (strdup_to(&b.entry_token, arg_entry_token) < 0 || + strdup_to(&b.entry_title, arg_entry_title) < 0 || + strdup_to(&b.entry_version, arg_entry_version) < 0) + return log_oom(); + + if (arg_root) { + b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (b.root_fd < 0) + return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root); + + if (strdup_to(&b.root, arg_root) < 0) + return log_oom(); + } else + b.root_fd = XAT_FDROOT; + + r = path_extract_filename(kernel, &b.kernel_filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel); + if (!efi_loader_entry_resource_filename_valid(b.kernel_filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel); + b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID); + if (b.kernel_fd < 0) + return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel); + + KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID; + r = inspect_kernel(b.kernel_fd, /* filename= */ NULL, &kit); + if (r == -EBADMSG) + return log_error_errno(r, "Kernel image '%s' is not valid.", kernel); + if (r < 0) + return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", kernel); + if (kit != KERNEL_IMAGE_TYPE_UKI) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Kernel image '%s' is not a UKI.", kernel); + + STRV_FOREACH(x, arg_extras) { + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(*x, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x); + + _cleanup_close_ int fd = -EBADF; + fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID); + if (fd < 0) + return log_error_errno(fd, "Failed to open '%s': %m", *x); + + if (!GREEDY_REALLOC(b.extra, b.n_extra+1)) + return log_oom(); + + b.extra[b.n_extra++] = (ExtraFile) { + .source_fd = TAKE_FD(fd), + .filename = TAKE_PTR(fn), + .temp_fd = -EBADF, + }; + } + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + &b.dollar_boot_fd, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) + return r; + if (r > 0) { /* XBOOTLDR has been found */ + assert(arg_xbootldr_path); + + if (arg_root) { + const char *e = path_startswith(arg_xbootldr_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root); + + r = strdup_to(&b.dollar_boot_path, e); + } else + r = strdup_to(&b.dollar_boot_path, arg_xbootldr_path); + if (r < 0) + return log_oom(); + + b.dollar_boot_source = BOOT_ENTRY_XBOOTLDR; + } else { + /* No XBOOTLDR has been found, look for ESP */ + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + &b.dollar_boot_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) + return r; + + assert(arg_esp_path); + + if (arg_root) { + const char *e = path_startswith(arg_esp_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root); + + r = strdup_to(&b.dollar_boot_path, e); + } else + r = strdup_to(&b.dollar_boot_path, arg_esp_path); + if (r < 0) + return log_oom(); + + b.dollar_boot_source = BOOT_ENTRY_ESP; + } + + *ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL); + return 0; +} + +static int link_context_load_etc_machine_id(LinkContext *c) { + int r; + + assert(c); + + r = id128_get_machine_at(c->root_fd, &c->machine_id); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get machine-id: %m"); + + log_debug("Loaded machine ID %s from '%s/etc/machine-id'.", SD_ID128_TO_STRING(c->machine_id), strempty(c->root)); + return 0; +} + +static int link_context_pick_entry_token(LinkContext *c) { + int r; + + assert(c); + + r = link_context_load_etc_machine_id(c); + if (r < 0) + return r; + + const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT"); + r = boot_entry_token_ensure_at( + e ? XAT_FDROOT : c->root_fd, + e, + c->machine_id, + /* machine_id_is_random= */ false, + &c->entry_token_type, + &c->entry_token); + if (r < 0) + return r; + + log_debug("Using entry token: %s", c->entry_token); + return 0; +} + +static int begin_copy_file( + int source_fd, + const char *filename, + int target_dir_fd, + int *ret_tmpfile_fd, + char **ret_tmpfile_filename) { + + int r; + + assert(source_fd >= 0); + assert(filename); + assert(target_dir_fd >= 0); + assert(ret_tmpfile_fd); + assert(ret_tmpfile_filename); + + if (faccessat(target_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists already: %m", filename); + } else { + log_info("'%s' already in place, not copying.", filename); + + *ret_tmpfile_fd = -EBADF; + *ret_tmpfile_filename = NULL; + return 0; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int write_fd = open_tmpfile_linkable_at(target_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t); + if (write_fd < 0) + return log_error_errno(write_fd, "Failed to create '%s': %m", filename); + + CLEANUP_TMPFILE_AT(target_dir_fd, t); + + 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 into '%s': %m", filename); + + (void) copy_times(source_fd, write_fd, /* flags= */ 0); + (void) fchmod(write_fd, 0644); + + *ret_tmpfile_fd = TAKE_FD(write_fd); + *ret_tmpfile_filename = TAKE_PTR(t); + + return 1; +} + +static int begin_write_entry_file( + LinkContext *c, + unsigned profile_nr, + const char *osrelease_text, + const char *profile_text, + Profile *ret) { + + int r; + + assert(c); + assert(osrelease_text); + assert(ret); + + assert(c->entry_token); + assert(c->kernel_filename); + assert(c->loader_entries_dir_fd >= 0); + + _cleanup_free_ char *good_name = NULL, *good_sort_key = NULL, *os_version_id = NULL, *image_version = NULL; + r = bootspec_extract_osrelease( + osrelease_text, + /* These three fields are used by systemd-stub for showing entries + sorting them */ + &good_name, /* human readable */ + /* ret_good_version= */ NULL, + &good_sort_key, + /* These four fields are the raw fields provided in os-release */ + /* ret_os_id= */ NULL, + &os_version_id, + /* ret_image_id= */ NULL, + &image_version); + if (r < 0) + return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image, refusing."); + + assert(good_name); /* This one is the only field guaranteed to be defined once the above succeeds */ + + _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; + if (profile_text) { + r = parse_env_data( + profile_text, /* size= */ SIZE_MAX, + ".profile", + "ID", &profile_id, + "TITLE", &profile_title); + if (r < 0) + return log_error_errno(r, "Failed to parse profile data from unified kernel image: %m"); + } + + const char *version = c->entry_version ?: image_version ?: os_version_id; + + _cleanup_free_ char *filename = NULL; + r = boot_entry_make_commit_filename( + c->entry_token, + c->entry_commit, + version, + profile_nr, + c->tries_left, + &filename); + if (r < 0) + return log_error_errno(r, "Failed to generate filename for entry file: %m"); + + if (faccessat(c->loader_entries_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", filename); + } else + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Boot menu entry '%s' exists already, refusing.", filename); + + log_info("Writing new boot menu entry '%s/loader/entries/%s' for profile %u.", c->dollar_boot_path, filename, profile_nr); + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int write_fd = open_tmpfile_linkable_at(c->loader_entries_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t); + if (write_fd < 0) + return log_error_errno(write_fd, "Failed to create '%s': %m", filename); + + CLEANUP_TMPFILE_AT(c->loader_entries_dir_fd, t); + + _cleanup_free_ char *_title = NULL; + const char *title; + if (profile_title || profile_id) { + _title = strjoin(c->entry_title ?: good_name, " (", profile_title ?: profile_id, ")"); + if (!_title) + return log_oom(); + + title = _title; + } else if (profile_nr > 0) { + _title = asprintf_safe("%s (Profile #%u)", c->entry_title ?: good_name, profile_nr); + if (!_title) + return log_oom(); + + title = _title; + } else + title = c->entry_title ?: good_name; + + /* Do some validation that this will result in a valid type #1 entry before we write this out */ + if (string_has_cc(title, /* ok= */ NULL) || !utf8_is_valid(title)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to generate valid title for new commit: %s", title); + if (string_has_cc(c->kernel_filename, /* ok= */ NULL) || !utf8_is_valid(c->kernel_filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI filename is not suitable for inclusion in new commit: %s", c->kernel_filename); + + _cleanup_free_ char *text = NULL; + if (asprintf(&text, + "title %s\n" + "uki /%s/%s\n" + "version %" PRIu64 "%s%s\n", + title, + c->entry_token, c->kernel_filename, + c->entry_commit, isempty(version) ? "" : ".", strempty(version)) < 0) + return log_oom(); + + if (good_sort_key && strextendf(&text, "sort-key %s\n", good_sort_key) < 0) + return log_oom(); + + if (profile_nr > 0 && strextendf(&text, "profile %u\n", profile_nr) < 0) + return log_oom(); + + if (!sd_id128_is_null(c->machine_id) && strextendf(&text, "machine-id " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(c->machine_id)) < 0) + return log_oom(); + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + if (string_has_cc(x->filename, /* ok= */ NULL) || !utf8_is_valid(x->filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename is not suitable for inclusion in new commit: %s", x->filename); + + if (strextendf(&text, + "extra /%s/%s\n", + c->entry_token, + x->filename) < 0) + return log_oom(); + } + + r = loop_write(write_fd, text, /* nbytes= */ SIZE_MAX); + if (r < 0) + return log_error_errno(r, "Failed to write entry file: %m"); + + *ret = (Profile) { + .entry_filename = TAKE_PTR(filename), + .entry_temp_filename = TAKE_PTR(t), + .entry_temp_fd = TAKE_FD(write_fd), + }; + + return 0; +} + +static int finalize_file( + const char *filename, + int target_dir_fd, + int tmpfile_fd, + const char *tmpfile_filename) { + + int r; + + assert(filename); + assert(target_dir_fd >= 0); + + if (tmpfile_fd < 0) /* If the file already existed, we don't move anything into place. */ + return 0; + + r = link_tmpfile_at(tmpfile_fd, target_dir_fd, tmpfile_filename, filename, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC); + if (r < 0) + return log_error_errno(r, "Failed to move from '%s' into place: %m", filename); + + log_info("Installed '%s' into place.", filename); + return 1; +} + +static int link_context_pick_entry_commit(LinkContext *c) { + int r; + + assert(c); + assert(c->loader_entries_dir_fd >= 0); + assert(c->entry_token); + + /* Already have a commit nr? */ + if (c->entry_commit != 0) + return 0; + + _cleanup_close_ int opened_fd = fd_reopen(c->loader_entries_dir_fd, O_DIRECTORY|O_CLOEXEC); + if (opened_fd < 0) + return log_error_errno(opened_fd, "Failed to reopen loader entries dir: %m"); + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(opened_fd, RECURSE_DIR_IGNORE_DOT, &dentries); + if (r < 0) + return log_error_errno(r, "Failed to read loader entries directory: %m"); + + uint64_t m = 0; /* largest commit number seen */ + FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) { + const struct dirent *de = *i; + + /* We look for files named -commit_[.][.p].conf */ + + if (!dirent_is_file(de)) + continue; + + if (!efi_loader_entry_name_valid(de->d_name)) + continue; + + _cleanup_free_ char *et = NULL; + uint64_t ec; + r = boot_entry_parse_commit_filename(de->d_name, &et, &ec); + if (r < 0) { + log_debug_errno(r, "Cannot extract entry token/commit number from '%s', ignoring.", de->d_name); + continue; + } + + if (!streq(c->entry_token, et)) + continue; + + log_debug("Found existing commit %" PRIu64 ".", ec); + if (ec > m) + m = ec; + } + + assert(m < UINT64_MAX); + uint64_t next = m + 1; + + if (!entry_commit_valid(next)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many commits already in place, refusing."); + + log_debug("Picking commit %" PRIu64 " for new commit.", next); + c->entry_commit = next; + return 0; +} + +static int clean_temporary_files(int fd) { + int r; + + assert(fd >= 0); + + /* Before we create any new files let's clear any possible left-overs from a previous run. We look + * specifically for all temporary files whose name starts with .# because that's what we create, via + * open_tmpfile_linkable_at(). + * + * Ideally, this would not be necessary because O_TMPFILE would ensure that files are not + * materialized before they are fully written. However, vfat currently does not support O_TMPFILE, + * hence we need to clean things up manually. */ + + _cleanup_close_ int dfd = fd_reopen(fd, O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open directory: %m"); + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dfd, RECURSE_DIR_ENSURE_TYPE, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate contents of directory: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *e = *i; + + if (e->d_type != DT_REG) + continue; + + if (!startswith_no_case(e->d_name, ".#")) + continue; + + if (unlinkat(dfd, e->d_name, /* flags= */ 0) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to remove temporary file '%s', ignoring: %m", e->d_name); + } + + return 0; +} + +static int link_context_unlink_oldest(LinkContext *c) { + int r; + + assert(c); + + /* We only load the entries from the partition we want to make space on (!) */ + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + r = boot_config_load_and_select( + &config, + c->root, + c->dollar_boot_source == BOOT_ENTRY_ESP ? c->dollar_boot_path : NULL, + /* esp_devid= */ 0, + c->dollar_boot_source == BOOT_ENTRY_XBOOTLDR ? c->dollar_boot_path : NULL, + /* xbootldr_devid= */ 0); + if (r < 0) + return r; + + _cleanup_(strv_freep) char **ids = NULL; + r = boot_config_find_oldest_commit( + &config, + c->entry_token, + &ids); + if (r == -ENXIO) + return log_error_errno(r, "No suitable boot menu entry to delete found."); + if (r == -EBUSY) + return log_error_errno(r, "Refusing to remove currently booted boot menu entry."); + if (r < 0) + return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m"); + + _cleanup_(hashmap_freep) Hashmap *known_files = NULL; + r = boot_config_count_known_files(&config, c->dollar_boot_source, &known_files); + if (r < 0) + return r; + + int ret = 0; + STRV_FOREACH(id, ids) { + const BootEntry *entry = boot_config_find_entry(&config, *id); + if (!entry) + continue; + + RET_GATHER(ret, boot_entry_unlink(entry, c->dollar_boot_path, c->dollar_boot_fd, known_files, /* dry_run= */ false)); + } + + if (ret < 0) + return ret; + + return 1; +} + +static int verify_keep_free(LinkContext *c) { + int r; + + assert(c); + + if (c->keep_free == 0) + return 0; + + uint64_t f; + r = vfs_free_bytes(ASSERT_FD(c->dollar_boot_fd), &f); + if (r < 0) + return log_error_errno(r, "Failed to statvfs() the $BOOT partition: %m"); + + if (f < c->keep_free) + return log_error_errno( + SYNTHETIC_ERRNO(EDQUOT), + "Not installing boot menu entry, free space after installation of %s would be below configured keep free size %s.", + FORMAT_BYTES(f), FORMAT_BYTES(c->keep_free)); + + return 0; +} + +static int run_link_now(LinkContext *c) { + int r; + + assert(c); + assert(c->dollar_boot_fd >= 0); + + _cleanup_free_ char *j = path_join(empty_to_root(c->root), c->dollar_boot_path); + if (!j) + return log_oom(); + + if (c->loader_entries_dir_fd < 0) { + r = chaseat(/* root_fd= */ c->dollar_boot_fd, + /* dir_fd= */ c->dollar_boot_fd, + "loader/entries", + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &c->loader_entries_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to pin '/loader/entries' directory below '%s': %m", j); + } + + /* Remove any left-overs from an earlier run before we write new stuff */ + (void) clean_temporary_files(c->loader_entries_dir_fd); + + r = link_context_pick_entry_commit(c); + if (r < 0) + return r; + + log_info("Will create commit %" PRIu64 ".", c->entry_commit); + + if (c->entry_token_dir_fd < 0) { + r = chaseat(/* root_fd= */ c->dollar_boot_fd, + /* dir_fd= */ c->dollar_boot_fd, + c->entry_token, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &c->entry_token_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to pin '/%s' directory below '%s': %m", c->entry_token, j); + } + + /* As above */ + (void) clean_temporary_files(c->entry_token_dir_fd); + + /* Synchronize everything to disk before we verify the disk space, to ensure the counters are + * accurate (some file systems delay accurate counters) */ + (void) syncfs(c->dollar_boot_fd); + + /* Before we start copying things, let's see if there's even a remote chance to get this copied + * in. Note that we do not try to be overly smart here, i.e. we do not try to calculate how much + * extra space we'll need here. Doing that is not trivial since after all the same resources can be + * referenced by multiple entries, which makes copying them multiple times unnecessary. */ + r = verify_keep_free(c); + if (r < 0) + return r; + + for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) { + _cleanup_free_ char *osrelease = NULL, *profile = NULL; + r = pe_find_uki_sections(c->kernel_fd, j, p, &osrelease, &profile, /* ret_cmdline= */ NULL); + if (r < 0) + return r; + if (r == 0) /* this profile does not exist, we are done */ + break; + + if (!GREEDY_REALLOC(c->profiles, c->n_profiles+1)) + return log_oom(); + + r = begin_write_entry_file( + c, + p, + osrelease, + profile, + c->profiles + c->n_profiles); + if (r < 0) + return r; + + c->n_profiles++; + } + + if (c->n_profiles == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "UKI with no valid profile, refusing."); + + r = begin_copy_file( + c->kernel_fd, + c->kernel_filename, + c->entry_token_dir_fd, + &c->kernel_temp_fd, + &c->kernel_temp_filename); + if (r < 0) + return r; + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + r = begin_copy_file( + x->source_fd, + x->filename, + c->entry_token_dir_fd, + &x->temp_fd, + &x->temp_filename); + if (r < 0) + return r; + } + + /* We copied all files into place, but they are not materialized yet. Let's ensure the data hits the + * disk before we proceed */ + (void) syncfs(c->dollar_boot_fd); + + /* Before we materialize things, let's ensure the space to keep free is not taken */ + r = verify_keep_free(c); + if (r < 0) + return r; + + /* We successfully managed to put all resources we need into the $BOOT partition. Now, let's + * "materialize" them by linking them into the file system. Before this point we'd get rid of every + * file we created on error again. But from now on we switch modes: what we manage to move into place + * we leave in place even on error. These are not lost resources after all, the GC logic implemented + * by "bootctl cleanup" will take care of removing things again if necessary. */ + + r = finalize_file( + c->kernel_filename, + c->entry_token_dir_fd, + c->kernel_temp_fd, + c->kernel_temp_filename); + if (r < 0) + return r; + + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + r = finalize_file( + x->filename, + c->entry_token_dir_fd, + x->temp_fd, + x->temp_filename); + if (r < 0) + return r; + + x->temp_fd = safe_close(x->temp_fd); + x->temp_filename = mfree(x->temp_filename); + } + + /* Finally, after all our resources are in place, also materialize the menu entry files themselves */ + FOREACH_ARRAY(profile, c->profiles, c->n_profiles) { + r = finalize_file( + profile->entry_filename, + c->loader_entries_dir_fd, + profile->entry_temp_fd, + profile->entry_temp_filename); + if (r < 0) + return r; + + profile->entry_temp_fd = safe_close(profile->entry_temp_fd); + profile->entry_temp_filename = mfree(profile->entry_temp_filename); + + _cleanup_free_ char *stripped = NULL; + r = boot_filename_extract_tries( + profile->entry_filename, + &stripped, + /* ret_tries_left= */ NULL, + /* ret_tries_done= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to extract tries counters from id '%s'", profile->entry_filename); + + if (strv_consume(&c->linked_ids, TAKE_PTR(stripped)) < 0) + return log_oom(); + } + + (void) syncfs(c->dollar_boot_fd); + return 0; +} + +static int run_link(LinkContext *c) { + int r; + + assert(c); + assert(c->dollar_boot_path); + assert(c->dollar_boot_fd >= 0); + + if (c->keep_free == UINT64_MAX) + c->keep_free = KEEP_FREE_BYTES_DEFAULT; + + r = link_context_pick_entry_token(c); + if (r < 0) + return r; + + unsigned n_removals = 0; + for (;;) { + r = run_link_now(c); + if (r < 0) { + if (!ERRNO_IS_NEG_DISK_SPACE(r)) + return r; + } else + break; + + log_notice("Attempt to link entry failed due to exhausted disk space, trying to remove oldest boot menu entry."); + + link_context_unlink_temporary(c); + link_context_clear_profiles(c); + + if (link_context_unlink_oldest(c) <= 0) { + log_warning("Attempted to make space on $BOOT, but this failed, attempt to link entry failed."); + return r; /* propagate original error */ + } + + /* Close entry token dir here, quite possible the unlinking above might have removed it too, in case it was empty */ + c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd); + + log_info("Removing oldest boot menu entry succeeded, will retry to create boot loader menu entry."); + n_removals++; + } + + _cleanup_free_ char *j = strv_join(c->linked_ids, "', '"); + if (!j) + return log_oom(); + + if (n_removals > 0) + log_info("Successfully installed boot loader entries '%s', after removing %u old entries.", j, n_removals); + else + log_info("Successfully installed boot loader entries '%s'.", j); + + return 0; +} + +int verb_link(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc == 2); + + _cleanup_free_ char *x = NULL; + r = parse_path_argument(argv[1], /* suppress_root= */ false, &x); + if (r < 0) + return r; + + _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL; + r = link_context_from_cmdline(&c, x); + if (r < 0) + return r; + + return run_link(&c); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string); + +typedef struct LinkParameters { + LinkContext context; + unsigned root_fd_index; + unsigned kernel_fd_index; + sd_varlink *link; +} LinkParameters; + +static void link_parameters_done(LinkParameters *p) { + assert(p); + + link_context_done(&p->context); +} + +typedef struct ExtraParameters { + ExtraFile extra_file; + unsigned fd_index; +} ExtraParameters; + +static void extra_parameters_done(ExtraParameters *p) { + assert(p); + + extra_file_done(&p->extra_file); +} + +static int json_dispatch_loader_entry_resource_filename(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + char **n = ASSERT_PTR(userdata); + const char *filename; + int r; + + assert(variant); + + r = json_dispatch_const_filename(name, variant, flags, &filename); + if (r < 0) + return r; + + if (filename && !efi_loader_entry_resource_filename_valid(filename)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid boot entry resource filename.", strna(name)); + + if (free_and_strdup(n, filename) < 0) + return json_log_oom(variant, flags); + + return 0; +} + +static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + LinkParameters *c = ASSERT_PTR(userdata); + int r; + + if (!sd_json_variant_is_array(v)) + return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + _cleanup_(extra_parameters_done) ExtraParameters xp = { + .extra_file = EXTRA_FILE_NULL, + .fd_index = UINT_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "filename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename), SD_JSON_MANDATORY }, + { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(i, dispatch_table, /* flags= */ 0, &xp); + if (r < 0) + return r; + + xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index); + if (xp.extra_file.source_fd < 0) + return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m"); + + r = fd_verify_safe_flags(xp.extra_file.source_fd); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(c->link, name); + + r = fd_verify_regular(xp.extra_file.source_fd); + if (r < 0) + return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m"); + + if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1)) + return log_oom(); + + c->context.extra[c->context.n_extra++] = TAKE_GENERIC(xp.extra_file, ExtraFile, EXTRA_FILE_NULL); + } + + return 0; +} + +int vl_method_link( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(link_parameters_done) LinkParameters p = { + .context = LINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + .kernel_fd_index = UINT_MAX, + .link = link, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 }, + { "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 }, + { "entryTitle", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_title), 0 }, + { "entryVersion", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_version), 0 }, + { "entryCommit", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, voffsetof(p, context.entry_commit), 0 }, + { "kernelFilename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, voffsetof(p, context.kernel_filename), SD_JSON_MANDATORY }, + { "kernelFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, kernel_fd_index), SD_JSON_MANDATORY }, + { "extraFiles", SD_JSON_VARIANT_ARRAY, dispatch_extras, 0, 0 }, + { "triesLeft", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, context.tries_left), 0 }, + { "keepFree", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, context.keep_free), 0 }, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.root_fd_index != UINT_MAX) { + p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index); + if (p.context.root_fd < 0) + return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m"); + + r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor"); + + r = fd_verify_directory(p.context.root_fd); + if (r < 0) + return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m"); + + if (!p.context.root) { + r = fd_get_path(p.context.root_fd, &p.context.root); + if (r < 0) + return log_debug_errno(r, "Failed to get path of file descriptor: %m"); + + if (empty_or_root(p.context.root)) + p.context.root = mfree(p.context.root); + } + } else if (p.context.root) { + p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (p.context.root_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root); + } else + p.context.root_fd = XAT_FDROOT; + + if (p.context.entry_token_type < 0) + p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + + if (p.context.entry_title && !efi_loader_entry_title_valid(p.context.entry_title)) + return sd_varlink_error_invalid_parameter_name(link, "entryTitle"); + + if (p.context.entry_version && !version_is_valid_versionspec(p.context.entry_version)) + return sd_varlink_error_invalid_parameter_name(link, "entryVersion"); + + if (p.context.entry_commit != 0 && !entry_commit_valid(p.context.entry_commit)) + return sd_varlink_error_invalid_parameter_name(link, "entryCommit"); + + p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index); + if (p.context.kernel_fd < 0) + return log_debug_errno(p.context.kernel_fd, "Failed to acquire kernel fd from Varlink: %m"); + + r = fd_verify_safe_flags(p.context.kernel_fd); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "kernelFileDescriptor"); + r = fd_verify_regular(p.context.kernel_fd); + if (r < 0) + return log_debug_errno(r, "Failed to validate that kernel image file is a regular file descriptor: %m"); + + /* Refuse non-UKIs for now. */ + KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID; + r = inspect_kernel(p.context.kernel_fd, /* filename= */ NULL, &kit); + if (r == -EBADMSG) + return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL); + if (r < 0) + return r; + if (kit != KERNEL_IMAGE_TYPE_UKI) + return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL); + + r = find_xbootldr_and_warn_at( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.dollar_boot_path, + &p.context.dollar_boot_fd); + if (r < 0) { + if (r != -ENOKEY) + return r; + + /* No XBOOTLDR found, let's look for ESP then. */ + + r = find_esp_and_warn_at( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.dollar_boot_path, + &p.context.dollar_boot_fd); + if (r == -ENOKEY) + return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL); + if (r < 0) + return r; + + p.context.dollar_boot_source = BOOT_ENTRY_ESP; + } else + p.context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR; + + r = run_link(&p.context); + if (r == -EUNATCH) /* no boot entry token is set */ + return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL); + if (r < 0) + return r; + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p.context.linked_ids)); +} diff --git a/src/bootctl/bootctl-link.h b/src/bootctl/bootctl-link.h new file mode 100644 index 0000000000000..de64563b87c7f --- /dev/null +++ b/src/bootctl/bootctl-link.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_link(int argc, char *argv[], uintptr_t data, void *userdata); + +int vl_method_link(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 67a20814daf9e..c627a7dd077d5 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -9,6 +9,7 @@ #include "bootctl.h" #include "bootctl-cleanup.h" #include "bootctl-install.h" +#include "bootctl-link.h" #include "bootctl-random-seed.h" #include "bootctl-reboot-to-firmware.h" #include "bootctl-set-efivar.h" @@ -16,6 +17,7 @@ #include "bootctl-uki.h" #include "bootctl-unlink.h" #include "bootctl-util.h" +#include "bootspec-util.h" #include "build.h" #include "crypto-util.h" #include "devnum-util.h" @@ -82,6 +84,12 @@ char *arg_private_key = NULL; KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; char *arg_private_key_source = NULL; bool arg_oldest = false; +uint64_t arg_keep_free = KEEP_FREE_BYTES_DEFAULT; +char *arg_entry_title = NULL; +char *arg_entry_version = NULL; +uint64_t arg_entry_commit = 0; +char **arg_extras = NULL; +unsigned arg_tries_left = UINT_MAX; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -95,6 +103,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_title, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_version, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extras, strv_freep); static const char* const install_source_table[_INSTALL_SOURCE_MAX] = { [INSTALL_SOURCE_IMAGE] = "image", @@ -366,6 +377,9 @@ VERB_SCOPE_NOARG(, verb_list, "list", VERB_SCOPE(, verb_unlink, "unlink", "ID", VERB_ANY, 2, 0, "Remove boot loader entry"); +VERB_SCOPE(, verb_link, "link", "KERNEL", 2, 2, 0, + "Create boot loader entry for specified kernel"); + VERB_SCOPE_NOARG(, verb_cleanup, "cleanup", "Remove files in ESP not referenced in any boot entry"); @@ -641,8 +655,115 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; + + OPTION_LONG("keep-free", "BYTES", + "How much space to keep free on ESP/XBOOTLDR"): + + if (isempty(opts.arg)) + arg_keep_free = KEEP_FREE_BYTES_DEFAULT; + else { + r = parse_size(opts.arg, 1024, &arg_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-free=: %s", opts.arg); + } + + break; + + OPTION_LONG("entry-title", "TITLE", + "Selects the entry title for the new boot menu entry"): + + if (isempty(opts.arg)) { + arg_entry_title = mfree(arg_entry_title); + break; + } + + if (!efi_loader_entry_title_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry title: %s", opts.arg); + + r = free_and_strdup_warn(&arg_entry_title, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("entry-version", "VERSION", + "Selects the entry version for the new boot menu entry"): + if (isempty(opts.arg)) { + arg_entry_version = mfree(arg_entry_version); + break; + } + + if (!version_is_valid_versionspec(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry version: %s", opts.arg); + + r = free_and_strdup_warn(&arg_entry_version, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("entry-commit", "NR", + "Selects the entry commit version for the new boot menu entry"): { + if (isempty(opts.arg)) { + arg_entry_commit = 0; + break; + } + + uint64_t n; + r = safe_atou64(opts.arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse --entry-commit= parameter: %s", opts.arg); + if (!entry_commit_valid(n)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid entry commit number."); + + arg_entry_commit = n; + break; + } + + OPTION('X', "extra", "PATH", + "Pass extra resource (confext, sysext, credential) to the invoked UKI of the boot menu entry"): { + + if (isempty(opts.arg)) { + arg_extras = strv_free(arg_extras); + break; + } + + _cleanup_free_ char *x = NULL; + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &x); + if (r < 0) + return r; + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(x, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", x); + if (!efi_loader_entry_resource_filename_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename '%s' is not suitable for reference in a boot menu entry.", fn); + + r = strv_consume(&arg_extras, TAKE_PTR(x)); + if (r < 0) + return log_oom(); + + strv_uniq(arg_extras); + break; } + OPTION_LONG("tries-left", "NR", + "Set boot menu entries tries-left counter to the specified value"): { + if (isempty(opts.arg)) { + arg_tries_left = UINT_MAX; + break; + } + + unsigned u; + r = safe_atou(opts.arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse tries left counter: %s", opts.arg); + if (u >= UINT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Tries left counter too large, refusing: %u", u); + + arg_tries_left = u; + break; + }} + char **args = option_parser_get_args(&opts); 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) @@ -711,6 +832,7 @@ static int vl_server(void) { "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware, "io.systemd.BootControl.Install", vl_method_install, + "io.systemd.BootControl.Link", vl_method_link, "io.systemd.BootControl.Unlink", vl_method_unlink); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index ea097ba329753..b478ae0ec1d1a 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -52,6 +52,12 @@ extern char *arg_private_key; extern KeySourceType arg_private_key_source_type; extern char *arg_private_key_source; extern bool arg_oldest; +extern uint64_t arg_keep_free; +extern char *arg_entry_title; +extern char *arg_entry_version; +extern uint64_t arg_entry_commit; +extern char **arg_extras; +extern unsigned arg_tries_left; static inline const char* arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ @@ -68,3 +74,7 @@ int acquire_xbootldr(int unprivileged_mode, int *ret_fd, sd_id128_t *ret_uuid, d * string, but we limit the length to something reasonable to prevent from the firmware * having to deal with a potentially too long string. */ #define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255) + +/* Before we "materialize" a new entry, let's ensure we have this much space free still on the partition, by + * default */ +#define KEEP_FREE_BYTES_DEFAULT (5U * U64_MB) diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index ff33cde3f615b..06137bdae00ce 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -3,6 +3,7 @@ bootctl_sources = files( 'bootctl.c', 'bootctl-install.c', + 'bootctl-link.c', 'bootctl-random-seed.c', 'bootctl-reboot-to-firmware.c', 'bootctl-set-efivar.c', diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index ce10a44d34ccc..20e4719bb067e 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -433,3 +433,14 @@ bool efi_loader_entry_name_valid(const char *s) { return in_charset(s, ALPHANUMERICAL "+-_.@"); } + +bool efi_loader_entry_title_valid(const char *s) { + return string_is_safe(s, /* flags= */ 0); +} + +bool efi_loader_entry_resource_filename_valid(const char *s) { + /* Validates file names so that they are safe for their inclusion in boot loader type #1 + * entries. i.e. may not contain CCs, and should be ASCII */ + + return string_is_safe(s, STRING_ASCII|STRING_FILENAME); +} diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index abf8bdc49ef04..c51c2dbb2f918 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -23,3 +23,5 @@ int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat int efi_get_variable_id128(const char *variable, sd_id128_t *ret); bool efi_loader_entry_name_valid(const char *s); +bool efi_loader_entry_title_valid(const char *s); +bool efi_loader_entry_resource_filename_valid(const char *s); diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index 42f7b53492e45..c16a295d8979f 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -27,7 +27,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The location of the local addon."), SD_VARLINK_DEFINE_FIELD(localAddon, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The command line options by the addon."), - SD_VARLINK_DEFINE_FIELD(options, SD_VARLINK_STRING, 0)); + SD_VARLINK_DEFINE_FIELD(options, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( BootEntry, @@ -147,6 +147,40 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("If true, remove the oldest entry."), SD_VARLINK_DEFINE_INPUT(oldest, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_STRUCT_TYPE( + BootEntryExtraFile, + SD_VARLINK_FIELD_COMMENT("The name of the extra file"), + SD_VARLINK_DEFINE_FIELD(filename, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file."), + SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, 0)); + +static SD_VARLINK_DEFINE_METHOD( + Link, + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), + SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry title for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryTitle, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry version for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The commit number for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryCommit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Target filename for the kernel image (UKI) in the $BOOT partition"), + SD_VARLINK_DEFINE_INPUT(kernelFilename, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to the kernel image to copy"), + SD_VARLINK_DEFINE_INPUT(kernelFileDescriptor, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("An array of 'extra' files for this entry, i.e. credentials, confexts, sysexts, addons."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(extraFiles, BootEntryExtraFile, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("What to set the triesLeft counter of the boot menu entry to initially."), + SD_VARLINK_DEFINE_INPUT(triesLeft, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("How much space to always keep free on ESP/XBOOTLDR. Defaults to 1 MiB"), + SD_VARLINK_DEFINE_INPUT(keepFree, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."), + SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY)); + static SD_VARLINK_DEFINE_ERROR( RebootToFirmwareNotSupported); @@ -162,6 +196,9 @@ static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR( BootEntryTokenUnavailable); +static SD_VARLINK_DEFINE_ERROR( + InvalidKernelImage); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_BootControl, "io.systemd.BootControl", @@ -172,6 +209,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_BootEntrySource, SD_VARLINK_SYMBOL_COMMENT("A structure encapsulating an addon of a boot entry"), &vl_type_BootEntryAddon, + SD_VARLINK_SYMBOL_COMMENT("An additional file to install"), + &vl_type_BootEntryExtraFile, SD_VARLINK_SYMBOL_COMMENT("A structure encapsulating a boot entry"), &vl_type_BootEntry, SD_VARLINK_SYMBOL_COMMENT("The operation to execute"), @@ -188,6 +227,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Install, SD_VARLINK_SYMBOL_COMMENT("Unlink a boot menu item"), &vl_method_Unlink, + SD_VARLINK_SYMBOL_COMMENT("Install a kernel as boot menu item"), + &vl_method_Link, SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."), &vl_error_RebootToFirmwareNotSupported, SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."), @@ -197,4 +238,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Neither ESP nor XBOOTLDR found, hence no $BOOT location identified."), &vl_error_NoDollarBootFound, SD_VARLINK_SYMBOL_COMMENT("The selected boot entry token could not be determined."), - &vl_error_BootEntryTokenUnavailable); + &vl_error_BootEntryTokenUnavailable, + SD_VARLINK_SYMBOL_COMMENT("The specified kernel image is not valid."), + &vl_error_InvalidKernelImage); From e7128bb795949da6b785f2c78f75c8442ffb0b77 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Feb 2026 15:13:02 +0100 Subject: [PATCH 1399/2155] bootspec: generalize "addon" logic for all kinds of extra sidecars Let's pick up all kinds of sidecars and show them, not just addons This also fixes some issues regarding "root" directory handling. In one context we'd resolve a directory claiming it was a "root", but it wasn't. Implements: https://github.com/uapi-group/specifications/pull/212 --- src/shared/bootspec.c | 471 ++++++++++++++------ src/shared/bootspec.h | 28 +- src/shared/varlink-io.systemd.BootControl.c | 2 + test/units/TEST-87-AUX-UTILS-VM.bootctl.sh | 2 +- 4 files changed, 346 insertions(+), 157 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index c0111d1f9c433..a167e19a90b58 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -14,10 +14,12 @@ #include "efi-loader.h" #include "efivars.h" #include "env-file.h" +#include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "find-esp.h" +#include "json-util.h" #include "log.h" #include "parse-util.h" #include "path-util.h" @@ -65,15 +67,65 @@ static const char* const boot_entry_source_table[_BOOT_ENTRY_SOURCE_MAX] = { DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource); -static void boot_entry_addons_done(BootEntryAddons *addons) { - assert(addons); +static BootEntryExtraType boot_entry_extra_type_from_filename(const char *path) { + if (!path) + return _BOOT_ENTRY_EXTRA_TYPE_INVALID; - FOREACH_ARRAY(addon, addons->items, addons->n_items) { - free(addon->cmdline); - free(addon->location); + if (endswith_no_case(path, ".addon.efi")) + return BOOT_ENTRY_ADDON; + if (endswith_no_case(path, ".confext.raw")) + return BOOT_ENTRY_CONFEXT; + if (endswith_no_case(path, ".sysext.raw")) + return BOOT_ENTRY_SYSEXT; + if (endswith_no_case(path, ".cred")) + return BOOT_ENTRY_CREDENTIAL; + + return _BOOT_ENTRY_EXTRA_TYPE_INVALID; +} + +static void boot_entry_extras_done(BootEntryExtras *extras) { + assert(extras); + + FOREACH_ARRAY(extra, extras->items, extras->n_items) { + free(extra->location); + free(extra->cmdline); + } + extras->items = mfree(extras->items); + extras->n_items = 0; +} + +static int boot_entry_extras_add( + BootEntryExtras *extras, + BootEntryExtraType type, + const char *path, + const char *cmdline) { + + assert(extras); + assert(type >= 0); + assert(type < _BOOT_ENTRY_EXTRA_TYPE_MAX); + assert(path); + + _cleanup_free_ char *p = strdup(path); + if (!p) + return -ENOMEM; + + _cleanup_free_ char *c = NULL; + if (cmdline) { + c = strdup(cmdline); + if (!c) + return -ENOMEM; } - addons->items = mfree(addons->items); - addons->n_items = 0; + + if (!GREEDY_REALLOC(extras->items, extras->n_items + 1)) + return -ENOMEM; + + extras->items[extras->n_items++] = (BootEntryExtra) { + .type = type, + .location = TAKE_PTR(p), + .cmdline = TAKE_PTR(c), + }; + + return 0; } static void boot_entry_free(BootEntry *entry) { @@ -91,7 +143,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->machine_id); free(entry->architecture); strv_free(entry->options); - boot_entry_addons_done(&entry->local_addons); + boot_entry_extras_done(&entry->local_extras); free(entry->kernel); free(entry->efi); free(entry->uki); @@ -213,6 +265,50 @@ static int parse_path_many( return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false); } +static int parse_extra( + const char *fname, + unsigned line, + const char *field, + BootEntryExtras *extras, + const char *p) { + + int r; + + assert(extras); + + _cleanup_strv_free_ char **l = strv_split(p, NULL); + if (!l) + return -ENOMEM; + + STRV_FOREACH(i, l) { + _cleanup_free_ char *c = NULL; + r = mangle_path(fname, line, field, *i, &c); + if (r < 0) + return r; + if (r == 0) + continue; + + BootEntryExtraType type = boot_entry_extra_type_from_filename(c); + if (type < 0) { + log_debug_errno(type, "Failed to determine boot entry extra type of '%s', skipping: %m", c); + continue; + } + + /* Let's filter out EFI addons for now. We have no protocol for passing them from sd-boot to + * sd-stub, hence supporting them would require major plumbing first. */ + if (type == BOOT_ENTRY_ADDON) { + log_debug("EFI addons are currently not supported for Type #1 entries, skipping '%s'.", c); + continue; + } + + r = boot_entry_extras_add(extras, type, c, /* cmdline= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + static int parse_tries(const char *fname, const char **p, unsigned *ret) { _cleanup_free_ char *d = NULL; unsigned tries; @@ -421,6 +517,8 @@ static int boot_entry_load_type1( r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p); else if (streq(field, "devicetree-overlay")) r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p); + else if (streq(field, "extra")) + r = parse_extra(tmp.path, line, field, &tmp.local_extras, p); else { log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field); continue; @@ -458,7 +556,7 @@ int boot_config_load_type1( return r; config->n_entries++; - entry->global_addons = &config->global_addons[source]; + entry->global_extras = &config->global_extras[source]; return 0; } @@ -479,8 +577,8 @@ void boot_config_free(BootConfig *config) { boot_entry_free(i); free(config->entries); - FOREACH_ELEMENT(i, config->global_addons) - boot_entry_addons_done(i); + FOREACH_ELEMENT(i, config->global_extras) + boot_entry_extras_done(i); set_free(config->inodes_seen); } @@ -1188,126 +1286,140 @@ static int pe_find_addon_sections( return 0; } -static int insert_boot_entry_addon( - BootEntryAddons *addons, - char *location, - char *cmdline) { - - assert(addons); - - if (!GREEDY_REALLOC(addons->items, addons->n_items + 1)) - return log_oom(); - - addons->items[addons->n_items++] = (BootEntryAddon) { - .location = location, - .cmdline = cmdline, - }; - - return 0; -} - -static int boot_entries_find_unified_addons( +static int boot_entries_find_unified_extras( BootConfig *config, int d_fd, - const char *addon_dir, - const char *root, - BootEntryAddons *ret_addons) { + const char *extra_dir, + BootEntryExtraType only_type, + const char *where, + bool suppress_seen, + BootEntryExtras *extras) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *full = NULL; - _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {}; int r; - assert(ret_addons); assert(config); + assert(extras); - r = chase_and_opendirat(d_fd, d_fd, addon_dir, /* chase_flags= */ 0, &full, &d); + _cleanup_closedir_ DIR *d = NULL; + r = chase_and_opendirat( + /* root_fd= */ d_fd, + /* dir_fd= */ d_fd, + extra_dir, + /* chase_flags= */ 0, + /* ret_path= */ NULL, + &d); if (r == -ENOENT) return 0; if (r < 0) - return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir)); - - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) { - _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL; - _cleanup_close_ int fd = -EBADF; + return log_error_errno(r, "Failed to open '%s/%s': %m", where, skip_leading_slash(extra_dir)); + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read '%s': %m", extra_dir)) { if (!dirent_is_file(de)) continue; - if (!endswith_no_case(de->d_name, ".addon.efi")) + BootEntryExtraType type = boot_entry_extra_type_from_filename(de->d_name); + if (type < 0) { + log_debug_errno(type, "Unrecognized extra file '%s', skipping.", de->d_name); continue; + } + if (only_type >= 0 && type != only_type) { + log_debug("Extra file '%s' type not permitted in '%s', skipping.", de->d_name, extra_dir); + continue; + } - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); - if (fd < 0) { - log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name); + _cleanup_free_ char *location = path_join(extra_dir, de->d_name); + if (!location) + return log_oom(); + + _cleanup_close_ int pin_fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (pin_fd < 0) { + log_debug_errno(errno, "Failed to pin '%s', ignoring: %m", location); continue; } - r = config_check_inode_relevant_and_unseen(config, fd, de->d_name); - if (r < 0) - return r; - if (r == 0) /* inode already seen or otherwise not relevant */ + r = fd_verify_regular(pin_fd); + if (r < 0) { + log_debug_errno(r, "Unrecognized inode type of '%s', skipping.", location); continue; + } - j = path_join(full, de->d_name); - if (!j) - return log_oom(); + if (suppress_seen) { + r = config_check_inode_relevant_and_unseen(config, pin_fd, location); + if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + } - if (pe_find_addon_sections(fd, j, &cmdline) <= 0) - continue; + _cleanup_free_ char *cmdline = NULL; + if (type == BOOT_ENTRY_ADDON) { + _cleanup_close_ int fd = fd_reopen(pin_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_debug_errno(fd, "Failed to open '%s', ignoring: %m", location); + continue; + } - location = strdup(j); - if (!location) - return log_oom(); + /* Try to extract the command line, but let's handle any failures gracefully, but + * still mention the extra file exists. */ + (void) pe_find_addon_sections(fd, location, &cmdline); + } - r = insert_boot_entry_addon(&addons, location, cmdline); + r = boot_entry_extras_add(extras, type, location, cmdline); if (r < 0) return r; - - TAKE_PTR(location); - TAKE_PTR(cmdline); } - *ret_addons = TAKE_STRUCT(addons); return 0; } -static int boot_entries_find_unified_global_addons( +static int boot_entries_find_unified_global_extras( BootConfig *config, - const char *root, - const char *d_name, - BootEntryAddons *ret_addons) { - - int r; - _cleanup_closedir_ DIR *d = NULL; + const char *where, + const char *extra_dir, + BootEntryExtraType only_type, + BootEntryExtras *extras) { - assert(ret_addons); + assert(extras); - r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d); - if (r == -ENOENT) + _cleanup_close_ int where_fd = RET_NERRNO(open(where, O_DIRECTORY|O_CLOEXEC)); + if (where_fd == -ENOENT) return 0; - if (r < 0) - return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name)); - - return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons); + if (where_fd < 0) + return log_error_errno(where_fd, "Failed to open '%s': %m", where); + + return boot_entries_find_unified_extras( + config, + where_fd, + extra_dir, + only_type, + where, + /* suppress_seen= */ true, + extras); } -static int boot_entries_find_unified_local_addons( +static int boot_entries_find_unified_local_extras( BootConfig *config, int d_fd, - const char *d_name, - const char *root, + const char *uki, + const char *where, BootEntry *ret) { - _cleanup_free_ char *addon_dir = NULL; + _cleanup_free_ char *extra_dir = NULL; assert(ret); - addon_dir = strjoin(d_name, ".extra.d"); - if (!addon_dir) + extra_dir = strjoin(uki, ".extra.d"); + if (!extra_dir) return log_oom(); - return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons); + return boot_entries_find_unified_extras( + config, + d_fd, + extra_dir, + /* only_type= */ _BOOT_ENTRY_EXTRA_TYPE_INVALID, + where, + /* suppress_seen= */ false, + &ret->local_extras); } static int boot_entries_find_unified( @@ -1369,11 +1481,11 @@ static int boot_entries_find_unified( if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0) continue; - /* look for .efi.extra.d */ - (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, entry); + /* Look for .efi.extra.d/ */ + (void) boot_entries_find_unified_local_extras(config, dirfd(d), de->d_name, full, entry); - /* Set up the backpointer, so that we can find the global addons */ - entry->global_addons = &config->global_addons[source]; + /* Set up the backpointer, so that we can find the global extras */ + entry->global_extras = &config->global_extras[source]; config->n_entries++; } @@ -1614,45 +1726,73 @@ int boot_config_finalize(BootConfig *config) { return 0; } -int boot_config_load( +static int boot_entries_load( BootConfig *config, - const char *esp_path, - const char *xbootldr_path) { + BootEntrySource source, + const char *where) { /* Mount point of ESP/XBOOTLDR */ int r; assert(config); + assert(source >= 0); + assert(source < _BOOT_ENTRY_SOURCE_MAX); - if (esp_path) { - r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); - if (r < 0) - return r; + if (!where) + return 0; - r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries"); - if (r < 0) - return r; + r = boot_entries_find_type1(config, where, source, "/loader/entries"); + if (r < 0) + return r; - r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/"); - if (r < 0) - return r; + r = boot_entries_find_unified(config, where, source, "/EFI/Linux/"); + if (r < 0) + return r; - r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/", - &config->global_addons[BOOT_ENTRY_ESP]); + static const struct { + BootEntryExtraType extra_type; + const char *directory; + } table[] = { + { BOOT_ENTRY_ADDON, "/loader/addons/" }, + { BOOT_ENTRY_CONFEXT, "/loader/extensions/" }, + { BOOT_ENTRY_SYSEXT, "/loader/extensions/" }, + { BOOT_ENTRY_CREDENTIAL, "/loader/credentials/" }, + }; + + FOREACH_ELEMENT(i, table) { + r = boot_entries_find_unified_global_extras( + config, + where, + i->directory, + i->extra_type, + &config->global_extras[source]); if (r < 0) return r; } - if (xbootldr_path) { - r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries"); + return 0; +} + +int boot_config_load( + BootConfig *config, + const char *esp_path, + const char *xbootldr_path) { + + int r; + + assert(config); + + if (esp_path) { + r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); if (r < 0) return r; - r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/"); + r = boot_entries_load(config, BOOT_ENTRY_ESP, esp_path); if (r < 0) return r; + } - r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/", - &config->global_addons[BOOT_ENTRY_XBOOTLDR]); + if (xbootldr_path) { + r = boot_entries_load(config, BOOT_ENTRY_XBOOTLDR, xbootldr_path); if (r < 0) return r; } @@ -1724,7 +1864,7 @@ int boot_config_augment_from_loader( char **found_by_loader, bool auto_only) { - static const BootEntryAddons no_addons = (BootEntryAddons) {}; + static const BootEntryExtras no_extras = (BootEntryExtras) {}; static const char *const title_table[] = { /* Pretty names for a few well-known automatically discovered entries. */ "auto-osx", "macOS", @@ -1783,7 +1923,7 @@ int boot_config_augment_from_loader( .tries_left = UINT_MAX, .tries_done = UINT_MAX, .profile = UINT_MAX, - .global_addons = &no_addons, + .global_extras = &no_extras, }; } @@ -1806,10 +1946,10 @@ static void boot_entry_file_list( const char *field, const char *root, const char *p, - int *ret_status) { + int *pstatus) { assert(p); - assert(ret_status); + assert(pstatus); int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL); @@ -1824,16 +1964,23 @@ static void boot_entry_file_list( } else printf("%s\n", p); - if (*ret_status == 0 && status < 0) - *ret_status = status; + if (*pstatus == 0 && status < 0) + *pstatus = status; } -static void print_addon( - BootEntryAddon *addon, - const char *addon_str) { +static void print_extra( + const BootEntry *e, + const BootEntryExtra *extra, + const char *field, + int *status) { + + assert(e); + assert(extra); + + boot_entry_file_list(field, e->root, extra->location, status); - printf(" %s: %s\n", addon_str, addon->location); - printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline); + if (extra->cmdline) + printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT), extra->cmdline); } static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { @@ -1851,12 +1998,10 @@ static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { return -ENOMEM; *ret_cmdline = TAKE_PTR(t); - return 0; } -static int print_cmdline(const BootEntry *e) { - +static int print_cmdline(const BootEntry *e, int *status) { _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL; assert(e); @@ -1877,17 +2022,20 @@ static int print_cmdline(const BootEntry *e) { return log_oom(); } - FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) { - print_addon(addon, "global-addon"); - if (!strextend(&t2, " ", addon->cmdline)) - return log_oom(); + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + print_extra(e, extra, "extra", status); + + if (extra->cmdline) + if (!strextend(&t2, " ", extra->cmdline)) + return log_oom(); } - FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { - /* Add space at the beginning of addon_str to align it correctly */ - print_addon(addon, " local-addon"); - if (!strextend(&t2, " ", addon->cmdline)) - return log_oom(); + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + print_extra(e, extra, "extra", status); + + if (extra->cmdline) + if (!strextend(&t2, " ", extra->cmdline)) + return log_oom(); } /* Don't print the combined cmdline if it's same as options. */ @@ -1904,19 +2052,19 @@ static int print_cmdline(const BootEntry *e) { } static int json_addon( - BootEntryAddon *addon, - const char *addon_str, + const BootEntryExtra *extra, + const char *extra_str, sd_json_variant **array) { int r; - assert(addon); - assert(addon_str); + assert(extra); + assert(extra_str); r = sd_json_variant_append_arraybo( array, - SD_JSON_BUILD_PAIR_STRING(addon_str, addon->location), - SD_JSON_BUILD_PAIR_STRING("options", addon->cmdline)); + SD_JSON_BUILD_PAIR_STRING(extra_str, extra->location), + JSON_BUILD_PAIR_STRING_NON_EMPTY("options", extra->cmdline)); if (r < 0) return log_oom(); @@ -1940,20 +2088,31 @@ static int json_cmdline( return log_oom(); } - FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) { - r = json_addon(addon, "globalAddon", &addons_array); + /* NB: these JSON fields are kinda obsolete, we want the more generic 'extra' ones to be used. */ + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + if (extra->type != BOOT_ENTRY_ADDON) + continue; + + r = json_addon(extra, "globalAddon", &addons_array); if (r < 0) return r; - if (!strextend(&combined_cmdline, " ", addon->cmdline)) - return log_oom(); + + if (extra->cmdline) + if (!strextend(&combined_cmdline, " ", extra->cmdline)) + return log_oom(); } - FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { - r = json_addon(addon, "localAddon", &addons_array); + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + if (extra->type != BOOT_ENTRY_ADDON) + continue; + + r = json_addon(extra, "localAddon", &addons_array); if (r < 0) return r; - if (!strextend(&combined_cmdline, " ", addon->cmdline)) - return log_oom(); + + if (extra->cmdline) + if (!strextend(&combined_cmdline, " ", extra->cmdline)) + return log_oom(); } r = sd_json_variant_merge_objectbo( @@ -2064,7 +2223,7 @@ int show_boot_entry( *s, &status); - r = print_cmdline(e); + r = print_cmdline(e, &status); if (r < 0) return r; @@ -2144,6 +2303,24 @@ int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) { if (r < 0) return log_oom(); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *jextras = NULL; + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location)); + if (r < 0) + return log_oom(); + } + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location)); + if (r < 0) + return log_oom(); + } + + r = sd_json_variant_merge_objectbo( + &v, + SD_JSON_BUILD_PAIR_CONDITION(!!jextras, "extras", SD_JSON_BUILD_VARIANT(jextras))); + if (r < 0) + return log_oom(); + *ret = TAKE_PTR(v); return 1; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index d5f6930be99d1..677667383c9de 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -20,15 +20,25 @@ typedef enum BootEntrySource { _BOOT_ENTRY_SOURCE_INVALID = -EINVAL, } BootEntrySource; -typedef struct BootEntryAddon { +typedef enum BootEntryExtraType { + BOOT_ENTRY_ADDON, + BOOT_ENTRY_CONFEXT, + BOOT_ENTRY_SYSEXT, + BOOT_ENTRY_CREDENTIAL, + _BOOT_ENTRY_EXTRA_TYPE_MAX, + _BOOT_ENTRY_EXTRA_TYPE_INVALID = -EINVAL, +} BootEntryExtraType; + +typedef struct BootEntryExtra { + BootEntryExtraType type; char *location; - char *cmdline; -} BootEntryAddon; + char *cmdline; /* only for BOOT_ENTRY_ADDON */ +} BootEntryExtra; -typedef struct BootEntryAddons { - BootEntryAddon *items; +typedef struct BootEntryExtras { + BootEntryExtra *items; size_t n_items; -} BootEntryAddons; +} BootEntryExtras; typedef struct BootEntry { BootEntryType type; @@ -46,8 +56,8 @@ typedef struct BootEntry { char *machine_id; char *architecture; char **options; - BootEntryAddons local_addons; - const BootEntryAddons *global_addons; /* Backpointer into the BootConfig; we don't own this here */ + BootEntryExtras local_extras; + const BootEntryExtras *global_extras; /* Backpointer into the BootConfig; we don't own this here */ char *kernel; /* linux is #defined to 1, yikes! */ char *efi; char *uki; @@ -84,7 +94,7 @@ typedef struct BootConfig { BootEntry *entries; size_t n_entries; - BootEntryAddons global_addons[_BOOT_ENTRY_SOURCE_MAX]; + BootEntryExtras global_extras[_BOOT_ENTRY_SOURCE_MAX]; ssize_t default_entry; ssize_t selected_entry; diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index c16a295d8979f..920b9479db0a4 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -80,6 +80,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(isSelected, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Addon images of the entry."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(addons, BootEntryAddon, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Extra files associated with the entry."), + SD_VARLINK_DEFINE_FIELD(extras, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), SD_VARLINK_FIELD_COMMENT("Command line options of the entry."), SD_VARLINK_DEFINE_FIELD(cmdline, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); diff --git a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh index 440c3e5edfbcc..668c0cfac4580 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh @@ -374,7 +374,7 @@ testcase_00_secureboot() { bootctl status | grep "Secure Boot: enabled" >/dev/null # Ensure the addon is fully loaded and parsed - bootctl status | grep "global-addon: loader/addons/test.addon.efi" >/dev/null + bootctl status | grep "extra: /boot//loader/addons/test.addon.efi" >/dev/null bootctl status | grep "cmdline" | grep addonfoobar >/dev/null grep -q addonfoobar /proc/cmdline } From e7ab31b4d1c90ffe41e29b60731742c3cdeed1bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 17:33:07 +0100 Subject: [PATCH 1400/2155] bootctl: make sure "unlink" properly tracks "extra" files --- src/bootctl/bootctl-unlink.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index 80e74926c6c76..6e8e5c4bb1703 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -146,6 +146,12 @@ static int boot_entry_ref_files( return r; } + FOREACH_ARRAY(x, e->local_extras.items, e->local_extras.n_items) { + r = ref_file(known_files, x->location, increment); + if (r < 0) + return r; + } + return 0; } @@ -294,6 +300,8 @@ int boot_entry_unlink( (void) unref_unlink_file(&known_files, root, root_fd, e->device_tree, dry_run); STRV_FOREACH(s, e->device_tree_overlay) (void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run); + FOREACH_ARRAY(x, e->local_extras.items, e->local_extras.n_items) + (void) unref_unlink_file(&known_files, root, root_fd, x->location, dry_run); if (dry_run) log_info("Would remove \"%s\"", e->path); From c68b8ff4d192b47d1b99a52b6217c1dc3f7a39ea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 15:01:00 +0200 Subject: [PATCH 1401/2155] ci: add integration test for new bootctl functionality --- test/units/TEST-87-AUX-UTILS-VM.bootctl.sh | 271 +++++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh index 668c0cfac4580..90daaf52eadc2 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh @@ -397,4 +397,275 @@ testcase_install_varlink() { bootctl is-installed } +cleanup_link() { + if [[ -n "${LINK_WORKDIR:-}" ]]; then + rm -rf "$LINK_WORKDIR" + unset LINK_WORKDIR + fi + restore_esp +} + +testcase_bootctl_link() { + if ! command -v ukify >/dev/null; then + echo "ukify not found, skipping." + return 0 + fi + + backup_esp + LINK_WORKDIR="$(mktemp --directory /tmp/test-bootctl-link.XXXXXXXXXX)" + trap cleanup_link RETURN ERR + + # Ensure loader/entries directory is present + bootctl install --make-entry-directory=yes + + local ESP + ESP="$(bootctl --print-esp-path)" + + # Build a minimal UKI via ukify. The .linux content does not need to be a + # real kernel — bootctl link only requires a valid PE with .osrel (and the + # systemd-stub SBAT marker that pe_is_uki() checks for). + cat >"$LINK_WORKDIR/os-release" <<'EOF' +ID=testos +NAME="Test OS" +PRETTY_NAME="Test OS" +EOF + echo "fake-kernel" >"$LINK_WORKDIR/vmlinuz" + echo "fake-initrd" >"$LINK_WORKDIR/initrd" + echo "fake-sysext-data" >"$LINK_WORKDIR/hello.sysext.raw" + echo "fake-confext-data" >"$LINK_WORKDIR/hello.confext.raw" + echo "fake-credential" >"$LINK_WORKDIR/hello.cred" + + ukify build \ + --linux "$LINK_WORKDIR/vmlinuz" \ + --initrd "$LINK_WORKDIR/initrd" \ + --os-release "@$LINK_WORKDIR/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet" \ + --output "$LINK_WORKDIR/testuki.efi" + + # Pin an explicit entry token so the resulting filenames are deterministic + local TOKEN="systemdtest" + local BOOTCTL=(bootctl "--entry-token=literal:$TOKEN") + + # --- Test 1: basic link/unlink --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" + + # Exactly one entry file should exist, named "${TOKEN}-commit_1.conf" + local ENTRY="$ESP/loader/entries/${TOKEN}-commit_1.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + + # Verify the entry file contents + grep "^title " "$ENTRY" >/dev/null + grep "^uki /${TOKEN}/testuki.efi\$" "$ENTRY" >/dev/null + grep "^version 1\$" "$ENTRY" >/dev/null + + # Make sure bootctl list sees it + bootctl list --json=short | grep -F "${TOKEN}-commit_1.conf" >/dev/null + + # Remove it again using the ID (entry IDs include the .conf suffix) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 2: link with --entry-title/--entry-version/--entry-commit/--tries-left --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" \ + --entry-title="My Funky Entry" \ + --entry-version="9.8.7" \ + --entry-commit=42 \ + --tries-left=3 + + ENTRY="$ESP/loader/entries/${TOKEN}-commit_42.9.8.7+3.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + + grep "^title My Funky Entry\$" "$ENTRY" >/dev/null + grep "^version 42.9.8.7\$" "$ENTRY" >/dev/null + grep "^uki /${TOKEN}/testuki.efi\$" "$ENTRY" >/dev/null + + # Unlink using the ID (the tries counter "+3" is stripped from the canonical ID) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_42.9.8.7.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 3: link with extras (-X and --extra=) --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" \ + --entry-commit=50 \ + -X "$LINK_WORKDIR/hello.sysext.raw" \ + --extra="$LINK_WORKDIR/hello.confext.raw" \ + -X "$LINK_WORKDIR/hello.cred" + + ENTRY="$ESP/loader/entries/${TOKEN}-commit_50.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + test -f "$ESP/$TOKEN/hello.sysext.raw" + test -f "$ESP/$TOKEN/hello.confext.raw" + test -f "$ESP/$TOKEN/hello.cred" + + grep "^extra /${TOKEN}/hello.sysext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.confext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.cred\$" "$ENTRY" >/dev/null + + # Unlink must also clean up the extra resources + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_50.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + test ! -e "$ESP/$TOKEN/hello.sysext.raw" + test ! -e "$ESP/$TOKEN/hello.confext.raw" + test ! -e "$ESP/$TOKEN/hello.cred" + + # --- Test 4: --oldest drops the lowest commit first --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=10 + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=20 + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=30 + + test -f "$ESP/loader/entries/${TOKEN}-commit_10.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + "${BOOTCTL[@]}" unlink --oldest=yes + test ! -e "$ESP/loader/entries/${TOKEN}-commit_10.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + "${BOOTCTL[@]}" unlink --oldest=yes + test ! -e "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + # --- Test 5: --dry-run leaves everything in place --- + "${BOOTCTL[@]}" --dry-run unlink "${TOKEN}-commit_30.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + # Actually remove it now + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_30.conf" + test ! -e "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 6: invalid combinations are rejected --- + # Neither an ID nor --oldest + (! "${BOOTCTL[@]}" unlink) + # Both an ID and --oldest + (! "${BOOTCTL[@]}" unlink --oldest=yes "${TOKEN}-commit_1.conf") + + # --- Test 7: refusing to link when --keep-free cannot be satisfied --- + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=99 --keep-free=1T) + test ! -e "$ESP/loader/entries/${TOKEN}-commit_99.conf" + + # --- Test 8: refusing to re-link the same commit number --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=77 + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=77) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_77.conf" + + # --- Test 9: passing a non-UKI is rejected --- + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/vmlinuz") + + # === Varlink coverage === + # + # Exercise io.systemd.BootControl.Link/Unlink by forking bootctl as a + # varlink server via 'varlinkctl call '. Note the Varlink schema + # has no way to supply a literal entry token (unlike --entry-token= on + # the command line), so the token is chosen by bootctl from + # machine-id/os-release — we recover it from the returned id. + local BOOTCTL_BIN vreply vid vtoken + BOOTCTL_BIN="$(type -p bootctl)" + + # --- Test 10: Link + Unlink via varlink --- + vreply="$(varlinkctl call --json=short \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"vluki.efi","kernelFileDescriptor":0}')" + vid="$(echo "$vreply" | jq -r '.ids[0]')" + test -n "$vid" + test "$vid" != "null" + vtoken="${vid%%-commit_*}" + test -n "$vtoken" + + test -f "$ESP/loader/entries/$vid" + test -f "$ESP/$vtoken/vluki.efi" + grep "^uki /$vtoken/vluki.efi\$" "$ESP/loader/entries/$vid" >/dev/null + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vid\"}" + test ! -e "$ESP/loader/entries/$vid" + test ! -e "$ESP/$vtoken/vluki.efi" + + # --- Test 11: Link with entryTitle/entryVersion/entryCommit/triesLeft + extraFiles via varlink --- + vreply="$(varlinkctl call --json=short \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + --push-fd="$LINK_WORKDIR/hello.sysext.raw" \ + --push-fd="$LINK_WORKDIR/hello.cred" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"vluki2.efi","kernelFileDescriptor":0,"entryTitle":"Varlink Title","entryVersion":"2.3.4","entryCommit":111,"triesLeft":2,"extraFiles":[{"filename":"hello.sysext.raw","fileDescriptor":1},{"filename":"hello.cred","fileDescriptor":2}]}')" + vid="$(echo "$vreply" | jq -r '.ids[0]')" + # The returned id has the tries counter ("+2") stripped + assert_eq "$vid" "$vtoken-commit_111.2.3.4.conf" + # The on-disk entry filename includes the tries counter + local VENTRY="$ESP/loader/entries/$vtoken-commit_111.2.3.4+2.conf" + test -f "$VENTRY" + test -f "$ESP/$vtoken/vluki2.efi" + test -f "$ESP/$vtoken/hello.sysext.raw" + test -f "$ESP/$vtoken/hello.cred" + + grep "^title Varlink Title\$" "$VENTRY" >/dev/null + grep "^version 111.2.3.4\$" "$VENTRY" >/dev/null + grep "^extra /$vtoken/hello.sysext.raw\$" "$VENTRY" >/dev/null + grep "^extra /$vtoken/hello.cred\$" "$VENTRY" >/dev/null + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vid\"}" + test ! -e "$VENTRY" + test ! -e "$ESP/$vtoken/vluki2.efi" + test ! -e "$ESP/$vtoken/hello.sysext.raw" + test ! -e "$ESP/$vtoken/hello.cred" + + # --- Test 12: Unlink oldest via varlink --- + local c + for c in 210 220 230; do + varlinkctl call --quiet \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + "{\"kernelFilename\":\"vluki3.efi\",\"kernelFileDescriptor\":0,\"entryCommit\":$c}" + done + test -f "$ESP/loader/entries/$vtoken-commit_210.conf" + test -f "$ESP/loader/entries/$vtoken-commit_220.conf" + test -f "$ESP/loader/entries/$vtoken-commit_230.conf" + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"oldest":true}' + test ! -e "$ESP/loader/entries/$vtoken-commit_210.conf" + test -f "$ESP/loader/entries/$vtoken-commit_220.conf" + test -f "$ESP/loader/entries/$vtoken-commit_230.conf" + test -f "$ESP/$vtoken/vluki3.efi" + + # Clean up remaining entries + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vtoken-commit_220.conf\"}" + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vtoken-commit_230.conf\"}" + test ! -e "$ESP/loader/entries/$vtoken-commit_220.conf" + test ! -e "$ESP/loader/entries/$vtoken-commit_230.conf" + test ! -e "$ESP/$vtoken/vluki3.efi" + + # --- Test 13: Link with a non-UKI via varlink returns InvalidKernelImage --- + varlinkctl call --quiet \ + --push-fd="$LINK_WORKDIR/vmlinuz" \ + --graceful=io.systemd.BootControl.InvalidKernelImage \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"notauki.efi","kernelFileDescriptor":0}' + + # --- Test 14: Unlink with invalid argument combinations is rejected --- + # Both id and oldest=true + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"id":"foo.conf","oldest":true}') + # Neither id nor oldest + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink '{}') + # Invalid id characters (e.g. a glob) + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"id":"foo*.conf"}') +} + run_testcases From 5e74fd06ba92ac1956b29daeb2a2ecef21d01dc6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Feb 2026 12:39:02 +0100 Subject: [PATCH 1402/2155] update TODO --- TODO.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 60a11dcee3ad1..b5777556e999d 100644 --- a/TODO.md +++ b/TODO.md @@ -143,6 +143,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later volume is auto-destroyed. Would be exposed via a new flag on the Acquire call, similar to the locking logic above. +- clean up credential naming a bit: let's say encrypted creds always should + carry .cred suffix, and unencrypted should not. + +- clean up naming of sidecar files in sd-stub: let's put global ones strictly + into /loader/extras/ + - 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 @@ -2163,10 +2169,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - run0: maybe enable utmp for run0 sessions, so that they are easily visible. -- 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. - - **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. From 6a672f5df87ebd6b4edd1aea3a1a31fc76651313 Mon Sep 17 00:00:00 2001 From: Jim Spentzos Date: Fri, 1 May 2026 00:59:23 +0000 Subject: [PATCH 1403/2155] 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 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/po/el.po b/po/el.po index c7b321f942d13..1e93e147b1b8d 100644 --- a/po/el.po +++ b/po/el.po @@ -10,7 +10,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-05-01 00:59+0000\n" "Last-Translator: Jim Spentzos \n" "Language-Team: Greek \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.16.1\n" +"X-Generator: Weblate 5.17.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1078,13 +1078,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 eb357bfff8685c10e7b1f6365b3a80cd792f0336 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 30 Apr 2026 11:59:26 +0200 Subject: [PATCH 1404/2155] dbus-manager: limit the number of states/patterns per query Let's cap the number of states/patterns per query to something reasonable, i.e. max 256 states and 4K patterns per query. --- src/core/dbus-manager.c | 20 ++++++++++++++++++++ src/core/manager.h | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 076a26c6fd171..37b38c6ae9ee5 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1251,6 +1251,14 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e /* Anyone can call this method */ + if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many states in a single query."); + + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); + r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; @@ -1434,6 +1442,10 @@ static int dump_impl( assert(message); + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); + /* 'status' access is the bare minimum always needed for this, as the policy might straight out * forbid a client from querying any information from systemd, regardless of any rate limiting. */ r = mac_selinux_access_check(message, "status", reterr_error); @@ -2177,6 +2189,14 @@ static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, /* Anyone can call this method */ + if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many states in a single query."); + + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); + r = mac_selinux_access_check(message, "status", reterr_error); if (r < 0) return r; diff --git a/src/core/manager.h b/src/core/manager.h index 7d58c330a1b82..3bb1a0154a399 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -14,9 +14,13 @@ struct libmnt_monitor; -/* Enforce upper limit how many names we allow */ +/* Enforce upper limit on how many names we allow */ #define MANAGER_MAX_NAMES 131072 /* 128K */ +/* Enforce upper limit on the number of patterns/states requested over IPC */ +#define MANAGER_MAX_PATTERNS_PER_CALL 4096U +#define MANAGER_MAX_STATES_PER_CALL 256U + /* On sigrtmin+18, private commands */ enum { MANAGER_SIGNAL_COMMAND_DUMP_JOBS = _COMMON_SIGNAL_COMMAND_PRIVATE_BASE + 0, From 49c1e1bcf2b482b6de35a4212a06ed1d8c382745 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 30 Apr 2026 14:03:47 +0200 Subject: [PATCH 1405/2155] dbus: limit the number of env variables to something reasonable, vol. 2 Turns out we can utilize this limit at a couple more places, so let's move the previously defined limit constant to env-util.h and use it to guard a couple more D-Bus methods. Also, bump it a bit, given it's meant to be a safety cap that can't be hit in valid scenarios. Follow-up for 7671b43cb88532cce2aa9ad12f777922206d6a42. --- src/basic/env-util.h | 2 ++ src/core/dbus-manager.c | 10 ++++++++++ src/libsystemd/sd-json/json-util.h | 2 -- src/machine/machine-dbus.c | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 28338a1458e07..4063517660b30 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -3,6 +3,8 @@ #include "basic-forward.h" +#define ENVIRONMENT_ASSIGNMENTS_MAX 16384U + size_t sc_arg_max(void); bool env_name_is_valid(const char *e); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 37b38c6ae9ee5..0e93bc723c092 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1892,6 +1892,10 @@ static int method_set_environment(sd_bus_message *message, void *userdata, sd_bu r = sd_bus_message_read_strv(message, &plus); if (r < 0) return r; + + if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments in a single query."); if (!strv_env_is_valid(plus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); @@ -1923,6 +1927,9 @@ static int method_unset_environment(sd_bus_message *message, void *userdata, sd_ if (r < 0) return r; + if (strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names in a single query."); if (!strv_env_name_or_assignment_is_valid(minus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); @@ -1959,6 +1966,9 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd if (r < 0) return r; + if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX || strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names or assignments in a single query."); if (!strv_env_name_or_assignment_is_valid(minus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 34d79d5238aaa..cea2d368b43db 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -9,8 +9,6 @@ #include "sd-forward.h" #include "string-util.h" /* IWYU pragma: keep */ -#define ENVIRONMENT_ASSIGNMENTS_MAX 1024U - #define JSON_VARIANT_REPLACE(v, q) \ do { \ typeof(v)* _v = &(v); \ diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index a9d15ca5f72b1..28f64b3c9b683 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -435,6 +435,10 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu r = sd_bus_message_read_strv(message, &env); if (r < 0) return r; + + if (strv_length(env) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments in a single query."); if (!strv_env_is_valid(env)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); From 2cfaf50cc86670b4b671dd4d4f9614b2c2cfb736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Sarasola?= Date: Fri, 1 May 2026 19:30:42 +0200 Subject: [PATCH 1406/2155] hwdb: Add missing Steelseries Arctis Pro Wireless The Hub for these headsets uses the following USB entries: Bus 007 Device 002: ID 0451:2036 Texas Instruments, Inc. TUSB2036 Hub Bus 007 Device 003: ID 1038:1290 SteelSeries ApS Arctis Pro Wireless Bus 007 Device 004: ID 1038:1294 SteelSeries ApS Arctis Pro Wireless --- hwdb.d/70-sound-card.hwdb | 1 + 1 file changed, 1 insertion(+) diff --git a/hwdb.d/70-sound-card.hwdb b/hwdb.d/70-sound-card.hwdb index 03a0a5eefc28c..f9ceeacb7d79c 100644 --- a/hwdb.d/70-sound-card.hwdb +++ b/hwdb.d/70-sound-card.hwdb @@ -55,6 +55,7 @@ usb:v1038p2216* usb:v1038p2236* usb:v1038p12C2* usb:v1038p1290* +usb:v1038p1294* usb:v1038p12EC* usb:v1038p2269* usb:v1038p226D* From fa9b3b26c7421ebf39004094ecee0ca9bcea2bd9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 22:31:03 +0900 Subject: [PATCH 1407/2155] socket-util: introduce tos_to_priority() This maps from TOS, which can be used for setsockopt(IPPROTO_IP, IP_TOS), to socket priority, which can be used for setsockopt(SOL_SOCKET, SO_PRIORITY). With this, we can set priority like the following: ``` uint8_t tos = IPTOS_CLASS_CS6; setsockopt_int(fd, IPPROTO_IP, IP_TOS, tos); setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(tos)); ``` Co-authored with Google Gemini. --- src/basic/socket-util.c | 27 +++++++++++++++++++++++++++ src/basic/socket-util.h | 2 ++ src/test/test-socket-util.c | 20 ++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 698aa69a4b0f1..2e0ee684ff98b 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -1873,3 +1874,29 @@ void cmsg_close_all(struct msghdr *mh) { } } } + +int tos_to_priority(uint8_t tos) { + /* Map the IP Precedence (top 3 bits of the TOS field) to Linux internal packet priorities + * (TC_PRIO_*). This exactly mirrors the standard Linux kernel IP precedence-to-priority mapping + * (rt_tos2priority) to ensure consistent behavior when explicitly setting SO_PRIORITY. */ + switch (IPTOS_PREC(tos)) { + case IPTOS_PREC_NETCONTROL: /* 0xc0 (CS7) - Network Control. Used for infrastructure control (e.g., STP, keepalives). */ + case IPTOS_PREC_INTERNETCONTROL: /* 0xe0 (CS6) - Internetwork Control. Used for routing protocols (e.g., OSPF, BGP) and DHCP. */ + return TC_PRIO_CONTROL; + + case IPTOS_PREC_CRITIC_ECP: /* 0xa0 (CS5) - Critical. Used for delay-sensitive traffic like Voice over IP (VoIP). */ + case IPTOS_PREC_FLASHOVERRIDE: /* 0x80 (CS4) - Flash Override. Used for interactive video and multimedia. */ + return TC_PRIO_INTERACTIVE; + + case IPTOS_PREC_FLASH: /* 0x60 (CS3) - Flash. Used for broadcast video and call signaling (e.g., SIP). */ + case IPTOS_PREC_IMMEDIATE: /* 0x40 (CS2) - Immediate. Used for OAM (Operations, Administration, and Management) and transactional data. */ + return TC_PRIO_INTERACTIVE_BULK; + + case IPTOS_PREC_PRIORITY: /* 0x20 (CS1) - Priority. Used for background traffic and bulk data transfers. */ + return TC_PRIO_BULK; + + case IPTOS_PREC_ROUTINE: /* 0x00 (CS0) - Routine. Best effort traffic. */ + default: + return TC_PRIO_BESTEFFORT; + } +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 78b948ad461b5..208eb7ac077be 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -270,3 +270,5 @@ int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_ int socket_get_cookie(int fd, uint64_t *ret); void cmsg_close_all(struct msghdr *mh); + +int tos_to_priority(uint8_t tos); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 090839ac06842..713844b09b720 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include "alloc-util.h" @@ -530,4 +532,22 @@ TEST(getpeerpidref) { ASSERT_TRUE(!pidref_equal(&pidref0, &pidref_pid1)); } +TEST(tos_to_priority) { + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS7), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS6), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS5), TC_PRIO_INTERACTIVE); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS4), TC_PRIO_INTERACTIVE); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS3), TC_PRIO_INTERACTIVE_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS2), TC_PRIO_INTERACTIVE_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS1), TC_PRIO_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS0), TC_PRIO_BESTEFFORT); + + /* check if lower bits are correctly filtered. */ + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS7 | IPTOS_LOWDELAY), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS1 | IPTOS_LOWCOST), TC_PRIO_BULK); + + ASSERT_EQ(tos_to_priority(0x00), TC_PRIO_BESTEFFORT); + ASSERT_EQ(tos_to_priority(0xff), TC_PRIO_CONTROL); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 5b10b583581eac104805f67a87cf19f086447d86 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 22:10:03 +0900 Subject: [PATCH 1408/2155] iovec-util: introduce iovec_done_and_memdup() It is similar to free_and_strdup(), but for struct iovec. --- src/basic/iovec-util.c | 15 +++++++++++++++ src/basic/iovec-util.h | 1 + src/test/test-iovec-util.c | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index dab734b9010f1..429f08dea9ad4 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -129,6 +129,21 @@ struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret) { return ret; } +int iovec_done_and_memdup(struct iovec *iovec, const struct iovec *source) { + assert(iovec); + + if (iovec_equal(iovec, source)) + return 0; + + struct iovec copy; + if (!iovec_memdup(source, ©)) + return -ENOMEM; + + iovec_done(iovec); + *iovec = copy; + return 1; +} + struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append) { assert(iovec_is_valid(iovec)); diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index c8261861a0ff7..a0a059550b7db 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -43,5 +43,6 @@ static inline bool iovec_equal(const struct iovec *a, const struct iovec *b) { } struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret); +int iovec_done_and_memdup(struct iovec *iovec, const struct iovec *source); struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append); diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index bd73be1ea76e6..e510e86ca144a 100644 --- a/src/test/test-iovec-util.c +++ b/src/test/test-iovec-util.c @@ -163,4 +163,29 @@ TEST(iovec_make_byte) { ASSERT_EQ(memcmp_nn(x.iov_base, x.iov_len, "x", 1), 0); } +TEST(iovec_done_and_memdup) { + _cleanup_(iovec_done) struct iovec iov = {}; + + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, NULL)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &(struct iovec) {})); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("bbbbb"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("bbbbb"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, NULL)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &(struct iovec) {})); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &iov)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("ddd"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ddd"))); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ddd"))); +} + DEFINE_TEST_MAIN(LOG_INFO); From db28490c9f3c4146f1892509064bb8e43e78e590 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 23:03:15 +0900 Subject: [PATCH 1409/2155] siphash24: introduce siphash24_compress_iovec() helper function --- src/basic/siphash24.c | 11 +++++++++++ src/basic/siphash24.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c index 72d6bbe4f0cb7..b73ede34c8bc4 100644 --- a/src/basic/siphash24.c +++ b/src/basic/siphash24.c @@ -21,6 +21,7 @@ #include +#include "iovec-util.h" #include "siphash24.h" #include "string-util.h" #include "unaligned.h" @@ -156,6 +157,16 @@ void siphash24_compress_string(const char *in, struct siphash *state) { siphash24_compress_safe(in, strlen_ptr(in), state); } +void siphash24_compress_iovec(const struct iovec *iov, struct siphash *state) { + assert(iovec_is_valid(iov)); + assert(state); + + if (!iovec_is_set(iov)) + return; + + siphash24_compress(iov->iov_base, iov->iov_len, state); +} + uint64_t siphash24_finalize(struct siphash *state) { uint64_t b; diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h index d72233beda89b..772e2728b69e0 100644 --- a/src/basic/siphash24.h +++ b/src/basic/siphash24.h @@ -36,6 +36,7 @@ static inline void siphash24_compress_safe(const void *in, size_t inlen, struct } void siphash24_compress_string(const char *in, struct siphash *state); +void siphash24_compress_iovec(const struct iovec *iov, struct siphash *state); uint64_t siphash24_finalize(struct siphash *state); From d667b6b97ff45e0739d165563b896f7d80417b99 Mon Sep 17 00:00:00 2001 From: Simon Lucido Date: Mon, 20 Apr 2026 17:05:27 +0200 Subject: [PATCH 1410/2155] core: add ReloadCount to Manager and bump on successful reload Introduce a counter that tracks how many configuration reloads have been successfully completed by the manager. The increment lives in manager_reload() right after the "point of no return", so failed reload attempts that bail out earlier (e.g. during serialization) do not bump the counter. It is accessible as a new ReloadCount property to org.freedesktop.systemd1.Manager (D-Bus) and ReloadCount to io.systemd.Manager.Describe (Varlink). Also add an integration test for ReloadCount that verifies that the new ReloadCount property increments by one per daemon-reload, accumulates correctly across multiple reloads, and that D-Bus and Varlink return identical values. Also tests that the counter reset after a reexec. Co-developed-by: Claude Opus 4.7 Signed-off-by: Simon Lucido --- NEWS | 6 +++ man/org.freedesktop.systemd1.xml | 13 ++++++- src/core/dbus-manager.c | 1 + src/core/manager.c | 4 ++ src/core/manager.h | 3 ++ src/core/varlink-manager.c | 3 +- src/shared/varlink-io.systemd.Manager.c | 4 +- test/units/TEST-07-PID1.reload-count.sh | 51 +++++++++++++++++++++++++ 8 files changed, 81 insertions(+), 4 deletions(-) create mode 100755 test/units/TEST-07-PID1.reload-count.sh diff --git a/NEWS b/NEWS index 451e3f1b79603..49061c5e11a22 100644 --- a/NEWS +++ b/NEWS @@ -63,6 +63,12 @@ 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. + * The manager exposes a new ReloadCount property on its D-Bus and + Varlink interfaces (org.freedesktop.systemd1.Manager and + io.systemd.Manager respectively). The counter increments after + each successfully completed daemon-reload. It is not preserved + across daemon-reexec. + Changes in systemd-sysext/systemd-confext: * New initrd services systemd-sysext-sysroot.service and diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 76a8dd045f6c6..847e76f95c767 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -572,6 +572,8 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t ReloadCount = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultMemoryZSwapWriteback = ...; }; @@ -1279,6 +1281,8 @@ node /org/freedesktop/systemd1 { + + @@ -1866,6 +1870,10 @@ node /org/freedesktop/systemd1 { SoftRebootsCount encodes how many soft-reboots were successfully completed since the last full boot. Starts at 0. + ReloadCount encodes the number of successfully completed configuration + reloads of the manager. The counter is reset to 0 on + daemon-reexec and on the initial boot. + Virtualization contains a short ID string describing the virtualization technology the system runs in. On bare-metal hardware this is the empty string. Otherwise, it contains an identifier such as kvm, vmware and so on. For a full list of @@ -12646,8 +12654,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ DefaultMemoryZSwapWriteback, DefaultCPUPressureThresholdUSec, DefaultCPUPressureWatch, - DefaultIOPressureThresholdUSec, and - DefaultIOPressureWatch were added in version 261. + DefaultIOPressureThresholdUSec, + DefaultIOPressureWatch, and + ReloadCount were added in version 261. Unit Objects diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 076a26c6fd171..88de34c4ea434 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2978,6 +2978,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("ReloadCount", "t", NULL, offsetof(Manager, reload_count), 0), SD_BUS_PROPERTY("DefaultMemoryZSwapWriteback", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_zswap_writeback), SD_BUS_VTABLE_PROPERTY_CONST), /* deprecated cgroup v1 property */ diff --git a/src/core/manager.c b/src/core/manager.c index 17908d4db864e..da4e9ca408127 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3650,6 +3650,10 @@ int manager_reload(Manager *m) { /* 💀 This is the point of no return, from here on there is no way back. 💀 */ reloading = NULL; + /* Bump before sending the Reloading signal, so any client that reads + * ReloadCount in response to that signal observes the new value. */ + m->reload_count = saturate_add(m->reload_count, 1, UINT64_MAX); + bus_manager_send_reloading(m, true); /* Start by flushing out all jobs and units, all generated units, all runtime environments, all dynamic users diff --git a/src/core/manager.h b/src/core/manager.h index 7d58c330a1b82..abf1764d7859e 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -492,6 +492,9 @@ typedef struct Manager { unsigned soft_reboots_count; + /* The number of successfully completed configuration reloads. */ + uint64_t reload_count; + /* Original ambient capabilities when we were initialized */ uint64_t saved_ambient_set; } Manager; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 0bef5cbe9848d..384d4709c9786 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -193,7 +193,8 @@ static int manager_runtime_build_json(sd_json_variant **ret, const char *name, v JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("WatchdogLastPingTimestamp", watchdog_get_last_ping_as_dual_timestamp(&watchdog_last_ping)), SD_JSON_BUILD_PAIR_STRING("SystemState", manager_state_to_string(manager_state(m))), SD_JSON_BUILD_PAIR_UNSIGNED("ExitCode", m->return_value), - SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count)); + SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count), + SD_JSON_BUILD_PAIR_UNSIGNED("ReloadCount", m->reload_count)); } int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index 0c5ab53702b0d..81b3e894a348d 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -194,7 +194,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Exit code of the manager"), SD_VARLINK_DEFINE_FIELD(ExitCode, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Encodes how many soft-reboots were successfully completed"), - SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0)); + SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Number of successfully completed configuration reloads"), + SD_VARLINK_DEFINE_FIELD(ReloadCount, SD_VARLINK_INT, 0)); static SD_VARLINK_DEFINE_METHOD( Describe, diff --git a/test/units/TEST-07-PID1.reload-count.sh b/test/units/TEST-07-PID1.reload-count.sh new file mode 100755 index 0000000000000..7c31b65c5fc75 --- /dev/null +++ b/test/units/TEST-07-PID1.reload-count.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Verify that the manager exposes a ReloadCount property that increments on +# every daemon-reload, resets to zero across daemon-reexec (since the count +# is not serialized), and is reachable over both D-Bus and Varlink. + +read_count_dbus() { + busctl -j get-property org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + ReloadCount | jq -r '.data' +} + +read_count_varlink() { + varlinkctl call /run/systemd/io.systemd.Manager \ + io.systemd.Manager.Describe '{}' | jq -r '.runtime.ReloadCount' +} + +# Sanity: both transports must agree. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +(( dbus_count == varlink_count )) + +# A single reload bumps the counter by one. +before=$(read_count_dbus) +systemctl daemon-reload +(( $(read_count_dbus) == before + 1 )) + +# Multiple reloads accumulate. +systemctl daemon-reload +systemctl daemon-reload +(( $(read_count_dbus) == before + 3 )) + +# And both transports still agree after the reload. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +(( dbus_count == varlink_count )) + +# A daemon-reexec resets the counter back to zero on both transports, since +# reload_count lives only in memory and is not carried across the reexec. +# `systemctl daemon-reexec` returns as soon as the old PID 1 closes its bus +# connection, which is before the new PID 1 has rebound /run/systemd/private. +# Use --watch-bind=yes to block on inotify until the new socket is live. +systemctl daemon-reexec +busctl --watch-bind=yes call org.freedesktop.systemd1 /org/freedesktop/systemd1 \ + org.freedesktop.DBus.Peer Ping >/dev/null +(( $(read_count_dbus) == 0 )) +(( $(read_count_varlink) == 0 )) From 6d4c714d88147f75c231b7fa004191962c4c0b1b Mon Sep 17 00:00:00 2001 From: kakolla Date: Sun, 3 May 2026 01:01:24 -0700 Subject: [PATCH 1411/2155] hwdb: add correct keyboard mapping for touchpad_toggle event on msi gs66 stealth laptop --- 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 771b7dc43e477..99006885f076f 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1605,6 +1605,10 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGF63*:* KEYBOARD_KEY_85=touchpad_toggle # Toggle touchpad, sends meta+ctrl+toggle +# MSI GS66 Stealth toggles touchpad using Fn+F3 where the keyboard key is 76 +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pn*Stealth GS66*:* + KEYBOARD_KEY_76=touchpad_toggle # Toggle touchpad + evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE60*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE70*:* KEYBOARD_KEY_c2=ejectcd From e53fd6c8488d73abbdcd6db03163e2908b6587a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 10:59:56 +0000 Subject: [PATCH 1412/2155] build(deps): bump systemd/mkosi Bumps [systemd/mkosi](https://github.com/systemd/mkosi) from 66d51024b7149f40be4702e84275c936373ace97 to 9a28ad20bbea61894ea7b971d318a71f4374cf3b. - [Release notes](https://github.com/systemd/mkosi/releases) - [Commits](https://github.com/systemd/mkosi/compare/66d51024b7149f40be4702e84275c936373ace97...9a28ad20bbea61894ea7b971d318a71f4374cf3b) --- updated-dependencies: - dependency-name: systemd/mkosi dependency-version: 9a28ad20bbea61894ea7b971d318a71f4374cf3b dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/coverage.yml | 2 +- .github/workflows/linter.yml | 2 +- .github/workflows/mkosi.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c2b9493f6d8ba..ef366657fc9f7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 775b4f3f9d6fd..070f9b814e125 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,7 +40,7 @@ jobs: GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b - name: Check that tabs are not used in Python code run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 859e50a34ccc8..6c734cbdae877 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -169,7 +169,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location From 7d32d1227984d53cfd8de2c0307b44f54b51cd65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 10:55:59 +0000 Subject: [PATCH 1413/2155] build(deps): bump meson from 1.10.2 to 1.11.1 in /.github/workflows Bumps [meson](https://github.com/mesonbuild/meson) from 1.10.2 to 1.11.1. - [Release notes](https://github.com/mesonbuild/meson/releases) - [Commits](https://github.com/mesonbuild/meson/compare/1.10.2...1.11.1) --- updated-dependencies: - dependency-name: meson dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index 95f1bf1a6a5ad..6e412a6318a23 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -1,6 +1,5 @@ -meson==1.10.2 \ - --hash=sha256:5f84ef186e6e788d9154db63620fc61b3ece69f643b94b43c8b9203c43d89b36 \ - --hash=sha256:7890287d911dd4ee1ebd0efb61ed0321bfcd87c725df923a837cf90c6508f96b +meson==1.11.1 \ + --hash=sha256:9b3a023657e393dbc5335b95c561337d49b7a458f5541e47ec44f2cc566e0d80 ninja==1.13.0 \ --hash=sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f \ --hash=sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988 \ From a3502284def364235a9c31b016738607a75ddddc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 10:59:43 +0000 Subject: [PATCH 1414/2155] build(deps): bump the actions group with 6 updates Bumps the actions group with 6 updates: | Package | From | To | | --- | --- | --- | | [github/codeql-action](https://github.com/github/codeql-action) | `4.32.4` | `4.35.2` | | [actions/github-script](https://github.com/actions/github-script) | `8.0.0` | `9.0.0` | | [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) | `6.0.0` | `6.1.0` | | [redhat-plumbers-in-action/gather-pull-request-metadata](https://github.com/redhat-plumbers-in-action/gather-pull-request-metadata) | `1.9.0` | `1.9.1` | | [super-linter/super-linter](https://github.com/super-linter/super-linter) | `8.5.0` | `8.6.0` | | [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2.6.1` | `3.0.0` | Updates `github/codeql-action` from 4.32.4 to 4.35.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) Updates `actions/github-script` from 8.0.0 to 9.0.0 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/ed597411d8f924073f98dfc5c65a23a2325f34cd...3a2844b7e9c422d3c10d287c895573f7108da1b3) Updates `aws-actions/configure-aws-credentials` from 6.0.0 to 6.1.0 - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/8df5847569e6427dd6c4fb1cf565c83acfa8afa7...ec61189d14ec14c8efccab744f656cffd0e33f37) Updates `redhat-plumbers-in-action/gather-pull-request-metadata` from 1.9.0 to 1.9.1 - [Release notes](https://github.com/redhat-plumbers-in-action/gather-pull-request-metadata/releases) - [Commits](https://github.com/redhat-plumbers-in-action/gather-pull-request-metadata/compare/b86d1eaf7038cf88a56b26ba3e504f10e07b0ce5...62fc85c7acd15db62a0bdf007c8dbeda86eaf3b6) Updates `super-linter/super-linter` from 8.5.0 to 8.6.0 - [Release notes](https://github.com/super-linter/super-linter/releases) - [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md) - [Commits](https://github.com/super-linter/super-linter/compare/61abc07d755095a68f4987d1c2c3d1d64408f1f9...9e863354e3ff62e0727d37183162c4a88873df41) Updates `softprops/action-gh-release` from 2.6.1 to 3.0.0 - [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/153bb8e04406b158c6c84fc1615b65b24149a1fe...b4309332981a82ec1c5618f44dd2e27cc8bfbfda) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: aws-actions/configure-aws-credentials dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: redhat-plumbers-in-action/gather-pull-request-metadata dependency-version: 1.9.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: super-linter/super-linter dependency-version: 8.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: softprops/action-gh-release dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/cifuzz.yml | 2 +- .github/workflows/claude-review.yml | 6 +++--- .github/workflows/gather-pr-metadata.yml | 2 +- .github/workflows/labeler.yml | 6 +++--- .github/workflows/linter.yml | 2 +- .github/workflows/make-release.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index d352b2c7b4028..41f06cf8f1a56 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -67,7 +67,7 @@ jobs: path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e + uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6e0c32d6aec30..516a4087b52e9 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -67,7 +67,7 @@ jobs: - name: Fetch PR context and create tracking comment id: context - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 with: script: | const owner = context.repo.owner; @@ -179,7 +179,7 @@ jobs: sudo apt-get update && sudo apt-get install -y bubblewrap socat - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} role-session-name: GitHubActions-Claude-${{ github.run_id }} @@ -417,7 +417,7 @@ jobs: name: review-result.json - name: Post review comments - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 env: REVIEW_RESULT: ${{ needs.review.result }} PR_NUMBER: ${{ needs.setup.outputs.pr_number }} diff --git a/.github/workflows/gather-pr-metadata.yml b/.github/workflows/gather-pr-metadata.yml index 2ae9a098a6949..6f325c779d8ad 100644 --- a/.github/workflows/gather-pr-metadata.yml +++ b/.github/workflows/gather-pr-metadata.yml @@ -22,7 +22,7 @@ jobs: - id: metadata name: Gather Pull Request Metadata - uses: redhat-plumbers-in-action/gather-pull-request-metadata@b86d1eaf7038cf88a56b26ba3e504f10e07b0ce5 + uses: redhat-plumbers-in-action/gather-pull-request-metadata@62fc85c7acd15db62a0bdf007c8dbeda86eaf3b6 - name: Upload Pull Request Metadata artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 48d926a62b9a4..022d499f7f526 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -44,7 +44,7 @@ jobs: sync-labels: false - name: Set or remove labels based on systemd development workflow - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: startsWith(github.event_name, 'pull_request') && github.event.action != 'closed' && !github.event.pull_request.draft with: script: | @@ -85,7 +85,7 @@ jobs: } - name: Add please-review label on command in issue comment - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/please-review') with: script: | @@ -97,7 +97,7 @@ jobs: }) - name: Remove specific labels when PR is closed or merged - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: startsWith(github.event_name, 'pull_request') && github.event.action == 'closed' with: script: | diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 070f9b814e125..5e9b83b08edec 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: Lint Code Base - uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9 + uses: super-linter/super-linter/slim@9e863354e3ff62e0727d37183162c4a88873df41 env: DEFAULT_BRANCH: main MULTI_STATUS: false diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 3aa169f55ad5c..170d5d49d294e 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@153bb8e04406b158c6c84fc1615b65b24149a1fe + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda with: prerelease: ${{ contains(github.ref_name, '-rc') }} draft: ${{ github.repository == 'systemd/systemd' }} From 26cba7ffee4aa19e003da704be3362ca2a8be966 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 2 May 2026 23:18:22 +0100 Subject: [PATCH 1415/2155] test: make varlink StartTransient checks compatible with jq 1.6 The new "varlinkctl --more StartTransient" subtest pipes a JSON-SEQ stream of multiple records into "jq --seq -e ...". CentOS 9 ships jq 1.6, where -e only inspects the last input record's output: when the trailing record (the final reply) doesn't match the "select()" filter, jq exits non-zero even though earlier records match, so the test fails. Use --slurp which collapses the records into an array first and returns a single bool. Follow-up for 1cde1cc3bab595fe7b4e2befbfa08a01a172db0f --- test/units/TEST-26-SYSTEMCTL.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 32842c1e90e64..ed030031d26cf 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -552,32 +552,35 @@ result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ echo "$result" | grep '"context"' >/dev/null # Streaming with notifyJobChanges: should get intermediate state updates and a final result +# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on +# CentOS 9) -e checks only the last input record's output, so a select() that filters +# out the trailing record makes jq exit non-zero even when earlier records match. defer_transient_cleanup varlink-transient-test3.service result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') -printf '%s' "$result" | jq --seq -e 'select(.job.State == "waiting")' >/dev/null -printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null # Fire-and-forget: --more without notify flags should return immediately with context+runtime defer_transient_cleanup varlink-transient-fireforget.service result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') -printf '%s' "$result" | jq --seq -e 'select(.context)' >/dev/null -printf '%s' "$result" | jq --seq -e 'select(.runtime)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null # Streaming with notifyUnitChanges: should get unit state change notifications defer_transient_cleanup varlink-transient-unitnotify.service result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') -printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null # Streaming with both notifyJobChanges and notifyUnitChanges defer_transient_cleanup varlink-transient-both.service result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') -printf '%s' "$result" | jq --seq -e 'select(.job.State)' >/dev/null -printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null -printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null # prepare for the error case below: create a long-running service, then try to create it again while it's active defer_transient_cleanup varlink-transient-exists.service @@ -589,7 +592,7 @@ timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; defer_transient_cleanup varlink-transient-multi.service result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') -printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null # Transient service with Description and RemainAfterExit defer_transient_cleanup varlink-transient-desc.service From afa4a559e113a0b7c570806ae655c035ae962b7d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 2 May 2026 23:46:46 +0100 Subject: [PATCH 1416/2155] test: avoid getting stuck on /dev/fuse On Fedora Rawhide checking /dev/fuse in the test is getting stuck and timing out: [ 47.930104] TEST-13-NSPAWN.sh[2588]: + testcase_fuse [ 47.930424] TEST-13-NSPAWN.sh[2589]: ++ cat [ 58.772538] audit: type=1131 audit(1777728357.726:778): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-importd comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success' [ 901.882562] audit: type=1131 audit(1777729200.830:782): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-tmpfiles-clean comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success' Finishing after writing 176921 entries qemu-system-x86_64: terminating on signal 15 from pid 70717 (/usr/bin/python3) Wrap it with a timeout to avoid getting stuck forever Follow-up for dc3223919f663b7c8b8d8d1d6072b4487df7709b --- test/units/TEST-13-NSPAWN.nspawn.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index 47c19f08c01f2..0332a12f64665 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -1350,7 +1350,10 @@ testcase_unpriv() { } testcase_fuse() { - if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then + # On some kernels reading from /dev/fuse without an attached connection blocks indefinitely + # rather than returning EPERM, so guard the probe with a short timeout and skip the test + # whenever we don't get the expected error string. + if [[ "$(timeout --foreground 5 cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then echo "FUSE is not supported, skipping the test..." return 0 fi @@ -1381,7 +1384,7 @@ testcase_fuse() { # "cat: -: Operation not permitted" # pass the test; opened but not read # "bash: line 1: /dev/fuse: Operation not permitted" # fail the test; could not open # "" # fail the test; reading worked - [[ "$(systemd-nspawn --register=no --pipe --directory="$root" \ + [[ "$(timeout --foreground 30 systemd-nspawn --register=no --pipe --directory="$root" \ bash -c 'cat <>/dev/fuse' 2>&1)" == 'cat: -: Operation not permitted' ]] rm -fr "$root" @@ -1390,7 +1393,7 @@ testcase_fuse() { testcase_unpriv_fuse() { # Same as above, but for unprivileged operation. - if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then + if [[ "$(timeout --foreground 5 cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then echo "FUSE is not supported, skipping the test..." return 0 fi @@ -1409,7 +1412,7 @@ testcase_unpriv_fuse() { create_dummy_ddi "$tmpdir" "$name" chown --recursive testuser: "$tmpdir" - [[ "$(run0 -u testuser --pipe systemd-run \ + [[ "$(timeout --foreground 60 run0 -u testuser --pipe systemd-run \ --user \ --pipe \ --property=Delegate=yes \ From 378d6bd9c68f7085448975ae9e95d20e37c6390b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 3 May 2026 00:06:56 +0100 Subject: [PATCH 1417/2155] test: fix json encoding issue due to backslashes TEST-74-AUX-UTILS.sh[3782]: + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "proc-sysrq\x2dtrigger.mount"}' TEST-74-AUX-UTILS.sh[3783]: + jq -e .context.Mount TEST-74-AUX-UTILS.sh[3782]: Failed to parse parameters at :1:10: Invalid argument Use jq to encode the input --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 9a22757067f24..3e6d4a9a257e6 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -235,13 +235,17 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {" # 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' +# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that +# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. +automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | 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' +mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From 8fbc93345e50d4541600e1ec9e633d4c33f7d787 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 3 May 2026 13:27:32 +0100 Subject: [PATCH 1418/2155] test: bump device timeout to 300s for TPM2 tests Booting with TPM2 has become slower recently so tests are randomly failing, try to bump the default device timeout in those test VMs --- test/integration-tests/TEST-70-TPM2/meson.build | 3 +++ test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/integration-tests/TEST-70-TPM2/meson.build b/test/integration-tests/TEST-70-TPM2/meson.build index bf66f8f73e3ce..5932215ceb6f3 100644 --- a/test/integration-tests/TEST-70-TPM2/meson.build +++ b/test/integration-tests/TEST-70-TPM2/meson.build @@ -10,5 +10,8 @@ integration_tests += [ 'vm' : true, 'firmware' : 'auto', 'tpm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'systemd.default_device_timeout_sec=300', + ], }, ] diff --git a/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build b/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build index 51a70970906b9..acc3512e33d7e 100644 --- a/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build +++ b/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build @@ -7,5 +7,8 @@ integration_tests += [ 'vm' : true, 'firmware' : 'auto', 'tpm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'systemd.default_device_timeout_sec=300', + ], }, ] From 2fc008b9e437065c5e33b98cc7410dc91f555564 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 3 May 2026 16:23:41 +0100 Subject: [PATCH 1419/2155] test: make TEST-70-TPM2 robust against reruns The test leaves a lot of state around, and when the test is re-run, for example due to the qemu bug that makes a VM reboot instead of shutting down, it fails. Do more cleanups in the traps. [ 162.642175] TEST-70-TPM2.sh[2815]: Calculated public key name: 000b2b66edc3a466e81059286aaf38d09ea42a7a9dcdf6ba3b664c62f0cae4ce4f66 [ 162.642628] TEST-70-TPM2.sh[2815]: PolicyAuthorize calculated digest: 2caa740101f65734d50395d6abc64fa46015d40d1f5de239434578544e592a92 [ 162.643681] TEST-70-TPM2.sh[2815]: Calculated NV index name: 000b439cfa1534815bbe8d33b80c56f5a8d17d36fe94a7782b23a37b50def5fc5eaa [ 162.645111] TEST-70-TPM2.sh[2815]: PolicyAuthorizeNV calculated digest: 69ee0e89fafe6b9df2cd6a5defbf74aa46cf6d92703e645d463549da4ba5e1a4 [ 162.645407] TEST-70-TPM2.sh[2815]: Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently. [ 162.649576] TEST-70-TPM2.sh[2815]: Releasing crypt device /dev/loop0 context. [ 162.652433] TEST-70-TPM2.sh[2815]: Releasing device-mapper backend. [ 162.653518] TEST-70-TPM2.sh[2815]: Closing read only fd for /dev/loop0. [ 162.654359] TEST-70-TPM2.sh[2815]: Closing read write fd for /dev/loop0. [ 162.654786] TEST-70-TPM2.sh[2815]: Failed to encrypt device: Operation not supported Fixes https://github.com/systemd/systemd/issues/38241 --- test/units/TEST-70-TPM2.creds.sh | 8 ++++++-- test/units/TEST-70-TPM2.cryptenroll.sh | 6 ++++++ test/units/TEST-70-TPM2.cryptsetup.sh | 13 +++++++++++++ test/units/TEST-70-TPM2.measure.sh | 25 +++++++++++++++++++++++++ test/units/TEST-70-TPM2.nvpcr.sh | 2 +- test/units/TEST-70-TPM2.pcrextend.sh | 10 ++++++++++ test/units/TEST-70-TPM2.pcrlock.sh | 21 ++++++++++++++++++++- 7 files changed, 81 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-70-TPM2.creds.sh b/test/units/TEST-70-TPM2.creds.sh index 15899d1057899..53ff862e18cd5 100755 --- a/test/units/TEST-70-TPM2.creds.sh +++ b/test/units/TEST-70-TPM2.creds.sh @@ -5,6 +5,12 @@ set -o pipefail export SYSTEMD_LOG_LEVEL=debug +at_exit() { + rm -f /tmp/testdata /tmp/testdata.encrypted +} + +trap at_exit EXIT + # Ensure that sandboxing doesn't stop creds from being accessible echo "test" >/tmp/testdata systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 @@ -12,5 +18,3 @@ systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata # SetCredentialEncrypted systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata - -rm -f /tmp/testdata diff --git a/test/units/TEST-70-TPM2.cryptenroll.sh b/test/units/TEST-70-TPM2.cryptenroll.sh index d09f702093681..07309429d749d 100755 --- a/test/units/TEST-70-TPM2.cryptenroll.sh +++ b/test/units/TEST-70-TPM2.cryptenroll.sh @@ -11,6 +11,12 @@ cryptenroll_wipe_and_check() {( grep -qE "Wiped slot [[:digit:]]+" /tmp/cryptenroll.out )} +at_exit() { + rm -f "${IMAGE:-}" /tmp/cryptenroll.out /tmp/password +} + +trap at_exit EXIT + # There is an external issue with libcryptsetup on ppc64 that hits 95% of Ubuntu ppc64 test runs, so skip it if [[ "$(uname -m)" == "ppc64le" ]]; then echo "Skipping systemd-cryptenroll tests on ppc64le, see https://github.com/systemd/systemd/issues/27716" diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index 24c87d0f2495c..5a7f0facfcc0c 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -31,10 +31,23 @@ tpm_check_failure_with_wrong_pin() { } at_exit() { + set +e + + umount /tmp/dditest.mnt + systemd-cryptsetup detach test-volume + systemd-cryptsetup detach dditest + # Evict the TPM primary key that we persisted if [[ -n "${PERSISTENT_HANDLE:-}" ]]; then tpm2_evictcontrol -c "$PERSISTENT_HANDLE" fi + + if [[ -n "${DEVICE:-}" ]]; then + systemd-dissect --detach "$DEVICE" + fi + + rm -rf /tmp/dditest /tmp/dditest.mnt + rm -f /tmp/dditest.raw "${IMAGE:-}" "${PRIMARY:-}" /tmp/passphrase /tmp/pcr.dat /tmp/srk.pub /tmp/srk2.pub } trap at_exit EXIT diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh index 30fa51e52137c..90d6390da0964 100755 --- a/test/units/TEST-70-TPM2.measure.sh +++ b/test/units/TEST-70-TPM2.measure.sh @@ -14,6 +14,31 @@ if [[ ! -x "${SD_MEASURE:?}" ]]; then exit 0 fi +at_exit() { + set +e + + systemd-cryptsetup detach test-volume2 + rm -f "${IMAGE:-}" \ + /tmp/passphrase \ + /tmp/pcrsign-private.pem \ + /tmp/pcrsign-public.pem \ + /tmp/pcrsign.sig \ + /tmp/pcrsign.sig2 \ + /tmp/pcrsign.sig3 \ + /tmp/pcrsign.sig4 \ + /tmp/pcrsign.sig5 \ + /tmp/pcrsign.sig6 \ + /tmp/pcrsign.sig7 \ + /tmp/pcrtestdata \ + /tmp/pcrtestdata.encrypted \ + /tmp/result \ + /tmp/result.json \ + /tmp/tpmdata1 \ + /tmp/tpmdata2 +} + +trap at_exit EXIT + IMAGE="$(mktemp /tmp/systemd-measure-XXX.image)" echo HALLO >/tmp/tpmdata1 diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh index 29319e601aced..571b3eea770b3 100755 --- a/test/units/TEST-70-TPM2.nvpcr.sh +++ b/test/units/TEST-70-TPM2.nvpcr.sh @@ -21,7 +21,7 @@ at_exit() { fi rm -rf /run/nvpcr /tmp/nvpcr - rm -f /var/tmp/nvpcr.raw /run/verity.d/test-79-nvpcr.crt + rm -f /var/tmp/nvpcr.raw /run/verity.d/test-70-nvpcr.crt /run/systemd/nvpcr/test.anchor } trap at_exit EXIT diff --git a/test/units/TEST-70-TPM2.pcrextend.sh b/test/units/TEST-70-TPM2.pcrextend.sh index 14808f07637bd..ec330576b2531 100755 --- a/test/units/TEST-70-TPM2.pcrextend.sh +++ b/test/units/TEST-70-TPM2.pcrextend.sh @@ -19,6 +19,16 @@ at_exit() { # Dump the event log on fail, to make debugging a bit easier jq --seq --slurp Date: Sun, 3 May 2026 16:33:38 +0100 Subject: [PATCH 1420/2155] test: make TEST-86-MULTI-PROFILE-UKI robust against reruns When qemu reboots instead of shutting down after the last iteration, the profile is already set to profile2 but the /root/encrypted.raw is gone so the test fails. Reset the default boot entry at the end of the test to make it robust against reruns. Fixes https://github.com/systemd/systemd/issues/39553 --- test/units/TEST-86-MULTI-PROFILE-UKI.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/units/TEST-86-MULTI-PROFILE-UKI.sh b/test/units/TEST-86-MULTI-PROFILE-UKI.sh index 5518cb656149f..72fb7b6f554af 100755 --- a/test/units/TEST-86-MULTI-PROFILE-UKI.sh +++ b/test/units/TEST-86-MULTI-PROFILE-UKI.sh @@ -64,6 +64,9 @@ elif [[ "$ID" == "profile1" ]]; then elif [[ "$ID" == "profile2" ]]; then grep testprofile2=1 /proc/cmdline rm /root/encrypted.raw + # Reset the default boot entry so a subsequent re-run of the test does not + # boot straight back into @profile2 (where encrypted.raw is now gone) and fail. + bootctl set-default "" else exit 1 fi From 5580db885c08b45bec5f665cbb0b114fa2538736 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Sat, 18 Apr 2026 15:09:00 +0200 Subject: [PATCH 1421/2155] boot: Try to load UKI from simple filesystem before LoadImage When the source buffer is NULL, the firmware is supposed to try to load the UKI with simple filesystem protocol then load file 2 protocol. But it seems on some versions of AMI, it does not use simple filesystem protocol, and then fails to load if the ESP was loaded from an El Torito boot catalog. Trying to load the source buffer from the simple filesystem protocol protocols seems work around this limitation. Shim for example, also loads the source buffer before calling LoadImage. So it seems to be a safe thing to do. We could also maybe in the future use load file 2 protocol if simple filesystem failed in the first place. --- src/boot/shim.c | 33 ++++++++++++--------------------- src/boot/util.c | 26 ++++++++++++++++++++++++++ src/boot/util.h | 1 + 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/boot/shim.c b/src/boot/shim.c index 97410d6669659..10a0642ac3b9f 100644 --- a/src/boot/shim.c +++ b/src/boot/shim.c @@ -8,7 +8,6 @@ * https://github.com/mjg59/efitools */ -#include "device-path-util.h" #include "efi-efivars.h" #include "secure-boot.h" #include "shim.h" @@ -56,24 +55,7 @@ static bool shim_validate( if (!device_path) return false; - EFI_HANDLE device_handle; - EFI_DEVICE_PATH *file_dp = (EFI_DEVICE_PATH *) device_path; - err = BS->LocateDevicePath( - MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &file_dp, &device_handle); - if (err != EFI_SUCCESS) - return false; - - _cleanup_file_close_ EFI_FILE *root = NULL; - err = open_volume(device_handle, &root); - if (err != EFI_SUCCESS) - return false; - - _cleanup_free_ char16_t *dp_str = NULL; - err = device_path_to_str(file_dp, &dp_str); - if (err != EFI_SUCCESS) - return false; - - err = file_read(root, dp_str, 0, 0, &file_buffer_owned, &file_size); + err = load_file_from_simple_filesystem(device_path, &file_buffer_owned, &file_size); if (err != EFI_SUCCESS) return false; @@ -111,12 +93,21 @@ EFI_STATUS shim_load_image( if (have_shim) install_security_override(shim_validate, NULL); + _cleanup_free_ char *source_buffer = NULL; + size_t source_size = 0; + + /* For some AMI firmware, BS->LoadImage() does not read correctly when the file comes the ESP on an + * optical drive. But the simple filesystem protocol does work. So we try to load it. If that does + * not work, we let BS->LoadImage() try instead. + */ + (void) load_file_from_simple_filesystem(device_path, &source_buffer, &source_size); + EFI_STATUS ret = BS->LoadImage( /* BootPolicy= */ boot_policy, parent, (EFI_DEVICE_PATH *) device_path, - /* SourceBuffer= */ NULL, - /* SourceSize= */ 0, + source_buffer, + source_size, ret_image); if (have_shim) uninstall_security_override(); diff --git a/src/boot/util.c b/src/boot/util.c index c40a9aad65b0d..22981bc00db1f 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -195,6 +195,32 @@ EFI_STATUS file_read( return file_handle_read(handle, offset, size, ret, ret_size); } +EFI_STATUS load_file_from_simple_filesystem(const EFI_DEVICE_PATH *device_path, char **file_buffer, size_t *file_size) { + EFI_STATUS err; + EFI_HANDLE device_handle; + EFI_DEVICE_PATH *file_dp = (EFI_DEVICE_PATH *) device_path; + + assert(device_path); + assert(file_buffer); + assert(file_size); + + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &file_dp, &device_handle); + if (err != EFI_SUCCESS) + return err; + + _cleanup_file_close_ EFI_FILE *root = NULL; + err = open_volume(device_handle, &root); + if (err != EFI_SUCCESS) + return err; + + _cleanup_free_ char16_t *dp_str = NULL; + err = device_path_to_str(file_dp, &dp_str); + if (err != EFI_SUCCESS) + return err; + + return file_read(root, dp_str, 0, 0, file_buffer, file_size); +} + void set_attribute_safe(size_t attr) { /* Various UEFI implementations suppress color changes from a color to the same color. Often, we want * to force out the color change though, hence change the color here once, and then back. We simply diff --git a/src/boot/util.h b/src/boot/util.h index 2c8cc36ea580d..fa552d6f46c08 100644 --- a/src/boot/util.h +++ b/src/boot/util.h @@ -138,6 +138,7 @@ char16_t *mangle_stub_cmdline(char16_t *cmdline); EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf); EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, uint64_t offset, size_t size, char **ret, size_t *ret_size); +EFI_STATUS load_file_from_simple_filesystem(const EFI_DEVICE_PATH *device_path, char **file_buffer, size_t *file_size); EFI_STATUS file_handle_read(EFI_FILE *handle, uint64_t offset, size_t size, char **ret, size_t *ret_size); static inline void file_closep(EFI_FILE **handle) { From 4b35847aa3395890986bf5f93236160e0cfd9f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 4 May 2026 10:10:13 +0200 Subject: [PATCH 1422/2155] man/sd-bus: add a note that tcp: is w/o encryption --- man/sd_bus_set_address.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/sd_bus_set_address.xml b/man/sd_bus_set_address.xml index 603f153221c80..0057cc6afe8b3 100644 --- a/man/sd_bus_set_address.xml +++ b/man/sd_bus_set_address.xml @@ -74,6 +74,11 @@ One or both of the host= and port= keys must be present, while the rest is optional. family may be either or . + + Note: connections over TCP are made without encryption. Thus, this mode + should only be used in specific situations where integrity and confidentiality of the connection is + not necessary or is ensured through some other means. For local connections, unix: + connections should be used instead. From bd9971cd25f452e164e6c5af798a73d16aadbbd9 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Thu, 30 Apr 2026 16:44:39 +0200 Subject: [PATCH 1423/2155] cryptsetup: avoid a segfault when a keyfile is passed along with a TPM device When a keyfile is passed with tpm2-device=, e.g., systemd-cryptsetup attach test_data /vol /my-pass tpm2-device=auto the logic in attach_luks_or_plain_or_bitlk_by_tpm2() tries to process it as a TPM blob first. This did not work properly because it passes n_blobs=1 to acquire_tpm2_key(), and the key_file is only read when n_blobs == 0. As a result, the code ends up calling tpm2_unseal(..., blobs=NULL, n_blobs=1, ...). Before commit 5c6aad9 ("cryptsetup-tokens: Print tpm2-primary-alg: only when it is known"), the segfault was not observed because tpm2_unseal() was bailing out early when primary_alg == 0. However, after that change, it attempts to process the blob (which is NULL) and crashes. Fix this logic by passing n_blobs=0 to acquire_tpm2_key() so that it actually reads the key_file. Additionally, assert 'blobs' in tpm2_unseal() as a safeguard. Fixes #41867 --- src/cryptsetup/cryptsetup.c | 2 +- src/shared/tpm2-util.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 2130c54047c04..bf313340bf6c2 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2060,7 +2060,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( /* pcrlock_path= */ NULL, /* primary_alg= */ 0, key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, /* n_blobs= */ 1, + key_data, /* n_blobs= */ iovec_is_set(key_data) ? 1 : 0, /* policy_hash= */ NULL, /* we don't know the policy hash */ /* n_policy_hash= */ 0, /* salt= */ NULL, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 9fe3e018693fc..091a4dba89ac3 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -5804,6 +5804,7 @@ int tpm2_unseal(Tpm2Context *c, int r; assert(n_blobs > 0); + assert(blobs); assert(iovec_is_valid(pubkey)); assert(ret_secret); From 4820d57eeec98385c25a47e427826f466590360a Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Thu, 30 Apr 2026 16:44:45 +0200 Subject: [PATCH 1424/2155] TEST-70-TPM2: Test the key_file + tpm2-device= combo When key_file is passed along with tpm2-device= to systemd-cryptsetup, the logic is to try the blob as a TPM blob first, and then fall back to trying the file as a regular key file. Check that this fallback works. --- test/units/TEST-70-TPM2.cryptsetup.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index 5a7f0facfcc0c..a40f739689224 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -57,8 +57,9 @@ IMAGE="$(mktemp /tmp/systemd-cryptsetup-XXX.IMAGE)" truncate -s 20M "$IMAGE" echo -n passphrase >/tmp/passphrase +echo -n wrong_passphrase >/tmp/wrong_passphrase # Change file mode to avoid "/tmp/passphrase has 0644 mode that is too permissive" messages -chmod 0600 /tmp/passphrase +chmod 0600 /tmp/passphrase /tmp/wrong_passphrase cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase # Unlocking via keyfile @@ -237,4 +238,11 @@ EOF rmdir /tmp/dditest fi -rm -f "$IMAGE" "$PRIMARY" +# Key file can contain a TPM blob but in case it doesn't fallback should also work. +systemd-cryptsetup attach test-volume "$IMAGE" /tmp/passphrase tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Negative test: invalid passphrase should not work. +(! systemd-cryptsetup attach test-volume "$IMAGE" /tmp/wrong_passphrase tpm2-device=auto,headless=1) + +rm -f "$IMAGE" "$PRIMARY" /tmp/passphrase /tmp/wrong_passphrase From 7e6507c43fd16ced45b0080aa8c1883ad62fe054 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 3 May 2026 23:31:59 +0100 Subject: [PATCH 1425/2155] test: fix flaky TEST-04-JOURNAL.reload.sh due to service name collision write_to_journal() was called via $(...) command substitution, so SERVICE_COUNTER++ ran in a subshell and never incremented in the parent: [ 1492.668302] TEST-04-JOURNAL.sh[15064]: + local service=test-0-18493.service [ 1492.725882] TEST-04-JOURNAL.sh[15064]: + local service=test-0-18009.service [ 1492.739643] TEST-04-JOURNAL.sh[15064]: + local service=test-0-18493.service [ 1492.774586] TEST-04-JOURNAL.sh[15064]: + local service=test-0-25540.service [ 1492.815664] TEST-04-JOURNAL.sh[15064]: + local service=test-0-15916.service [ 1492.867067] TEST-04-JOURNAL.sh[15064]: + local service=test-0-20327.service [ 1492.899077] TEST-04-JOURNAL.sh[15064]: + local service=test-0-86.service [ 1497.289715] TEST-04-JOURNAL.sh[15064]: + local service=test-0-10849.service [ 1497.335791] TEST-04-JOURNAL.sh[15064]: + local service=test-0-18009.service With 99999 possible unit names collisions are rare but not impossible, so every now and then a CI run fails. Have write_to_journal() set a global SERVICE_NAME variable instead and call it directly so SERVICE_COUNTER actually goes up through the test. --- test/units/TEST-04-JOURNAL.reload.sh | 42 +++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/units/TEST-04-JOURNAL.reload.sh b/test/units/TEST-04-JOURNAL.reload.sh index 44003028aa292..a58c3de59f0fd 100755 --- a/test/units/TEST-04-JOURNAL.reload.sh +++ b/test/units/TEST-04-JOURNAL.reload.sh @@ -11,12 +11,12 @@ MACHINE_ID="$(persistent)" @@ -73,7 +74,8 @@ verify_journals "$VAL1" persistent : "Add entries in runtime" journalctl --relinquish -VAL2=$(write_to_journal) +write_to_journal +VAL2="$SERVICE_NAME" verify_journals "$VAL2" runtime : "Reload journald after relinquish (persistent->persistent)" @@ -84,13 +86,13 @@ verify_journals "$VAL1" persistent verify_journals "$VAL2" runtime : "Write new message and confirm it's written to runtime." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Flush and confirm that messages are written to system." journalctl --flush -VAL=$(write_to_journal) -verify_journals "$VAL" persistent +write_to_journal +verify_journals "$SERVICE_NAME" persistent # Test persistent->volatile cat </run/systemd/journald.conf.d/reload.conf @@ -100,16 +102,16 @@ EOF : "Confirm old message exists where it was written to persistent journal." systemctl reload systemd-journald.service -verify_journals "$VAL" persistent +verify_journals "$SERVICE_NAME" persistent : "Confirm that new message is written to runtime journal." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Test volatile works and logs are NOT getting written to system journal despite flush." journalctl --flush -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Disable compression" cat </run/systemd/journald.conf.d/reload.conf @@ -154,8 +156,8 @@ if (( total_size > max_size )) && (( num_archived_journals > 0 )); then fi : "Write a message to runtime journal" -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Reload volatile->persistent" cat </run/systemd/journald.conf.d/reload.conf @@ -165,15 +167,15 @@ EOF systemctl reload systemd-journald.service : "Confirm that previous message is still in runtime journal." -verify_journals "$VAL" runtime +verify_journals "$SERVICE_NAME" runtime : "Confirm that new messages are written to runtime journal." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Confirm that flushing writes to system journal." journalctl --flush -verify_journals "$VAL" persistent +verify_journals "$SERVICE_NAME" persistent : "Disable compression" cat </run/systemd/journald.conf.d/reload.conf From 4a11c5edeb37de4fd73dc2ff059e7a6112514583 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Mon, 4 May 2026 10:25:19 +0200 Subject: [PATCH 1426/2155] core: Open netfilter socket only when needed On initrds where nfnetlink module is missing, trying to open a NETLINK_NETFILTER netlink socket takes a lot of time then fails. This makes boot noticibly slower. Even though probably no unit in an initrd need netfilter. So here we delay opening the socket until we know we need it. --- src/core/cgroup.c | 12 ++++++------ src/core/unit.c | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/cgroup.c b/src/core/cgroup.c index ae5874cd99daa..acf2e8147f41b 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -1293,18 +1293,18 @@ static void unit_modify_nft_set(Unit *u, bool add) { if (!crt || crt->cgroup_id == 0) return; - if (!u->manager->nfnl) { - r = sd_nfnl_socket_open(&u->manager->nfnl); - if (r < 0) - return; - } - CGroupContext *c = ASSERT_PTR(unit_get_cgroup_context(u)); FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) { if (nft_set->source != NFT_SET_SOURCE_CGROUP) continue; + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); + if (r < 0) + return (void) log_once_errno(LOG_WARNING, r, "Failed to open NETLINK_NETFILTER socket, ignoring: %m"); + } + uint64_t element = crt->cgroup_id; r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); diff --git a/src/core/unit.c b/src/core/unit.c index 0edb7e25aaa1d..8ed74b080d144 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -5393,16 +5393,16 @@ static void unit_modify_user_nft_set(Unit *u, bool add, NFTSetSource source, uin if (!c) return; - if (!u->manager->nfnl) { - r = sd_nfnl_socket_open(&u->manager->nfnl); - if (r < 0) - return; - } - FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) { if (nft_set->source != source) continue; + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); + if (r < 0) + return (void) log_once_errno(LOG_WARNING, r, "Failed to open NETLINK_NETFILTER socket, ignoring: %m"); + } + r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); if (r < 0) log_warning_errno(r, "Failed to %s NFT set entry: family %s, table %s, set %s, ID %u, ignoring: %m", From 4276d3721e4bc4d8d4966b95c106fab1517f188f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 4 May 2026 12:58:33 +0100 Subject: [PATCH 1427/2155] semaphore: stop deleting all apt sources The image configuration was changed and the main sources are now in a drop-in apt sources files too, so deleting the whole drop-in directory breaks installing packages. Just delete the disabled ones and chrome. --- .semaphore/semaphore-runner.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.semaphore/semaphore-runner.sh b/.semaphore/semaphore-runner.sh index 171cac8e1c702..22dc9fc4ffd73 100755 --- a/.semaphore/semaphore-runner.sh +++ b/.semaphore/semaphore-runner.sh @@ -68,8 +68,8 @@ EOF for phase in "${PHASES[@]}"; do case "$phase" in SETUP) - # remove semaphore repos, some of them don't work and cause error messages - sudo rm -rf /etc/apt/sources.list.d/* + # remove chrome repo, we don't need it + sudo rm -rf /etc/apt/sources.list.d/google-chrome.sources # enable backports for latest LXC echo "deb http://archive.ubuntu.com/ubuntu $UBUNTU_RELEASE-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/backports.list From a1d0c58220896e483adbca7386b47f29d30dd09b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 3 May 2026 22:16:15 +0100 Subject: [PATCH 1428/2155] test: make TEST-64 mdadm_lvm cleanup robust against reruns mdadm --zero-superblock only wipes the MD metadata on the underlying disks, not the LVM PV header that lives in the array data area. When the VM is restarted and the test re-creates the array with the same UUID, /dev/md127 exposes the old data including the LVM PV header, so udev's 69-lvm.rules auto-triggers lvm-activate-mdlvm_vg.service which races with the test's own pvcreate for exclusive access on /dev/md127. Wipe the LVM signature off the MD device (and the underlying disks as a belt-and-braces measure) to avoid the race on re-run, fixing failures when the VM is rebooted instead of shut down. Co-developed-by: Claude Opus 4.7 --- test/units/TEST-64-UDEV-STORAGE.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/units/TEST-64-UDEV-STORAGE.sh b/test/units/TEST-64-UDEV-STORAGE.sh index f7bc59ff44a1b..de2d7d267212f 100755 --- a/test/units/TEST-64-UDEV-STORAGE.sh +++ b/test/units/TEST-64-UDEV-STORAGE.sh @@ -1333,6 +1333,12 @@ testcase_mdadm_lvm() { helper_check_device_units # Cleanup lvm vgchange -an "$vgroup" + # Wipe the LVM signature off the MD device, otherwise the underlying disks + # still hold the PV header at the same offset. If the VM is restarted (e.g. + # the test gets re-run because of a reboot), mdadm --create with the same + # UUID would expose the same data and udev would auto-trigger + # lvm-activate-${vgroup}.service, racing with the test's pvcreate. + wipefs --all "$raid_dev" mdadm -v --stop "$raid_dev" # Clear superblocks to make the MD device will not be restarted even if the VM is restarted. @@ -1340,6 +1346,10 @@ testcase_mdadm_lvm() { udevadm settle --timeout=30 # shellcheck disable=SC2046 mdadm -v --zero-superblock --force $(readlink -f "${devices[@]}") + # Also wipe any leftover signatures from the underlying disks for the same + # reason as above. + # shellcheck disable=SC2046 + wipefs --all $(readlink -f "${devices[@]}") udevadm settle --timeout=30 # Check if all expected symlinks were removed after the cleanup From 74338c0bb000cd805a87ce355478c5a0eae113b9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 4 May 2026 14:42:03 +0100 Subject: [PATCH 1429/2155] test: suppress PCR public key auto-loading in TEST-70-TPM2 dditest The dditest block calls systemd-repart with Encrypt=tpm2 but without --tpm2-public-key-pcrs=. Since systemd-stub drops /run/systemd/tpm2-pcr-public-key.pem when booting from a signed UKI systemd-repart auto-loads it and enrolls a signed PCR policy, and then systemd-cryptsetup tpm2-device=auto has no matching signature file, so unlock fails. --tpm2-public-key= is not enough as the default kicks in then. Follow-up for cd18656d47710c251a44a8f5f9d616151a909152 --- test/units/TEST-70-TPM2.cryptsetup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index 5a7f0facfcc0c..63f1373474450 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -223,7 +223,8 @@ Format=ext4 CopyFiles=/tmp/dditest:/ Encrypt=tpm2 EOF - PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --definitions=/tmp/dditest --empty=create --size=80M /tmp/dditest.raw --tpm2-pcrs= + # Use --tpm2-public-key-pcrs= to suppress auto-loading of the system PCR public key + PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --tpm2-public-key-pcrs= --definitions=/tmp/dditest --empty=create --size=80M /tmp/dditest.raw --tpm2-pcrs= DEVICE="$(systemd-dissect --attach /tmp/dditest.raw)" udevadm wait --settle --timeout=10 "$DEVICE"p1 systemd-cryptsetup attach dditest "$DEVICE"p1 - tpm2-device=auto,headless=yes From 50138e36ffc6d05d8c32a5b6ac1d4c5d32377450 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 4 May 2026 21:38:56 +0200 Subject: [PATCH 1430/2155] resolve: enforce the search domain limit earlier The search domain limit is already enforced by dns_search_domain_new(), but in this case it's way too late. Let's enforce it during the first loop to avoid unnecessary parsing. --- src/resolve/resolved-link-bus.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index ed4485671c8ad..f30ed5d22bac8 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -321,7 +321,7 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - for (;;) { + for (unsigned n_names = 0;; n_names++) { _cleanup_free_ char *prefixed = NULL; const char *name; int route_only; @@ -339,6 +339,8 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); if (!route_only && dns_name_is_root(name)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + if (n_names >= LINK_SEARCH_DOMAINS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many search domains per link"); if (route_only) { prefixed = strjoin("~", name); From 17e6a3e2a88e822b730f298ebb9fdb526a04a2e2 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 4 May 2026 22:07:46 +0200 Subject: [PATCH 1431/2155] resolve: limit the number NTAs to something sensible --- src/resolve/resolved-link-bus.c | 3 +++ src/resolve/resolved-link.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index f30ed5d22bac8..ba5b00c239afb 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -683,6 +683,9 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v if (r < 0) return r; + if (strv_length(ntas) > LINK_NEGATIVE_TRUST_ANCHORS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many negative trust anchors per link"); + STRV_FOREACH(i, ntas) { r = dns_name_is_valid(*i); if (r < 0) diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 44a6b511c1b67..4c81bdbe66695 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -11,6 +11,7 @@ #define LINK_SEARCH_DOMAINS_MAX 1024 #define LINK_DNS_SERVERS_MAX 256 +#define LINK_NEGATIVE_TRUST_ANCHORS_MAX 2048 typedef struct LinkAddress { Link *link; From 29b00c956fdfcc3516717eb5d7f13be237bb4f1a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 5 May 2026 08:02:07 +0200 Subject: [PATCH 1432/2155] update TODO --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.md b/TODO.md index b5777556e999d..588ff720d49ee 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,10 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- a tool that can prep credentials, put them in the ESP, for provisioning + systems for SBC. Should be doing what sysinstall does with the credentials, + and maybe even *be* sysinstall. + - StorageProvider interface + storagectl - hook-up in systemd-nspawn - hook-up in systemd-vmspawn From a551c1bd56ef2756eb0604c8f2a7d1a1b63c9c77 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 4 May 2026 21:06:02 +0100 Subject: [PATCH 1433/2155] test: skip TEST-07-PID1.DeferReactivation with sanitizers This test relies on tight timers, and is flaky under sanitizers as everything slows down a lot. Just skip it. --- test/units/TEST-07-PID1.DeferReactivation.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/units/TEST-07-PID1.DeferReactivation.sh b/test/units/TEST-07-PID1.DeferReactivation.sh index ff795ff002239..8d16ff114507f 100755 --- a/test/units/TEST-07-PID1.DeferReactivation.sh +++ b/test/units/TEST-07-PID1.DeferReactivation.sh @@ -4,6 +4,13 @@ set -eux set -o pipefail +if [[ -v ASAN_OPTIONS ]]; then + # Under sanitizers the service is slow enough that the calendar timer with 5s resolution ends up + # missing ticks, making the test flaky + echo "Sanitizers detected, skipping the test..." + exit 0 +fi + systemctl start defer-reactivation.timer timeout 20 bash -c 'until [[ -e /tmp/defer-reactivation.log ]]; do sleep .5; done' From ae973bb61767fce6b428f52b20cfcddf2c70c944 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 4 May 2026 23:41:10 +0100 Subject: [PATCH 1434/2155] test: avoid nspawn failure due to scope in use in TEST-06-SELINUX TEST-06-SELINUX occasionally fails because repeated nspawn invocations use the same machine name and scope: TEST-06-SELINUX.sh[598]: Failed to allocate scope: Unit H.scope was already loaded or has a fragment file. Use a different machine name/scope for each invocation in the test case to avoid hitting this issue --- test/units/TEST-06-SELINUX.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/units/TEST-06-SELINUX.sh b/test/units/TEST-06-SELINUX.sh index 5cf6e80815b12..76f5c1652bf07 100755 --- a/test/units/TEST-06-SELINUX.sh +++ b/test/units/TEST-06-SELINUX.sh @@ -39,12 +39,13 @@ CONTEXT="$(stat -c %C /proc/sys/kernel/core_pattern)" (! systemd-run --wait --pipe -p ConditionSecurity='selinux' false) systemd-run --wait --pipe -p ConditionSecurity='!selinux' false +# Pass a unique --machine= name on each invocation to avoid "already loaded" flakiness NSPAWN_ARGS=(systemd-nspawn -q --volatile=yes --directory=/ --bind-ro=/etc --inaccessible=/etc/machine-id) -[[ "$("${NSPAWN_ARGS[@]}" cat /proc/self/attr/current | tr -d '\0')" != "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-context="$CONTEXT" cat /proc/self/attr/current | tr -d '\0')" == "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" stat --printf %C /run)" != "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-apifs-context="$CONTEXT" stat --printf %C /run)" == "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-apifs-context="$CONTEXT" --tmpfs=/tmp stat --printf %C /tmp)" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-0" cat /proc/self/attr/current | tr -d '\0')" != "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-1" --selinux-context="$CONTEXT" cat /proc/self/attr/current | tr -d '\0')" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-2" stat --printf %C /run)" != "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-3" --selinux-apifs-context="$CONTEXT" stat --printf %C /run)" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-4" --selinux-apifs-context="$CONTEXT" --tmpfs=/tmp stat --printf %C /tmp)" == "$CONTEXT" ]] if [[ -n "${TEST_SELINUX_CHECK_AVCS:-}" ]] && ((TEST_SELINUX_CHECK_AVCS)); then (! journalctl -t audit -g AVC -o cat) From f7434671bda922781e8bc3ddaf108a7e2127bfc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 10:40:31 +0200 Subject: [PATCH 1435/2155] shared/help-util: automatically append ":" in sections --- src/ac-power/ac-power.c | 2 +- src/notify/notify.c | 2 +- src/run/run.c | 8 ++------ src/shared/help-util.c | 2 +- src/storage/storage-block.c | 2 +- src/storage/storage-fs.c | 2 +- src/storage/storagectl.c | 4 ++-- src/systemctl/systemctl.c | 16 ++++++++-------- src/udev/ata_id/ata_id.c | 2 +- src/udev/cdrom_id/cdrom_id.c | 2 +- src/udev/dmi_memory_id/dmi_memory_id.c | 2 +- src/udev/fido_id/fido_id.c | 2 +- src/udev/iocost/iocost.c | 4 ++-- src/udev/mtd_probe/mtd_probe.c | 2 +- src/udev/scsi_id/scsi_id.c | 2 +- src/udev/udev-config.c | 2 +- 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 | 4 ++-- 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/v4l_id/v4l_id.c | 2 +- 30 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 2a9c517329321..87242a3b08c7f 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -27,7 +27,7 @@ static int help(void) { help_cmdline("[OPTIONS...]"); help_abstract("Report whether we are connected to an external power source."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/notify/notify.c b/src/notify/notify.c index 6c50e4c57c394..0058eb9d3b961 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -69,7 +69,7 @@ static int help(void) { help_cmdline("[OPTIONS...] --fork -- CMDLINE..."); help_abstract("Notify the service manager about service status updates."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/run/run.c b/src/run/run.c index 46b8014e580c5..afae5b2d94af4 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -166,11 +166,7 @@ static int help(void) { help_abstract("Run the specified command in a transient scope or service."); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - _cleanup_free_ char *title = strjoin(groups[i] ?: "Options", ":"); - if (!title) - return log_oom(); - - help_section(title); + help_section(groups[i] ?: "Options"); r = table_print_or_warn(tables[i]); if (r < 0) @@ -196,7 +192,7 @@ static int help_sudo_mode(void) { help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); help_abstract("Elevate privileges interactively."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(opts_table); if (r < 0) diff --git a/src/shared/help-util.c b/src/shared/help-util.c index 7e9d3e70be0c6..7b67bb58f1a21 100644 --- a/src/shared/help-util.c +++ b/src/shared/help-util.c @@ -47,7 +47,7 @@ void help_abstract(const char *text) { void help_section(const char *title) { assert(title); - printf("\n%s%s%s\n", + printf("\n%s%s:%s\n", ansi_underline(), title, ansi_normal()); diff --git a/src/storage/storage-block.c b/src/storage/storage-block.c index e5454a29c28a0..b33bf9ce40bd3 100644 --- a/src/storage/storage-block.c +++ b/src/storage/storage-block.c @@ -393,7 +393,7 @@ static int help(void) { if (r < 0) return r; - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) diff --git a/src/storage/storage-fs.c b/src/storage/storage-fs.c index c01e91a4cefe6..167b10dd83542 100644 --- a/src/storage/storage-fs.c +++ b/src/storage/storage-fs.c @@ -753,7 +753,7 @@ static int help(void) { if (r < 0) return r; - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index 2bc7b7c2a3e40..f88dff29bc861 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -65,13 +65,13 @@ static int help(void) { (void) table_sync_column_widths(0, verbs, options); - help_section("Commands:"); + help_section("Commands"); r = table_print_or_warn(verbs); if (r < 0) return r; - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 4f76c5150021f..775188b5191cb 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -113,7 +113,7 @@ static int systemctl_help(void) { help_cmdline("[OPTIONS...] COMMAND ..."); help_abstract("Query or send control commands to the system manager."); - help_section("Unit Commands:"); + 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" @@ -162,7 +162,7 @@ static int systemctl_help(void) { " whoami [PID...] Return unit caller or specified PIDs are\n" " part of\n"); - help_section("Unit File Commands:"); + 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" @@ -186,27 +186,27 @@ static int systemctl_help(void) { " get-default Get the name of the default target\n" " set-default TARGET Set the default target\n"); - help_section("Machine Commands:"); + help_section("Machine Commands"); printf(" list-machines [PATTERN...] List local containers and host\n"); - help_section("Job Commands:"); + help_section("Job Commands"); printf(" list-jobs [PATTERN...] List jobs\n" " cancel [JOB...] Cancel all, one, or more jobs\n"); - help_section("Environment Commands:"); + 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"); - help_section("Manager State Commands:"); + 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"); - help_section("System Commands:"); + 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" @@ -226,7 +226,7 @@ static int systemctl_help(void) { " suspend-then-hibernate Suspend the system, wake after a period of\n" " time, and hibernate\n"); - help_section("Options:"); + help_section("Options"); printf(" -h --help Show this help\n" " --version Show package version\n" " --system Connect to system manager\n" diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index c2fabdcdb844b..1cde89ad6f602 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -368,7 +368,7 @@ static int help(void) { return r; help_cmdline("[OPTIONS...] DEVICE"); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index 27423e985155e..40fd7a1e77d8c 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -909,7 +909,7 @@ static int help(void) { return r; help_cmdline("[OPTIONS...] DEVICE"); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index a1708c128c928..64af7b8028770 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -653,7 +653,7 @@ static int help(void) { return r; help_cmdline("[OPTIONS...]"); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index a19c7eebec6e7..30bc96c526bdf 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -38,7 +38,7 @@ static int help(void) { help_cmdline("[OPTIONS...] SYSFS_PATH"); help_abstract("Identify FIDO security tokens."); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index eadab1cb8a091..1efd4a5365e1b 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -68,12 +68,12 @@ static int help(void) { help_cmdline("[OPTIONS...] COMMAND"); help_abstract("Set up iocost model and qos solutions for block devices."); - help_section("Commands:"); + help_section("Commands"); r = table_print_or_warn(verbs); if (r < 0) return r; - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index fe9924f1b6e28..7280573646d66 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -45,7 +45,7 @@ static int help(void) { help_cmdline("[OPTIONS...] /dev/mtd[n]"); help_abstract("Probe MTD devices."); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index d7970722848c8..295819351d19b 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -216,7 +216,7 @@ static int help(void) { help_cmdline("[OPTION...] DEVICE"); help_abstract("SCSI device identification."); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index 541ba16dd906b..27a72f2ac8dcd 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -160,7 +160,7 @@ static int help(void) { help_cmdline("[OPTIONS...]"); help_abstract("Rule-based manager for device events and files."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index 62d30d0234d24..fcce76663e1a5 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -28,7 +28,7 @@ static int help(void) { help_cmdline("cat [OPTIONS...] [FILE...]"); help_abstract("Show udev rules files."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index a6ffe83cecaf6..0a0bb35fb5dd5 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -57,7 +57,7 @@ static int help(void) { help_cmdline("control OPTION"); help_abstract("Control the udev daemon."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index b029db2262a04..bb6f03d540890 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -25,7 +25,7 @@ static int help(void) { help_cmdline("hwdb [OPTIONS]"); help_abstract("Update or query the hardware database."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index a5cbedc8deeda..73ed70f4bffb5 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -811,7 +811,7 @@ static int help(void) { help_cmdline("info [OPTIONS] [DEVPATH|FILE]"); help_abstract("Query sysfs or the udev database."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index c1c3211d34992..fd4b6a9059a4b 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -45,7 +45,7 @@ static int help(void) { help_cmdline("lock [OPTIONS...] COMMAND"); help_cmdline("lock [OPTIONS...] --print"); help_abstract("Lock a block device and run a command."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index c7d1f40fc49b6..76c9d16d1f963 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -109,7 +109,7 @@ static int help(void) { help_cmdline("monitor [OPTIONS]"); help_abstract("Listen to kernel and udev events."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 211a8ff1fbf8c..77882cc074a6c 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -39,7 +39,7 @@ static int help(void) { help_cmdline("settle [OPTIONS]"); help_abstract("Wait for pending udev events."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index 9c0082800f37a..3de1366b59f20 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -24,12 +24,12 @@ static int help(void) { help_cmdline("test-builtin [OPTIONS] COMMAND DEVPATH"); help_abstract("Test a built-in command."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; - help_section("Commands:"); + help_section("Commands"); udev_builtin_list(); return 0; } diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index ac368e0f00eec..ba8217c8d36b3 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -44,7 +44,7 @@ static int help(void) { help_cmdline("test [OPTIONS] DEVPATH"); help_abstract("Test an event run."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index 583d85be0b8d8..e1fdf323cbc20 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -331,7 +331,7 @@ static int help(void) { help_cmdline("trigger [OPTIONS] DEVPATH"); help_abstract("Request events from the kernel."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index f4388f843adc6..e7f803dfb1eac 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -33,7 +33,7 @@ static int help(void) { help_cmdline("verify [OPTIONS] [FILE...]"); help_abstract("Verify udev rules files."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 6017401440689..fa12e6c98c17c 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -307,7 +307,7 @@ static int help(void) { help_cmdline("wait [OPTIONS] DEVICE [DEVICE…]"); help_abstract("Wait for devices or device symlinks being created."); - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 47d4335baec7f..cdc10802749ea 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -30,12 +30,12 @@ static int help(void) { help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); help_abstract("Send control commands or test the device manager."); - help_section("Commands:"); + help_section("Commands"); r = table_print_or_warn(verbs); if (r < 0) return r; - help_section("Options:"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index 1a53e1092fb7a..f1f40324de7f7 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -32,7 +32,7 @@ static int help(void) { help_cmdline("[OPTIONS...] DEVICE"); help_abstract("Video4Linux device identification."); - help_section("Options:"); + help_section("Options"); return table_print_or_warn(options); } From f94da4b4c564f8cff4b5b739456c985e036a4201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 10:59:46 +0200 Subject: [PATCH 1436/2155] shared/verbs: display default verb as "[verb]" --- src/shared/verbs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index dfecf048612b7..276c6fd5be916 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -192,11 +192,15 @@ int _verbs_get_help_table( /* No help string — we do not show the verb */ continue; + bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT); + /* 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", + r = table_add_cell_stringf(table, NULL, " %s%s%s%s%s", + is_default ? "[" : "", verb->verb, + is_default ? "]" : "", verb->argspec ? " " : "", strempty(verb->argspec)); if (r < 0) From f8c0aaccef5388c6454f0a0b0a34a826bc882c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 09:59:16 +0200 Subject: [PATCH 1437/2155] test-options: add a check for custom logic in systemd-analyze --- src/test/test-options.c | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/test-options.c b/src/test/test-options.c index d00262fa34bb2..21fb7a2d028c1 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -818,6 +818,77 @@ TEST(option_optional_arg) { NULL); } +/* Check that we correctly implement the behaviour of + * systemd-analyze -a -b unit-shell -c -d name -e -f + * systemd-analyze -a -b other-verb -c -d name -e -f + * systemd-analyze -a -b -c -d -e -f + * where '-a', '-b', '-c', '-d' are "our" options, but '-e -f' is part of the commandline + * for unit-shell, but not in the other cases. */ +static void test_option_parsing_stops_at_second_nonoption_one( + char **cmdline, + unsigned options_to_see, + unsigned verbs_to_see, + char **args_to_see) { + + static const Option options[] = { + { 1, .short_code = 'a' }, + { 2, .short_code = 'b' }, + { 3, .short_code = 'c' }, + { 4, .short_code = 'd' }, + { 5, .short_code = 'e' }, + { 6, .short_code = 'f' }, + { 7, .long_code = "(positional)", .flags = OPTION_POSITIONAL_ENTRY }, + {}, + }; + + OptionParser opts = { strv_length(cmdline), cmdline, + .mode = OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + unsigned options_seen = 0; + unsigned verbs_seen = 0; + for (int c; (c = option_parse(options, options + ELEMENTSOF(options) - 1, &opts)) != 0; ) { + ASSERT_OK(c); + ASSERT_NOT_NULL(opts.opt); + + switch (opts.opt->id) { + case 1 ... 6: + options_seen++; + break; + case 7: + verbs_seen++; + ASSERT_EQ(opts.mode, (OptionParserMode) OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + if (streq(opts.arg, "unit-shell")) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; + else if (streq(opts.arg, "other-verb")) + opts.mode = OPTION_PARSER_NORMAL; + else + assert_not_reached(); + break; + default: + assert_not_reached(); + } + } + + ASSERT_EQ(options_seen, options_to_see); + ASSERT_EQ(verbs_seen, verbs_to_see); + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), args_to_see)); +} + +TEST(option_parsing_stops_at_second_nonoption) { + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "unit-shell", "-c", "-d", "name", "-e", "-f"), + 4, 1, + STRV_MAKE("name", "-e", "-f")); + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "other-verb", "-c", "-d", "name", "-e", "-f"), + 6, 1, + STRV_MAKE("name")); + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "-c", "-d", "-e"), + 5, 0, + STRV_EMPTY); +} + /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros * by using them in a FOREACH_OPTION switch, as they would be used in real code. */ From 10b97bbb98b7e3eb472d683d10f84b468bf6c624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 4 May 2026 08:00:32 +0200 Subject: [PATCH 1438/2155] analyze: convert to OPTION and VERB macros The logic that was tested in the previous commit is used to implement the behaviour for unit-shell and other verbs without changes. The compare-versions synopsis is shortened to "V1 [OP] V2" to make the verb synopsis fit. Unusual capitalizaition of "Command" is changed to "COMMAND" (it's a replace arg, not a fixed string), and some help strings are adjusted. The order of options in --help is based on the existing order in parse_argv(). The old order in --help was mostly random. I think it might be good to figure out something more rational here, but I'm leaving that as a separate step. The urlification of dot(1) in the --help string is lost. It's hard to do this with the help string being stored in a read-only section. I think this is not worth the trouble to reimplement in the current scheme. --- man/systemd-analyze.xml | 2 +- src/analyze/analyze.c | 695 ++++++++++++++++------------------------ 2 files changed, 276 insertions(+), 421 deletions(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index f3dfd7479f87f..4f3057f725d24 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -81,7 +81,7 @@ OPTIONS unit-shell SERVICE - Command + COMMAND systemd-analyze diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index e23b0038a9944..0f848264c73f0 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -3,7 +3,6 @@ Copyright © 2013 Simon Peeters ***/ -#include #include #include #include @@ -57,11 +56,14 @@ #include "calendarspec.h" #include "dissect-image.h" #include "extract-word.h" +#include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "log.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -196,553 +198,455 @@ static int verb_transient_settings(int argc, char *argv[], uintptr_t _data, void } static int help(void) { - _cleanup_free_ char *link = NULL, *dot_link = NULL; + static const char *const vgroups[] = { + "Boot Analysis", + "Dependency Analysis", + "Configuration Files and Search Paths", + "Enumerate OS Concepts", + "Expression Evaluation", + "Clock & Time", + "Unit & Service Analysis", + "Executable Analysis", + "TPM Operations", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-analyze", "1", &link); - if (r < 0) - return log_oom(); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } - /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */ - r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sProfile systemd, show unit dependencies, check unit files.%6$s\n" - "\n%3$sBoot Analysis:%4$s\n" - " [time] Print time required to boot the machine\n" - " blame Print list of running units ordered by\n" - " time to init\n" - " critical-chain [UNIT...] Print a tree of the time critical chain\n" - " of units\n" - "\n%3$sDependency Analysis:%4$s\n" - " plot Output SVG graphic showing service\n" - " initialization\n" - " dot [UNIT...] Output dependency graph in %7$s format\n" - " dump [PATTERN...] Output state serialization of service\n" - " manager\n" - "\n%3$sConfiguration Files and Search Paths:%4$s\n" - " cat-config NAME|PATH... Show configuration file and drop-ins\n" - " unit-files List files and symlinks for units\n" - " unit-paths List load directories for units\n" - "\n%3$sEnumerate OS Concepts:%4$s\n" - " exit-status [STATUS...] List exit status definitions\n" - " capability [CAP...] List capability definitions\n" - " syscall-filter [NAME...] List syscalls in seccomp filters\n" - " filesystems [NAME...] List known filesystems\n" - " architectures [NAME...] List known architectures\n" - " smbios11 List strings passed via SMBIOS Type #11\n" - " chid List local CHIDs\n" - " transient-settings TYPE... List transient settings for unit TYPE\n" - "\n%3$sExpression Evaluation:%4$s\n" - " condition CONDITION... Evaluate conditions and asserts\n" - " compare-versions VERSION1 [OP] VERSION2\n" - " Compare two version strings\n" - " image-policy POLICY... Analyze image policy string\n" - "\n%3$sClock & Time:%4$s\n" - " calendar SPEC... Validate repetitive calendar time\n" - " events\n" - " timestamp TIMESTAMP... Validate a timestamp\n" - " timespan SPAN... Validate a time span\n" - "\n%3$sUnit & Service Analysis:%4$s\n" - " verify FILE... Check unit files for correctness\n" - " security [UNIT...] Analyze security of unit\n" - " fdstore SERVICE... Show file descriptor store contents of service\n" - " malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n" - " unit-gdb SERVICE Attach a debugger to the given running service\n" - " unit-shell SERVICE [Command]\n" - " Run command on the namespace of the service\n" - "\n%3$sExecutable Analysis:%4$s\n" - " inspect-elf FILE... Parse and print ELF package metadata\n" - " 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" - "\n%3$sOptions:%4$s\n" - " --recursive-errors=MODE Control which units are verified\n" - " --offline=BOOL Perform a security review on unit file(s)\n" - " --threshold=N Exit with a non-zero status when overall\n" - " exposure level is over threshold value\n" - " --security-policy=PATH Use custom JSON security policy instead\n" - " of built-in one\n" - " --json=pretty|short|off Generate JSON output of the security\n" - " analysis table, or plot's raw time data\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Disable column headers and hints in plot\n" - " with either --table or --json=\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " --global Operate on global user configuration\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print services which finished SECONDS\n" - " earlier than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n" - " --generators[=BOOL] Do [not] run unit generators\n" - " (requires privileges)\n" - " --instance=NAME Specify fallback instance name for template units\n" - " --iterations=N Show the specified number of iterations\n" - " --base-time=TIMESTAMP Calculate calendar times relative to\n" - " specified time\n" - " --profile=name|PATH Include the specified profile in the\n" - " security review of the unit(s)\n" - " --unit=UNIT Evaluate conditions and asserts of unit\n" - " --table Output plot's raw time data as a table\n" - " --scale-svg=FACTOR Stretch x-axis of plot by FACTOR (default: 1.0)\n" - " --detailed Add more details to SVG plot,\n" - " e.g. show activation timestamps\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -q --quiet Do not emit hints\n" - " --tldr Skip comments and empty lines\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" - " -m --mask Parse parameter as numeric capability mask\n" - " --drm-device=PATH Use this DRM device sysfs path to get EDID\n" - " --debugger=DEBUGGER Use the given debugger\n" - " -A --debugger-arguments=ARGS\n" - " Pass the given arguments to the debugger\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - dot_link); - - /* When updating this list, including descriptions, apply changes to - * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */ + assert_se(ELEMENTSOF(vtables) == 9); + (void) table_sync_column_widths(0, options, vtables[0], vtables[1], vtables[2], + vtables[3], vtables[4], vtables[5], vtables[6], + vtables[7], vtables[8]); - return 0; -} + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Profile systemd, show unit dependencies, check unit files."); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ORDER, - ARG_REQUIRE, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_SYSTEM, - ARG_USER, - ARG_GLOBAL, - ARG_DOT_FROM_PATTERN, - ARG_DOT_TO_PATTERN, - ARG_FUZZ, - ARG_NO_PAGER, - ARG_MAN, - ARG_GENERATORS, - ARG_INSTANCE, - ARG_ITERATIONS, - ARG_BASE_TIME, - ARG_RECURSIVE_ERRORS, - ARG_OFFLINE, - ARG_THRESHOLD, - ARG_SECURITY_POLICY, - ARG_JSON, - ARG_PROFILE, - ARG_TABLE, - ARG_NO_LEGEND, - ARG_TLDR, - ARG_SCALE_FACTOR_SVG, - ARG_DETAILED_SVG, - ARG_DRM_DEVICE_PATH, - ARG_DEBUGGER, - }; + help_section("Options"); + 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 }, - { "quiet", no_argument, NULL, 'q' }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "recursive-errors" , required_argument, NULL, ARG_RECURSIVE_ERRORS }, - { "offline", required_argument, NULL, ARG_OFFLINE }, - { "threshold", required_argument, NULL, ARG_THRESHOLD }, - { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "generators", optional_argument, NULL, ARG_GENERATORS }, - { "instance", required_argument, NULL, ARG_INSTANCE }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "base-time", required_argument, NULL, ARG_BASE_TIME }, - { "unit", required_argument, NULL, 'U' }, - { "json", required_argument, NULL, ARG_JSON }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "table", optional_argument, NULL, ARG_TABLE }, - { "no-legend", optional_argument, NULL, ARG_NO_LEGEND }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "mask", no_argument, NULL, 'm' }, - { "scale-svg", required_argument, NULL, ARG_SCALE_FACTOR_SVG }, - { "detailed", no_argument, NULL, ARG_DETAILED_SVG }, - { "drm-device", required_argument, NULL, ARG_DRM_DEVICE_PATH }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - {} - }; + help_man_page_reference("systemd-analyze", "1"); - bool reorder = false; - int r, c, unit_shell = -1; + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +/* When updating this list, including descriptions, apply changes to + * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */ + +VERB_GROUP("Boot Analysis"); +VERB_SCOPE(, verb_time, "time", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Print time required to boot the machine"); +VERB_SCOPE(, verb_blame, "blame", NULL, VERB_ANY, 1, 0, + "Print list of running units ordered by time to init"); +VERB_SCOPE(, verb_critical_chain, "critical-chain", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Print a tree of the time critical chain of units"); + +VERB_GROUP("Dependency Analysis"); +VERB_SCOPE(, verb_plot, "plot", NULL, VERB_ANY, 1, 0, + "Output SVG graphic showing service initialization"); +VERB_SCOPE(, verb_dot, "dot", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Output dependency graph in dot(1) format"); +VERB_SCOPE(, verb_dump, "dump", "[PATTERN...]", VERB_ANY, VERB_ANY, 0, + "Output state serialization of service manager"); + +VERB_GROUP("Configuration Files and Search Paths"); +VERB_SCOPE(, verb_cat_config, "cat-config", "NAME|PATH...", 2, VERB_ANY, 0, + "Show configuration file and drop-ins"); +VERB_SCOPE(, verb_unit_files, "unit-files", NULL, VERB_ANY, VERB_ANY, 0, + "List files and symlinks for units"); +VERB_SCOPE(, verb_unit_paths, "unit-paths", NULL, 1, 1, 0, + "List load directories for units"); + +VERB_GROUP("Enumerate OS Concepts"); +VERB_SCOPE(, verb_exit_status, "exit-status", "[STATUS...]", VERB_ANY, VERB_ANY, 0, + "List exit status definitions"); +VERB_SCOPE(, verb_capabilities, "capability", "[CAP...]", VERB_ANY, VERB_ANY, 0, + "List capability definitions"); +VERB_SCOPE(, verb_syscall_filters, "syscall-filter", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List syscalls in seccomp filters"); +VERB_SCOPE(, verb_filesystems, "filesystems", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List known filesystems"); +VERB_SCOPE(, verb_architectures, "architectures", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List known architectures"); +VERB_SCOPE(, verb_smbios11, "smbios11", NULL, VERB_ANY, 1, 0, + "List strings passed via SMBIOS Type #11"); +VERB_SCOPE(, verb_chid, "chid", NULL, VERB_ANY, VERB_ANY, 0, + "List local CHIDs"); +VERB(verb_transient_settings, "transient-settings", "TYPE...", 2, VERB_ANY, 0, + "List transient settings for unit TYPE"); + +VERB_GROUP("Expression Evaluation"); +VERB_SCOPE(, verb_condition, "condition", "CONDITION...", VERB_ANY, VERB_ANY, 0, + "Evaluate conditions and asserts"); +VERB_SCOPE(, verb_compare_versions, "compare-versions", "V1 [OP] V2", 3, 4, 0, + "Compare two version strings"); +VERB_SCOPE(, verb_image_policy, "image-policy", "POLICY...", 2, 2, 0, + "Analyze image policy string"); + +VERB_GROUP("Clock & Time"); +VERB_SCOPE(, verb_calendar, "calendar", "SPEC...", 2, VERB_ANY, 0, + "Validate repetitive calendar time events"); +VERB_SCOPE(, verb_timestamp, "timestamp", "TIMESTAMP...", 2, VERB_ANY, 0, + "Validate a timestamp"); +VERB_SCOPE(, verb_timespan, "timespan", "SPAN...", 2, VERB_ANY, 0, + "Validate a time span"); + +VERB_GROUP("Unit & Service Analysis"); +VERB_SCOPE(, verb_verify, "verify", "FILE...", 2, VERB_ANY, 0, + "Check unit files for correctness"); +VERB_SCOPE(, verb_security, "security", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Analyze security of unit"); +VERB_SCOPE(, verb_fdstore, "fdstore", "SERVICE...", 2, VERB_ANY, 0, + "Show file descriptor store contents of service"); +VERB_SCOPE(, verb_malloc, "malloc", "[D-BUS SERVICE...]", VERB_ANY, VERB_ANY, 0, + "Dump malloc stats of a D-Bus service"); +VERB_SCOPE(, verb_unit_gdb, "unit-gdb", "SERVICE", 2, VERB_ANY, 0, + "Attach a debugger to the given running service"); +VERB_SCOPE(, verb_unit_shell, "unit-shell", "SERVICE [COMMAND ...]", 2, VERB_ANY, 0, + "Run command on the namespace of the service"); + +VERB_GROUP("Executable Analysis"); +VERB_SCOPE(, verb_elf_inspection, "inspect-elf", "FILE...", 2, VERB_ANY, 0, + "Parse and print ELF package metadata"); +VERB_SCOPE(, verb_dlopen_metadata, "dlopen-metadata", "FILE", 2, 2, 0, + "Parse and print ELF dlopen metadata"); + +VERB_GROUP("TPM Operations"); +VERB_SCOPE(, verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, + "Report whether TPM2 support is available"); +VERB_SCOPE(, verb_identify_tpm2, "identify-tpm2", NULL, VERB_ANY, 1, 0, + "Show TPM2 vendor information"); +VERB_SCOPE(, verb_pcrs, "pcrs", "[PCR...]", VERB_ANY, VERB_ANY, 0, + "Show TPM2 PCRs and their names"); +VERB_SCOPE(, verb_nvpcrs, "nvpcrs", "[NVPCR...]", VERB_ANY, VERB_ANY, 0, + "Show additional TPM2 PCRs stored in NV indexes"); +VERB_SCOPE(, verb_srk, "srk", "[>FILE]", VERB_ANY, 1, 0, + "Write TPM2 SRK (to FILE)"); + +/* The following are deprecated and not shown in --help. */ +VERB_SCOPE(, verb_log_control, "log-level", NULL, VERB_ANY, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "log-target", NULL, VERB_ANY, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "set-log-level", NULL, 2, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "get-log-level", NULL, VERB_ANY, 1, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "set-log-target", NULL, 2, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "get-log-target", NULL, VERB_ANY, 1, 0, /* help= */ NULL); +VERB_SCOPE(, verb_service_watchdogs, "service-watchdogs", NULL, VERB_ANY, 2, 0, /* help= */ NULL); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); + assert(ret_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; - - for (;;) { - static const char option_string[] = "-hqH:M:U:mA:"; - - c = getopt_long(argc, argv, option_string + reorder, options, NULL); - if (c < 0) - break; + /* For "unit-shell" the switches specified after the service name are part of the commandline + * to execute and are not processed by us. For other verbs, we consume all options as usual. + * To make this work, start with mode==OPTION_PARSER_RETURN_POSITIONAL_ARGS and switch to + * either OPTION_PARSER_STOP_AT_FIRST_NONOPTION or OPTION_PARSER_NORMAL after we've seen + * the verb. */ + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *verb = NULL; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a - * non-option argument was discovered. */ - - assert(!reorder); - - /* We generally are fine with the fact that getopt_long() reorders the command line, and looks - * for switches after the main verb. However, for "unit-shell" we really don't want that, since we - * want that switches specified after the service name are passed to the program to execute, - * and not processed by us. To make this possible, we'll first invoke getopt_long() with - * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first - * non-option parameter. If it's the verb "unit-shell" we remember its position and continue - * processing options. In this case, as soon as we hit the next non-option argument we found - * the service name, and stop further processing. If the first non-option argument is any other - * verb than "unit-shell" we switch to normal reordering mode and continue processing arguments - * normally. */ - - if (unit_shell >= 0) { - optind--; /* don't process this argument, go one step back */ - goto done; - } - if (streq(optarg, "unit-shell")) - /* Remember the position of the "unit_shell" verb, and continue processing normally. */ - unit_shell = optind - 1; - else { - int saved_optind; - - /* Ok, this is some other verb. In this case, turn on reordering again, and continue - * processing normally. */ - reorder = true; - - /* We changed the option string. getopt_long() only looks at it again if we invoke it - * at least once with a reset option index. Hence, let's reset the option index here, - * then invoke getopt_long() again (ignoring what it has to say, after all we most - * likely already processed it), and the bump the option index so that we read the - * intended argument again. */ - saved_optind = optind; - optind = 0; - (void) getopt_long(argc, argv, option_string + reorder, options, NULL); - optind = saved_optind - 1; /* go one step back, process this argument again */ - } + OPTION_POSITIONAL: + verb = opts.arg; + assert(opts.mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS); + if (streq(verb, "unit-shell")) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; + else + opts.mode = OPTION_PARSER_NORMAL; break; - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Do not emit hints"): arg_quiet = true; break; - case ARG_RECURSIVE_ERRORS: - if (streq(optarg, "help")) + OPTION_LONG("recursive-errors", "MODE", "Control which units are verified"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX); - r = recursive_errors_from_string(optarg); + r = recursive_errors_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg); + return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", opts.arg); arg_recursive_errors = r; 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(opts.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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate on system systemd instance"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate on user systemd instance"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_GLOBAL: + OPTION_LONG("global", NULL, "Operate on global user configuration"): arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; - case ARG_ORDER: + OPTION_LONG("order", NULL, "Show only order in the graph"): arg_dot = DEP_ORDER; break; - case ARG_REQUIRE: + OPTION_LONG("require", NULL, "Show only requirement in the graph"): arg_dot = DEP_REQUIRE; break; - case ARG_DOT_FROM_PATTERN: - if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + OPTION_LONG("from-pattern", "GLOB", "Show only origins in the graph"): + if (strv_extend(&arg_dot_from_patterns, opts.arg) < 0) return log_oom(); - break; - case ARG_DOT_TO_PATTERN: - if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + OPTION_LONG("to-pattern", "GLOB", "Show only destinations in the graph"): + if (strv_extend(&arg_dot_to_patterns, opts.arg) < 0) return log_oom(); - break; - case ARG_FUZZ: - r = parse_sec(optarg, &arg_fuzz); + OPTION_LONG("fuzz", "SECONDS", + "Also print services which finished SECONDS earlier than the latest in the branch"): + r = parse_sec(opts.arg, &arg_fuzz); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_MAN: - r = parse_boolean_argument("--man", optarg, &arg_man); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "man", "BOOL", "Whether to check for existence of man pages"): + r = parse_boolean_argument("--man", opts.arg, &arg_man); if (r < 0) return r; break; - case ARG_GENERATORS: - r = parse_boolean_argument("--generators", optarg, &arg_generators); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "generators", "BOOL", + "Whether to run unit generators (which requires privileges)"): + r = parse_boolean_argument("--generators", opts.arg, &arg_generators); if (r < 0) return r; break; - case ARG_INSTANCE: - arg_instance = optarg; + OPTION_LONG("instance", "NAME", "Specify fallback instance name for template units"): + arg_instance = opts.arg; break; - case ARG_OFFLINE: - r = parse_boolean_argument("--offline", optarg, &arg_offline); + OPTION_LONG("offline", "BOOL", "Perform a security review on unit files"): + r = parse_boolean_argument("--offline", opts.arg, &arg_offline); if (r < 0) return r; break; - case ARG_THRESHOLD: - r = safe_atou(optarg, &arg_threshold); + OPTION_LONG("threshold", "N", + "Exit with a non-zero status when overall exposure level is over threshold value"): + r = safe_atou(opts.arg, &arg_threshold); if (r < 0 || arg_threshold > 100) - return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg); - + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), + "Failed to parse threshold: %s", opts.arg); break; - case ARG_SECURITY_POLICY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy); + OPTION_LONG("security-policy", "PATH", + "Use custom JSON security policy instead of built-in one"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_security_policy); if (r < 0) return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_ITERATIONS: - r = safe_atou(optarg, &arg_iterations); + OPTION_LONG("iterations", "N", "Show the specified number of iterations"): + r = safe_atou(opts.arg, &arg_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse iterations: %s", optarg); + return log_error_errno(r, "Failed to parse iterations: %s", opts.arg); break; - case ARG_BASE_TIME: - r = parse_timestamp(optarg, &arg_base_time); + OPTION_LONG("base-time", "TIMESTAMP", + "Calculate calendar times relative to specified time"): + r = parse_timestamp(opts.arg, &arg_base_time); if (r < 0) - return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --base-time= parameter: %s", opts.arg); break; - case ARG_PROFILE: - if (isempty(optarg)) + OPTION_LONG("profile", "name|PATH", + "Include the specified profile in the security review of the units"): + if (isempty(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty"); - if (is_path(optarg)) { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile); + if (is_path(opts.arg)) { + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_profile); if (r < 0) return r; if (!endswith(arg_profile, ".conf")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Profile file name must end with .conf: %s", arg_profile); } else { - r = free_and_strdup(&arg_profile, optarg); + r = free_and_strdup(&arg_profile, opts.arg); if (r < 0) return log_oom(); } - break; - case 'U': { + OPTION('U', "unit", "UNIT", "Evaluate conditions and asserts of unit"): { _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle(optarg, UNIT_NAME_MANGLE_WARN, &mangled); + r = unit_name_mangle(opts.arg, UNIT_NAME_MANGLE_WARN, &mangled); if (r < 0) - return log_error_errno(r, "Failed to mangle unit name %s: %m", optarg); + return log_error_errno(r, "Failed to mangle unit name %s: %m", opts.arg); free_and_replace(arg_unit, mangled); break; } - case ARG_TABLE: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "table", NULL, + "Output plot's raw time data as a table"): arg_table = true; break; - case ARG_NO_LEGEND: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "no-legend", NULL, + "Disable column headers and hints in plot with either --table or --json="): arg_legend = false; break; - case ARG_TLDR: + OPTION_LONG("tldr", NULL, "Skip comments and empty lines"): arg_cat_flags = CAT_TLDR; break; - case 'm': + OPTION('m', "mask", NULL, "Parse parameter as numeric capability mask"): arg_capability = CAPABILITY_MASK; break; - case ARG_SCALE_FACTOR_SVG: - arg_svg_timescale = strtod(optarg, NULL); + OPTION_LONG("scale-svg", "FACTOR", "Stretch x-axis of plot by FACTOR (default: 1.0)"): + arg_svg_timescale = strtod(opts.arg, NULL); break; - case ARG_DETAILED_SVG: + OPTION_LONG("detailed", NULL, + "Add more details to SVG plot, e.g. show activation timestamps"): arg_detailed_svg = true; break; - case ARG_DRM_DEVICE_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_drm_device_path); + OPTION_LONG("drm-device", "PATH", "Use this DRM device sysfs path to get EDID"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_drm_device_path); if (r < 0) return r; break; - case ARG_DEBUGGER: - r = free_and_strdup_warn(&arg_debugger, optarg); + OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"): + r = free_and_strdup_warn(&arg_debugger, opts.arg); if (r < 0) return r; break; - case 'A': { + OPTION('A', "debugger-arguments", "ARGS", "Pass the given arguments to the debugger"): { _cleanup_strv_free_ char **l = NULL; - r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE); + r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE); if (r < 0) - return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg); + return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg); strv_free_and_replace(arg_debugger_args, l); break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } - -done: - if (unit_shell >= 0) { - char *t; - - /* We found the "unit-shell" verb while processing the argument list. Since we turned off reordering of the - * argument list initially let's readjust it now, and move the "unit-shell" verb to the back. */ - - optind -= 1; /* place the option index where the "unit-shell" verb will be placed */ - t = argv[unit_shell]; - for (int i = unit_shell; i < optind; i++) - argv[i] = argv[i+1]; - argv[optind] = t; - } + _cleanup_strv_free_ char **args = strv_copy(option_parser_get_args(&opts)); /* args is [arg1, arg2, …] */ + if (!args || strv_prepend(&args, verb) < 0) /* args is now [arg0, arg1, arg2, …] */ + return log_oom(); - if (arg_offline && !streq_ptr(argv[optind], "security")) + if (arg_offline && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= is only supported for security right now."); - if (arg_offline && optind >= argc - 1) + if (arg_offline && strv_length(args) < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= requires one or more units to perform a security review."); - if (arg_json_format_flags != SD_JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status")) + if (arg_json_format_flags != SD_JSON_FORMAT_OFF && + !STRPTR_IN_SET(verb, "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --json= is only supported for security, inspect-elf, dlopen-metadata, plot, fdstore, pcrs, nvpcrs, architectures, capability, exit-status right now."); - if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) + if (arg_threshold != 100 && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --threshold= is only supported for security right now."); - if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(argv[optind], "unit-paths")) + if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(verb, "unit-paths")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --global only makes sense with verb unit-paths."); - if (streq_ptr(argv[optind], "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER) + if (streq_ptr(verb, "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --user is not supported for cat-config right now."); - if (arg_security_policy && !streq_ptr(argv[optind], "security")) + if (arg_security_policy && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --security-policy= is only supported for security."); - if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition", "inspect-elf", "unit-gdb")) && - (!(streq_ptr(argv[optind], "security") && arg_offline))) + if ((arg_root || arg_image) && + !STRPTR_IN_SET(verb, "cat-config", "verify", "condition", "inspect-elf", "unit-gdb") && + (!(streq_ptr(verb, "security") && arg_offline))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --root= and --image= are only supported for cat-config, verify, condition, unit-gdb, and security when used with --offline= right now."); @@ -750,84 +654,35 @@ static int parse_argv(int argc, char *argv[]) { 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."); - if (arg_unit && !streq_ptr(argv[optind], "condition")) + if (arg_unit && !streq_ptr(verb, "condition")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --unit= is only supported for condition"); - if (streq_ptr(argv[optind], "condition") && !arg_unit && optind >= argc - 1) + if (streq_ptr(verb, "condition") && !arg_unit && strv_length(args) < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments for condition"); - if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1) + if (streq_ptr(verb, "condition") && arg_unit && strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); - if (arg_table && !streq_ptr(argv[optind], "plot")) + if (arg_table && !streq_ptr(verb, "plot")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now."); if (arg_table && sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive."); - if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(argv[optind], "capability")) + if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(verb, "capability")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --mask is only supported for capability."); - if (arg_drm_device_path && !streq_ptr(argv[optind], "chid")) + if (arg_drm_device_path && !streq_ptr(verb, "chid")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --drm-device is only supported for chid right now."); + *ret_args = TAKE_PTR(args); return 1; /* work to do */ } static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; - - static const Verb verbs[] = { - { "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 }, - { "plot", VERB_ANY, 1, 0, verb_plot }, - { "dot", VERB_ANY, VERB_ANY, 0, verb_dot }, - /* ↓ The following seven verbs are deprecated, from here … ↓ */ - { "log-level", VERB_ANY, 2, 0, verb_log_control }, - { "log-target", VERB_ANY, 2, 0, verb_log_control }, - { "set-log-level", 2, 2, 0, verb_log_control }, - { "get-log-level", VERB_ANY, 1, 0, verb_log_control }, - { "set-log-target", 2, 2, 0, verb_log_control }, - { "get-log-target", VERB_ANY, 1, 0, verb_log_control }, - { "service-watchdogs", VERB_ANY, 2, 0, verb_service_watchdogs }, - /* ↑ … until here ↑ */ - { "dump", VERB_ANY, VERB_ANY, 0, verb_dump }, - { "cat-config", 2, VERB_ANY, 0, verb_cat_config }, - { "unit-files", VERB_ANY, VERB_ANY, 0, verb_unit_files }, - { "unit-gdb", 2, VERB_ANY, 0, verb_unit_gdb }, - { "unit-paths", 1, 1, 0, verb_unit_paths }, - { "unit-shell", 2, VERB_ANY, 0, verb_unit_shell }, - { "exit-status", VERB_ANY, VERB_ANY, 0, verb_exit_status }, - { "syscall-filter", VERB_ANY, VERB_ANY, 0, verb_syscall_filters }, - { "capability", VERB_ANY, VERB_ANY, 0, verb_capabilities }, - { "filesystems", VERB_ANY, VERB_ANY, 0, verb_filesystems }, - { "condition", VERB_ANY, VERB_ANY, 0, verb_condition }, - { "compare-versions", 3, 4, 0, verb_compare_versions }, - { "verify", 2, VERB_ANY, 0, verb_verify }, - { "calendar", 2, VERB_ANY, 0, verb_calendar }, - { "timestamp", 2, VERB_ANY, 0, verb_timestamp }, - { "timespan", 2, VERB_ANY, 0, verb_timespan }, - { "security", VERB_ANY, VERB_ANY, 0, verb_security }, - { "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection }, - { "dlopen-metadata", 2, 2, 0, verb_dlopen_metadata }, - { "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc }, - { "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 }, - { "architectures", VERB_ANY, VERB_ANY, 0, verb_architectures }, - { "smbios11", VERB_ANY, 1, 0, verb_smbios11 }, - { "chid", VERB_ANY, VERB_ANY, 0, verb_chid }, - { "transient-settings", 2, VERB_ANY, 0, verb_transient_settings }, - {} - }; - + _cleanup_strv_free_ char **args = NULL; int r; setlocale(LC_ALL, ""); @@ -835,7 +690,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -861,7 +716,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 71ebb276861e28b63c9a1b86bc8fee4ca7de5500 Mon Sep 17 00:00:00 2001 From: Simon Lucido Date: Mon, 4 May 2026 11:40:41 +0200 Subject: [PATCH 1439/2155] core/varlink-metrics: expose ReloadCount as a metric Add ReloadCount to the io.systemd.Metrics family table so it can be queried alongside other manager-level metrics via systemd-report. Also extend the existing integration test to cross-check the value returned by systemd-report against the D-Bus and Varlink transports on every assertion. Co-developed-by: Claude Opus 4.7 Signed-off-by: Simon Lucido --- src/core/varlink-metrics.c | 18 ++++++++++++++ test/units/TEST-07-PID1.reload-count.sh | 33 +++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 82bc3cf4cba15..f1ac0791bc914 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -189,6 +189,18 @@ static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) { return 0; } +static int reload_count_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(context); + + return metric_build_send_unsigned( + context, + /* object= */ NULL, + manager->reload_count, + /* fields= */ NULL); +} + static int units_by_type_total_build_json(MetricFamilyContext *context, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; @@ -364,6 +376,12 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_COUNTER, .generate = nrestarts_build_json, }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "ReloadCount", + .description = "Number of successful manager reloads since startup; resets across daemon-reexec", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = reload_count_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", diff --git a/test/units/TEST-07-PID1.reload-count.sh b/test/units/TEST-07-PID1.reload-count.sh index 7c31b65c5fc75..a41a4e467d233 100755 --- a/test/units/TEST-07-PID1.reload-count.sh +++ b/test/units/TEST-07-PID1.reload-count.sh @@ -5,7 +5,13 @@ set -o pipefail # Verify that the manager exposes a ReloadCount property that increments on # every daemon-reload, resets to zero across daemon-reexec (since the count -# is not serialized), and is reachable over both D-Bus and Varlink. +# is not serialized), and is reachable over D-Bus, Varlink Describe, and the +# io.systemd.Metrics interface (queried via systemd-report). + +# systemd-report silently returns empty if the metrics source is missing, +# which would falsely pass the cross-checks below. Assert the socket exists +# so any failure points at the real problem. +test -S /run/systemd/report/io.systemd.Manager read_count_dbus() { busctl -j get-property org.freedesktop.systemd1 \ @@ -19,10 +25,23 @@ read_count_varlink() { io.systemd.Manager.Describe '{}' | jq -r '.runtime.ReloadCount' } -# Sanity: both transports must agree. +read_count_report() { + local out + # Strip the RS separator that jq --seq re-emits on output. + out=$(/usr/lib/systemd/systemd-report metrics --json=short \ + io.systemd.Manager.ReloadCount \ + | jq --seq -r 'select(.name == "io.systemd.Manager.ReloadCount") | .value' \ + | tr -d '\036') + [[ -n "$out" ]] || { echo "ReloadCount metric missing from systemd-report output" >&2; return 1; } + echo "$out" +} + +# Sanity: all three transports must agree. dbus_count=$(read_count_dbus) varlink_count=$(read_count_varlink) +report_count=$(read_count_report) (( dbus_count == varlink_count )) +(( dbus_count == report_count )) # A single reload bumps the counter by one. before=$(read_count_dbus) @@ -34,18 +53,22 @@ systemctl daemon-reload systemctl daemon-reload (( $(read_count_dbus) == before + 3 )) -# And both transports still agree after the reload. +# And all three transports still agree after the reload. dbus_count=$(read_count_dbus) varlink_count=$(read_count_varlink) +report_count=$(read_count_report) (( dbus_count == varlink_count )) +(( dbus_count == report_count )) -# A daemon-reexec resets the counter back to zero on both transports, since -# reload_count lives only in memory and is not carried across the reexec. +# A daemon-reexec resets the counter back to zero on all three transports, +# since the counter lives only in memory and is not carried across the reexec. # `systemctl daemon-reexec` returns as soon as the old PID 1 closes its bus # connection, which is before the new PID 1 has rebound /run/systemd/private. # Use --watch-bind=yes to block on inotify until the new socket is live. systemctl daemon-reexec busctl --watch-bind=yes call org.freedesktop.systemd1 /org/freedesktop/systemd1 \ org.freedesktop.DBus.Peer Ping >/dev/null + (( $(read_count_dbus) == 0 )) (( $(read_count_varlink) == 0 )) +(( $(read_count_report) == 0 )) From 45725d6434d552330adfa84d7cf314e1716990d3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 09:52:29 +0100 Subject: [PATCH 1440/2155] test: skip TEST-70-TPM2.nvpcr check if pcrextend socket inactive systemd-dissect --mtree calls io.systemd.PCRExtend over Varlink to extend the verity NvPCR after activation, and the test then diffs the measure log to find the new entry. But systemd-pcrextend.socket has ConditionSecurity=measured-os, which fails when the firmware did not initialize PCRs, so the test fails. [ 10.056930] systemd[1]: systemd-pcrextend.socket - TPM PCR Measurements skipped, unmet condition check ConditionSecurity=measured-os Follow-up for 521a523ce0cdcf0d529bd566f3d64ae93f10419d --- test/units/TEST-70-TPM2.nvpcr.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh index 571b3eea770b3..05ae378d849e5 100755 --- a/test/units/TEST-70-TPM2.nvpcr.sh +++ b/test/units/TEST-70-TPM2.nvpcr.sh @@ -54,6 +54,18 @@ DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -hex -r | cut -d' ' DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1)" test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2" +systemd-analyze identify-tpm2 +udevadm test-builtin 'tpm2_id identify' /dev/tpmrm0 + +# systemd-dissect calls io.systemd.PCRExtend over Varlink to extend the verity NvPCR after activation, +# but systemd-pcrextend.socket has ConditionSecurity=measured-os which fails when the firmware did not +# initialize PCRs (e.g. when not booting via a signed UKI). Skip the rest in that case, otherwise the +# 'diff | grep' below would find no new measurement and fail. +if ! systemctl is-active --quiet systemd-pcrextend.socket; then + echo "systemd-pcrextend.socket not active, skipping verity NvPCR measurement check" + exit 0 +fi + mkdir -p /tmp/nvpcr/tree touch /tmp/nvpcr/tree/file @@ -103,6 +115,3 @@ 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 090f9b5a9587e98243cfe8b23df7694d277ff7c5 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Mon, 4 May 2026 09:31:59 +0000 Subject: [PATCH 1441/2155] systemd-dissect: do not fail dissection on LUKS v1 partitions partition_is_luks2_integrity() was returning -EINVAL when it encountered a non-LUKS2 header (e.g. LUKS v1), which caused the caller to abort the entire disk dissection. A LUKS v1 partition simply isn't LUKS2-with-integrity, so return 0 instead and let dissection continue normally. --- src/shared/dissect-image.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 662739ea0fe9c..e69c644f58eeb 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -430,11 +430,15 @@ static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t s if (sz != sizeof(header)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS header."); - if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition's magic is not LUKS."); + if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) { + log_debug("Partition does not have a LUKS magic header, assuming no integrity."); + return 0; + } - if (be16toh(header.version) != 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported LUKS header version: %" PRIu16 ".", be16toh(header.version)); + if (be16toh(header.version) != 2) { + log_debug("Partition is LUKS v%" PRIu16 ", not LUKS2, assuming no integrity.", be16toh(header.version)); + return 0; + } if (be64toh(header.hdr_len) > size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header length exceeds partition size."); From 1041160cabddd0e8deefee370b5f5c9d4040cd58 Mon Sep 17 00:00:00 2001 From: Max Chernoff Date: Fri, 1 May 2026 02:15:07 -0600 Subject: [PATCH 1442/2155] github: rename "systemd-import" to "importctl" The user-facing components are the "systemd-importd.service" unit and the "importctl" binary, so using these names makes more sense. There _is_ a "systemd-import" binary, but it's in "/usr/lib/systemd/", so this is a confusing name for a user-facing form. --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 ++- .github/ISSUE_TEMPLATE/feature_request.yml | 3 ++- .github/advanced-issue-labeler.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 313234f82a791..a0547b8b52ae1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -82,6 +82,7 @@ body: - 'homectl' - 'hostnamectl' - 'hardware database files' + - 'importctl' - 'journalctl' - 'kernel-install' - 'loginctl' @@ -112,7 +113,7 @@ body: - 'systemd-homed' - 'systemd-hostnamed' - 'systemd-hwdb' - - 'systemd-import' + - 'systemd-importd' - 'systemd-journal-gatewayd' - 'systemd-journal-remote' - 'systemd-journal-upload' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index f6f8ac0f75210..5e5996d7574ed 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -21,6 +21,7 @@ body: - 'homectl' - 'hostnamectl' - 'hardware database files' + - 'importctl' - 'journalctl' - 'kernel-install' - 'loginctl' @@ -51,7 +52,7 @@ body: - 'systemd-homed' - 'systemd-hostnamed' - 'systemd-hwdb' - - 'systemd-import' + - 'systemd-importd' - 'systemd-journal-gatewayd' - 'systemd-journal-remote' - 'systemd-journal-upload' diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml index 4e19392598cb5..b06cc20292f6f 100644 --- a/.github/advanced-issue-labeler.yml +++ b/.github/advanced-issue-labeler.yml @@ -53,7 +53,7 @@ policy: keys: ['systemd-hwdb', 'hardware database files'] - name: import - keys: ['systemd-import'] + keys: ['systemd-importd', 'importctl'] - name: journal keys: ['systemd-journald', 'journalctl'] From e8e3bd1dc3d79af1c391039a21d640a2aa2e2d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 20:31:14 +0200 Subject: [PATCH 1443/2155] socket-activate: fix comment "optargs" is not a thing. --- src/socket-activate/socket-activate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 768a2a3ea7235..be0179ccc9eeb 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -422,7 +422,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { log_warning("File descriptor name \"%s\" is not valid.", esc); } - /* Empty optargs means one empty name */ + /* Empty argument means one empty name */ r = strv_extend_strv(&arg_fdnames, strv_isempty(names) ? STRV_MAKE("") : names, false); From e235b6a280f14f303aceaa1d5d7f3f1d1e1170bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 20:36:27 +0200 Subject: [PATCH 1444/2155] report-basic-server: use accessor function This is the API for "external" users. --- src/report/report-basic-server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index bca943fd7faee..bcab79d8f4c25 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - if (opts.optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); From e71074f761a9a9ab984f96f530fce20eb8fbc171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 23:22:45 +0200 Subject: [PATCH 1445/2155] various: convert "services" to option macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here we have the unusual situation that the option list is conditionalized. I thought about embedding some "tag" information in individual options to allow the options to be filtered by some arbitrary conditions. But it seems that using groups works quite well. It wouldn't scale well if there was a lot more options and conditions, but for the current set it's good enough. For options that are not supported in a given service, we print a custom message ("This service does not support [this] option"), instead of the generic "Unknown option …". I think this is actually better: we don't have to pretent that we don't know about the option, and can directly say that the it's a valid option in general but this service does not support it (yet). This converts systemd-homed, systemd-hostnamed, systemd-importd, systemd-localed, systemd-logind, systemd-machined, systemd-networkd, systemd-portabled, systemd-resolved, systemd-sysupdated, systemd-timedated, and systemd-timesyncd. When we add introspection of the option data, we'll somehow have to deal with conditionalization. But let's cross that bridge when we need to. --- src/shared/service-util.c | 142 +++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 78 deletions(-) diff --git a/src/shared/service-util.c b/src/shared/service-util.c index 70d59af6c51d1..7c1df8e73adad 100644 --- a/src/shared/service-util.c +++ b/src/shared/service-util.c @@ -1,53 +1,53 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include -#include "alloc-util.h" #include "build.h" #include "bus-object.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "runtime-scope.h" #include "service-util.h" -typedef enum HelpFlags { - HELP_WITH_BUS_INTROSPECT = 1 << 0, - HELP_WITH_RUNTIME_SCOPE = 1 << 1, -} HelpFlags; - -static int help(const char *program_path, - const char *service, +static int help(const char *service, const char *description, - HelpFlags flags) { + bool with_bus_introspect, + bool with_runtime_scope) { - _cleanup_free_ char *link = NULL; + static const char* const groups[] = { + NULL, + "Bus introspection", + "Runtime scope", + }; + + bool conds[ELEMENTSOF(groups)] = { true, with_bus_introspect, with_runtime_scope }; + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; - r = terminal_urlify_man(service, "8", &link); - if (r < 0) - return log_oom(); - - printf("%1$s [OPTIONS...]\n" - "\n%5$s%7$s%6$s\n" - "\nThis program takes no positional arguments.\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - "%8$s" - "%9$s" - "\nSee the %2$s for details.\n", - program_path, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - description, - FLAGS_SET(flags, HELP_WITH_BUS_INTROSPECT) ? " --bus-introspect=PATH Write D-Bus XML introspection data\n" : "", - FLAGS_SET(flags, HELP_WITH_RUNTIME_SCOPE) ? " --system Start service in system mode\n" - " --user Start service in user mode\n" : ""); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) + if (conds[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[1] ? tables[2] : NULL); + + help_cmdline("[OPTIONS...]"); + help_abstract(description); + help_section("Options"); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) + if (conds[i]) { + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference(service, "8"); return 0; /* No further action */ } @@ -58,64 +58,50 @@ int service_parse_argv( RuntimeScope *runtime_scope, int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_BUS_INTROSPECT, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(argv[0], - service, + OPTION_COMMON_HELP: + return help(service, description, - (bus_objects ? HELP_WITH_BUS_INTROSPECT : 0) | - (runtime_scope ? HELP_WITH_RUNTIME_SCOPE : 0)); + /* with_bus_introspect= */ bus_objects, + /* with_runtime_scope= */ runtime_scope); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_BUS_INTROSPECT: - return bus_introspect_implementations( - stdout, - optarg, - bus_objects); + OPTION_GROUP("Bus introspection"): {} - case ARG_SYSTEM: - case ARG_USER: - if (!runtime_scope) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This service cannot be run in --system or --user mode, refusing."); + OPTION_LONG("bus-introspect", "PATH", "Write D-Bus XML introspection data"): + /* The option is defined in the shared option table, but it's not supported in this binary, + * so we pretend it doesn't exist. */ + if (!bus_objects) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This service does not support the --bus-introspect= option."); - *runtime_scope = c == ARG_SYSTEM ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; - break; + return bus_introspect_implementations(stdout, opts.arg, bus_objects); - case '?': - return -EINVAL; + OPTION_GROUP("Runtime scope"): {} - default: - assert_not_reached(); + OPTION_LONG_DATA("system", NULL, /* data= */ RUNTIME_SCOPE_SYSTEM, + "Start service in system mode"): {} + OPTION_LONG_DATA("user", NULL, /* data= */ RUNTIME_SCOPE_USER, + "Start service in user mode"): + if (!runtime_scope) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This service does not support the --system/--user options."); + + *runtime_scope = opts.opt->data; + break; } - if (optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program takes no arguments."); + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; /* Further action */ } From 9cfad502f4aa103ef0d2191cbb6b82fecfbc5044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:34:19 +0200 Subject: [PATCH 1446/2155] sysext: move stuff around The verb implementation functions are reordered to match the listing in --help. The option are reorded a bit to have the "important" options that determine behaviour first, and various display options and tweaks later. The cases in parse_argv are ordered in the same way. No functional change. --- src/sysext/sysext.c | 831 ++++++++++++++++++++++---------------------- 1 file changed, 415 insertions(+), 416 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 5f97f43ffb4c2..e4d9e7e0c355d 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -424,353 +424,6 @@ static int daemon_reload(void) { return bus_service_manager_reload(bus); } -static int unmerge_hierarchy(ImageClass image_class, const char *p, const char *submounts_path) { - - _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; - int n_unmerged = 0; - int r; - - assert(p); - - dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); - if (!dot_dir) - return log_oom(); - - work_dir_info_file = path_join(dot_dir, "work_dir"); - if (!work_dir_info_file) - return log_oom(); - - for (;;) { - _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL; - - /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break - * systems where /usr/ is a mount point of its own already. */ - - r = is_our_mount_point(image_class, p); - if (r < 0) - return r; - if (r == 0) - break; - - r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root); - if (r < 0) { - if (r != -ENOENT) - return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file); - } else { - _cleanup_free_ char *work_dir_in_root = NULL; - ssize_t l; - - l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); - if (l < 0) - return log_error_errno(l, "Failed to unescape work directory path: %m"); - work_dir = path_join(arg_root, work_dir_in_root); - if (!work_dir) - return log_oom(); - } - - r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW); - if (r < 0) { - /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if - * the whole hierarchy was read-only, so the dot directory inside it was not - * bind-mounted as read-only. */ - if (r != -EINVAL) - return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir); - } - - /* After we've unmounted the metadata directory, save all other submounts so we can restore - * them after unmerging the hierarchy. */ - r = move_submounts(p, submounts_path); - if (r < 0) - return r; - - r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW); - if (r < 0) - return r; - - if (work_dir) { - r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL); - if (r < 0) - return log_error_errno(r, "Failed to remove '%s': %m", work_dir); - } - - log_info("Unmerged '%s'.", p); - n_unmerged++; - } - - return n_unmerged; -} - -static int unmerge_subprocess( - ImageClass image_class, - char **hierarchies, - const char *workspace) { - - int r, ret = 0; - - assert(workspace); - assert(path_startswith(workspace, "/run/")); - - /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on - * the host otherwise. */ - r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL); - if (r < 0) - return r; - - /* Let's create the workspace if it's missing */ - r = mkdir_p(workspace, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create '%s': %m", workspace); - - STRV_FOREACH(h, hierarchies) { - _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; - - submounts_path = path_join(workspace, "submounts", *h); - if (!submounts_path) - return log_oom(); - - r = chase(*h, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); - if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *h); - continue; - } - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *h)); - continue; - } - - r = unmerge_hierarchy(image_class, resolved, submounts_path); - if (r < 0) { - RET_GATHER(ret, r); - continue; - } - if (r == 0) - continue; - - /* If we unmerged something, then we have to move the submounts from the hierarchy back into - * place in the host's original hierarchy. */ - - r = move_submounts(submounts_path, resolved); - if (r < 0) - return r; - } - - return ret; -} - -static int unmerge( - ImageClass image_class, - char **hierarchies, - bool no_reload) { - - bool need_to_reload; - int r; - - (void) dlopen_libmount(LOG_DEBUG); - - r = need_reload(image_class, hierarchies, no_reload); - if (r < 0) - return r; - need_to_reload = r > 0; - - r = pidref_safe_fork( - "(sd-unmerge)", - FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, - /* ret= */ NULL); - if (r < 0) - return r; - if (r == 0) { - /* Child with its own mount namespace */ - - r = unmerge_subprocess(image_class, hierarchies, "/run/systemd/sysext"); - - /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we - * created below /run. Nice! */ - - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - if (need_to_reload) { - r = daemon_reload(); - if (r < 0) - return r; - } - - return 0; -} - -static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; - - r = have_effective_cap(CAP_SYS_ADMIN); - if (r < 0) - return log_error_errno(r, "Failed to check if we have enough privileges: %m"); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - - return unmerge(arg_image_class, - arg_hierarchies, - arg_no_reload); -} - -static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) { - _cleanup_strv_free_ char **h = NULL; - ImageClass c; - int r; - - assert(link); - assert(image_class); - - if (!value) - return 0; - - c = image_class_from_string(value); - if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) - return sd_varlink_error_invalid_parameter_name(link, "class"); - - if (hierarchies) { - r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse environment variable: %m"); - - strv_free_and_replace(*hierarchies, h); - } - - *image_class = c; - return 0; -} - -typedef struct MethodUnmergeParameters { - const char *class; - int no_reload; -} MethodUnmergeParameters; - -static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - - static const sd_json_dispatch_field dispatch_table[] = { - { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 }, - { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 }, - VARLINK_DISPATCH_POLKIT_FIELD, - {} - }; - MethodUnmergeParameters p = { - .no_reload = -1, - }; - Hashmap **polkit_registry = ASSERT_PTR(userdata); - _cleanup_strv_free_ char **hierarchies = NULL; - ImageClass image_class = arg_image_class; - bool no_reload; - int r; - - assert(link); - - r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); - if (r != 0) - return r; - - no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; - - r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); - if (r < 0) - return r; - - r = varlink_verify_polkit_async( - link, - /* bus= */ NULL, - image_class_info[image_class].polkit_rw_action_id, - (const char**) STRV_MAKE( - "verb", "unmerge", - "noReload", one_zero(no_reload)), - polkit_registry); - if (r <= 0) - return r; - - r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); - if (r < 0) - return r; - - return sd_varlink_reply(link, NULL); -} - -static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(table_unrefp) Table *t = NULL; - int r, ret = 0; - - t = table_new("hierarchy", "extensions", "since"); - if (!t) - return log_oom(); - - table_set_ersatz_string(t, TABLE_ERSATZ_DASH); - - STRV_FOREACH(p, arg_hierarchies) { - _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL; - _cleanup_strv_free_ char **l = NULL; - struct stat st; - - r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); - if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); - continue; - } - if (r < 0) { - log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); - goto inner_fail; - } - - r = is_our_mount_point(arg_image_class, resolved); - if (r < 0) - goto inner_fail; - if (r == 0) { - r = table_add_many( - t, - TABLE_PATH, *p, - TABLE_STRING, "none", - TABLE_SET_COLOR, ansi_grey(), - TABLE_EMPTY); - if (r < 0) - return table_log_add_error(r); - - continue; - } - - f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural); - if (!f) - return log_oom(); - - r = read_full_file(f, &buf, NULL); - if (r < 0) - return log_error_errno(r, "Failed to open '%s': %m", f); - - l = strv_split_newlines(buf); - if (!l) - return log_oom(); - - if (stat(*p, &st) < 0) - return log_error_errno(errno, "Failed to stat() '%s': %m", *p); - - r = table_add_many( - t, - TABLE_PATH, *p, - TABLE_STRV, l, - TABLE_TIMESTAMP, timespec_load(&st.st_mtim)); - if (r < 0) - return table_log_add_error(r); - - continue; - - inner_fail: - if (ret == 0) - ret = r; - } - - (void) table_set_sort(t, (size_t) 0); - - r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); - if (r < 0) - return r; - - return ret; -} - static int append_overlayfs_path_option( char **options, const char *separator, @@ -964,6 +617,63 @@ static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) { } DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free); +static int parse_env(void) { + const char *env_var; + int r; + + env_var = secure_getenv(image_class_info[arg_image_class].mode_env); + if (env_var) { + r = parse_mutable_mode(env_var); + if (r < 0) + log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", + image_class_info[arg_image_class].mode_env, env_var); + else { + arg_mutable = r; + arg_mutable_set = true; + } + } + + env_var = secure_getenv(image_class_info[arg_image_class].opts_env); + if (env_var) + arg_overlayfs_mount_options = env_var; + + /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and + * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline + * switch. */ + r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); + if (r < 0) + return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); + + return 0; +} + +static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) { + _cleanup_strv_free_ char **h = NULL; + ImageClass c; + int r; + + assert(link); + assert(image_class); + + if (!value) + return 0; + + c = image_class_from_string(value); + if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) + return sd_varlink_error_invalid_parameter_name(link, "class"); + + if (hierarchies) { + r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); + if (r < 0) + return log_error_errno(r, "Failed to parse environment variable: %m"); + + strv_free_and_replace(*hierarchies, h); + } + + *image_class = c; + return 0; +} + static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) { _cleanup_free_ char *resolved_path = NULL; int r; @@ -1793,7 +1503,7 @@ static int strverscmp_improvedp(char *const* a, char *const* b) { return strverscmp_improved(*a, *b); } -static const ImagePolicy *pick_image_policy(const Image *img) { +static const ImagePolicy* pick_image_policy(const Image *img) { assert(img); assert(img->path); @@ -1817,12 +1527,185 @@ static const ImagePolicy *pick_image_policy(const Image *img) { if (path_startswith(img->path, "/.extra/global_confext/")) return &image_policy_confext_strict; - /* Better safe than sorry, refuse everything else passed in via the untrusted /.extra/ dir */ - if (path_startswith(img->path, "/.extra/")) - return &image_policy_deny; + /* Better safe than sorry, refuse everything else passed in via the untrusted /.extra/ dir */ + if (path_startswith(img->path, "/.extra/")) + return &image_policy_deny; + } + + return image_class_info[img->class].default_image_policy; +} + +static int unmerge_hierarchy(ImageClass image_class, const char *p, const char *submounts_path) { + _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; + int n_unmerged = 0; + int r; + + assert(p); + + dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); + if (!dot_dir) + return log_oom(); + + work_dir_info_file = path_join(dot_dir, "work_dir"); + if (!work_dir_info_file) + return log_oom(); + + for (;;) { + _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL; + + /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break + * systems where /usr/ is a mount point of its own already. */ + + r = is_our_mount_point(image_class, p); + if (r < 0) + return r; + if (r == 0) + break; + + r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file); + } else { + _cleanup_free_ char *work_dir_in_root = NULL; + ssize_t l; + + l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); + if (l < 0) + return log_error_errno(l, "Failed to unescape work directory path: %m"); + work_dir = path_join(arg_root, work_dir_in_root); + if (!work_dir) + return log_oom(); + } + + r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) { + /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if + * the whole hierarchy was read-only, so the dot directory inside it was not + * bind-mounted as read-only. */ + if (r != -EINVAL) + return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir); + } + + /* After we've unmounted the metadata directory, save all other submounts so we can restore + * them after unmerging the hierarchy. */ + r = move_submounts(p, submounts_path); + if (r < 0) + return r; + + r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) + return r; + + if (work_dir) { + r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "Failed to remove '%s': %m", work_dir); + } + + log_info("Unmerged '%s'.", p); + n_unmerged++; + } + + return n_unmerged; +} + +static int unmerge_subprocess( + ImageClass image_class, + char **hierarchies, + const char *workspace) { + + int r, ret = 0; + + assert(workspace); + assert(path_startswith(workspace, "/run/")); + + /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on + * the host otherwise. */ + r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL); + if (r < 0) + return r; + + /* Let's create the workspace if it's missing */ + r = mkdir_p(workspace, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", workspace); + + STRV_FOREACH(h, hierarchies) { + _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; + + submounts_path = path_join(workspace, "submounts", *h); + if (!submounts_path) + return log_oom(); + + r = chase(*h, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *h); + continue; + } + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *h)); + continue; + } + + r = unmerge_hierarchy(image_class, resolved, submounts_path); + if (r < 0) { + RET_GATHER(ret, r); + continue; + } + if (r == 0) + continue; + + /* If we unmerged something, then we have to move the submounts from the hierarchy back into + * place in the host's original hierarchy. */ + + r = move_submounts(submounts_path, resolved); + if (r < 0) + return r; + } + + return ret; +} + +static int unmerge( + ImageClass image_class, + char **hierarchies, + bool no_reload) { + + bool need_to_reload; + int r; + + (void) dlopen_libmount(LOG_DEBUG); + + r = need_reload(image_class, hierarchies, no_reload); + if (r < 0) + return r; + need_to_reload = r > 0; + + r = pidref_safe_fork( + "(sd-unmerge)", + FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, + /* ret= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* Child with its own mount namespace */ + + r = unmerge_subprocess(image_class, hierarchies, "/run/systemd/sysext"); + + /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we + * created below /run. Nice! */ + + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } - return image_class_info[img->class].default_image_policy; + if (need_to_reload) { + r = daemon_reload(); + if (r < 0) + return r; + } + + return 0; } static int merge_subprocess( @@ -2421,6 +2304,86 @@ static int merge(ImageClass image_class, return 1; } +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(table_unrefp) Table *t = NULL; + int r, ret = 0; + + t = table_new("hierarchy", "extensions", "since"); + if (!t) + return log_oom(); + + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + STRV_FOREACH(p, arg_hierarchies) { + _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL; + _cleanup_strv_free_ char **l = NULL; + struct stat st; + + r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); + continue; + } + if (r < 0) { + log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); + goto inner_fail; + } + + r = is_our_mount_point(arg_image_class, resolved); + if (r < 0) + goto inner_fail; + if (r == 0) { + r = table_add_many( + t, + TABLE_PATH, *p, + TABLE_STRING, "none", + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY); + if (r < 0) + return table_log_add_error(r); + + continue; + } + + f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural); + if (!f) + return log_oom(); + + r = read_full_file(f, &buf, NULL); + if (r < 0) + return log_error_errno(r, "Failed to open '%s': %m", f); + + l = strv_split_newlines(buf); + if (!l) + return log_oom(); + + if (stat(*p, &st) < 0) + return log_error_errno(errno, "Failed to stat() '%s': %m", *p); + + r = table_add_many( + t, + TABLE_PATH, *p, + TABLE_STRV, l, + TABLE_TIMESTAMP, timespec_load(&st.st_mtim)); + if (r < 0) + return table_log_add_error(r); + + continue; + + inner_fail: + if (ret == 0) + ret = r; + } + + (void) table_set_sort(t, (size_t) 0); + + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + + return ret; +} + static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **ret_images) { _cleanup_hashmap_free_ Hashmap *images = NULL; Image *img; @@ -2597,6 +2560,72 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var return sd_varlink_reply(link, NULL); } +static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + r = have_effective_cap(CAP_SYS_ADMIN); + if (r < 0) + return log_error_errno(r, "Failed to check if we have enough privileges: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); + + return unmerge(arg_image_class, + arg_hierarchies, + arg_no_reload); +} + +typedef struct MethodUnmergeParameters { + const char *class; + int no_reload; +} MethodUnmergeParameters; + +static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 }, + { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + MethodUnmergeParameters p = { + .no_reload = -1, + }; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **hierarchies = NULL; + ImageClass image_class = arg_image_class; + bool no_reload; + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + + r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_rw_action_id, + (const char**) STRV_MAKE( + "verb", "unmerge", + "noReload", one_zero(no_reload)), + polkit_registry); + if (r <= 0) + return r; + + r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + static int refresh( ImageClass image_class, char **hierarchies, @@ -2816,20 +2845,20 @@ static int help(void) { " -h --help Show this help\n" " --version Show package version\n" "\n%3$sOptions:%4$s\n" + " --root=PATH Operate relative to root path\n" " --mutable=yes|no|auto|import|ephemeral|ephemeral-import|help\n" " Specify a mutability mode of the merged hierarchy\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --root=PATH Operate relative to root path\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" + " --noexec=BOOL Whether to mount extension overlay with noexec\n" " --force Ignore version incompatibilities\n" " --no-reload Do not reload the service manager\n" " --always-refresh=yes|no\n" " Do not skip refresh when no changes were found\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --noexec=BOOL Whether to mount extension overlay with noexec\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", program_invocation_short_name, link, @@ -2892,14 +2921,6 @@ 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 ARG_ROOT: r = parse_path_argument(optarg, false, &arg_root); if (r < 0) @@ -2908,15 +2929,19 @@ static int parse_argv(int argc, char *argv[]) { arg_no_reload = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; + case ARG_MUTABLE: + if (streq(optarg, "help")) { + if (arg_legend) + puts("Known mutability modes:"); - break; + return DUMP_STRING_TABLE(mutable_mode, MutableMode, _MUTABLE_MAX); + } - case ARG_FORCE: - arg_force = true; + r = parse_mutable_mode(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg); + arg_mutable = r; + arg_mutable_set = true; break; case ARG_IMAGE_POLICY: @@ -2936,6 +2961,10 @@ static int parse_argv(int argc, char *argv[]) { arg_noexec = r; break; + case ARG_FORCE: + arg_force = true; + break; + case ARG_NO_RELOAD: arg_no_reload = true; break; @@ -2946,19 +2975,19 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_MUTABLE: - if (streq(optarg, "help")) { - if (arg_legend) - puts("Known mutability modes:"); + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; - return DUMP_STRING_TABLE(mutable_mode, MutableMode, _MUTABLE_MAX); - } + 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; - r = parse_mutable_mode(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg); - arg_mutable = r; - arg_mutable_set = true; break; case '?': @@ -2977,36 +3006,6 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int parse_env(void) { - const char *env_var; - int r; - - env_var = secure_getenv(image_class_info[arg_image_class].mode_env); - if (env_var) { - r = parse_mutable_mode(env_var); - if (r < 0) - log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", - image_class_info[arg_image_class].mode_env, env_var); - else { - arg_mutable = r; - arg_mutable_set = true; - } - } - - env_var = secure_getenv(image_class_info[arg_image_class].opts_env); - if (env_var) - arg_overlayfs_mount_options = env_var; - - /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and - * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline - * switch. */ - r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); - - return 0; -} - static int sysext_main(int argc, char *argv[]) { static const Verb verbs[] = { From a8971f6c918711939413aafa93a234ba479fa016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:45:32 +0200 Subject: [PATCH 1447/2155] sysext: convert to option and verb macros Co-developed-by: Claude Opus 4.7 --- src/sysext/sysext.c | 188 +++++++++++++++++--------------------------- 1 file changed, 70 insertions(+), 118 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index e4d9e7e0c355d..40d39b7654d2c 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -11,6 +10,7 @@ #include "sd-json.h" #include "sd-varlink.h" +#include "ansi-color.h" #include "argv-util.h" #include "blkid-util.h" #include "blockdev-util.h" @@ -34,6 +34,7 @@ #include "format-table.h" #include "fs-util.h" #include "hashmap.h" +#include "help-util.h" #include "image-policy.h" #include "initrd-util.h" #include "label-util.h" /* IWYU pragma: keep */ @@ -44,13 +45,13 @@ #include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "rm-rf.h" @@ -2304,6 +2305,7 @@ static int merge(ImageClass image_class, return 1; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current merge status (default)"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, ret = 0; @@ -2440,6 +2442,7 @@ static int look_for_merged_hierarchies( return 0; } +VERB_NOARG(verb_merge, "merge", "Merge extensions into relevant hierarchies"); static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; const char *which; @@ -2560,6 +2563,7 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var return sd_varlink_reply(link, NULL); } +VERB_NOARG(verb_unmerge, "unmerge", "Unmerge extensions from relevant hierarchies"); static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2669,6 +2673,7 @@ static int refresh( return r; } +VERB_NOARG(verb_refresh, "refresh", "Unmerge/merge extensions again"); static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2735,6 +2740,7 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v return sd_varlink_reply(link, NULL); } +VERB_NOARG(verb_list, "list", "List installed extensions"); 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; @@ -2827,125 +2833,91 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *commands = NULL, *options = NULL; int r; - r = terminal_urlify_man(image_class_info[arg_image_class].full_identifier, "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$s%7$s%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current merge status (default)\n" - " merge Merge extensions into relevant hierarchies\n" - " unmerge Unmerge extensions from relevant hierarchies\n" - " refresh Unmerge/merge extensions again\n" - " list List installed extensions\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate relative to root path\n" - " --mutable=yes|no|auto|import|ephemeral|ephemeral-import|help\n" - " Specify a mutability mode of the merged hierarchy\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --noexec=BOOL Whether to mount extension overlay with noexec\n" - " --force Ignore version incompatibilities\n" - " --no-reload Do not reload the service manager\n" - " --always-refresh=yes|no\n" - " Do not skip refresh when no changes were found\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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - image_class_info[arg_image_class].blurb); + r = option_parser_get_help_table(&commands); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table_group("Options", &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, commands, options); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_ROOT, - ARG_JSON, - ARG_FORCE, - ARG_IMAGE_POLICY, - ARG_NOEXEC, - ARG_NO_RELOAD, - ARG_ALWAYS_REFRESH, - ARG_MUTABLE, - }; + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract(image_class_info[arg_image_class].blurb); - 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 }, - { "root", required_argument, NULL, ARG_ROOT }, - { "json", required_argument, NULL, ARG_JSON }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "always-refresh", required_argument, NULL, ARG_ALWAYS_REFRESH }, - { "mutable", required_argument, NULL, ARG_MUTABLE }, - {} - }; + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + r = table_print_or_warn(commands); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; - int c, r; + help_man_page_reference(image_class_info[arg_image_class].full_identifier, "8"); + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, false, &arg_root); + OPTION_GROUP("Options"): {} + + OPTION_LONG("root", "PATH", "Operate relative to root PATH"): + r = parse_path_argument(opts.arg, false, &arg_root); if (r < 0) return r; /* If --root= is provided, do not reload the service manager */ arg_no_reload = true; break; - case ARG_MUTABLE: - if (streq(optarg, "help")) { + OPTION_LONG("mutable", "MODE", + "Specify a mutability mode (yes, no, auto, import, ephemeral, ephemeral-import, help)"): + if (streq(opts.arg, "help")) { if (arg_legend) puts("Known mutability modes:"); return DUMP_STRING_TABLE(mutable_mode, MutableMode, _MUTABLE_MAX); } - r = parse_mutable_mode(optarg); + r = parse_mutable_mode(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg); + return log_error_errno(r, "Failed to parse argument to --mutable=: %s", opts.arg); arg_mutable = r; arg_mutable_set = true; 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(opts.arg, &arg_image_policy); if (r < 0) return r; /* When the CLI flag is given we initialize even if NULL @@ -2953,48 +2925,41 @@ static int parse_argv(int argc, char *argv[]) { arg_image_policy_set = true; break; - case ARG_NOEXEC: - r = parse_boolean_argument("--noexec", optarg, NULL); + OPTION_LONG("noexec", "BOOL", "Whether to mount extension overlay with noexec"): + r = parse_boolean_argument("--noexec", opts.arg, NULL); if (r < 0) return r; arg_noexec = r; break; - case ARG_FORCE: + OPTION_LONG("force", NULL, "Ignore version incompatibilities"): arg_force = true; break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, "Do not reload the service manager"): arg_no_reload = true; break; - case ARG_ALWAYS_REFRESH: - r = parse_boolean_argument("--always-refresh", optarg, &arg_always_refresh); + OPTION_LONG("always-refresh", "BOOL", "Whether to refresh when no changes were found"): + r = parse_boolean_argument("--always-refresh", opts.arg, &arg_always_refresh); if (r < 0) return r; 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(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -3003,25 +2968,12 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&opts); return 1; } -static int sysext_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "merge", VERB_ANY, 1, 0, verb_merge }, - { "unmerge", VERB_ANY, 1, 0, verb_unmerge }, - { "refresh", VERB_ANY, 1, 0, verb_refresh }, - { "list", VERB_ANY, 1, 0, verb_list }, - { "help", VERB_ANY, 1, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); @@ -3034,7 +2986,7 @@ static int run(int argc, char *argv[]) { return r; /* Parse command line */ - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -3096,7 +3048,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return sysext_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 4ce94c3bb4769606a991a081f1827a467736b939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:00:47 +0200 Subject: [PATCH 1448/2155] test-modem-manager-mock: convert to OPTION macros --help and --version are moved to the beginning of the option list. This is the usual location. Custom '-v' alias for --version is dropped. It is not used by anything and it's better to follow the usual style. Co-developed-by: Claude Opus 4.7 --- src/network/test-modem-manager-mock.c | 113 ++++++++++++-------------- 1 file changed, 53 insertions(+), 60 deletions(-) diff --git a/src/network/test-modem-manager-mock.c b/src/network/test-modem-manager-mock.c index 60f0dfa8d4ea2..7ddb18828af11 100644 --- a/src/network/test-modem-manager-mock.c +++ b/src/network/test-modem-manager-mock.c @@ -10,16 +10,17 @@ * - 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 "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" @@ -363,82 +364,74 @@ static int filter_handler(sd_bus_message *m, void *userdata, sd_bus_error *error 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...]"); + help_abstract("Mock ModemManager D-Bus service for testing."); + + help_section("Options"); + return table_print_or_warn(options); +} + 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) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_IFNAME: - if (free_and_strdup(&arg_ifname, optarg) < 0) + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("ifname", "NAME", "Interface name"): + if (free_and_strdup(&arg_ifname, opts.arg) < 0) return log_oom(); break; - case ARG_IPV4_ADDRESS: - if (free_and_strdup(&arg_ipv4_address, optarg) < 0) + + OPTION_LONG("ipv4-address", "ADDR", "IPv4 address"): + if (free_and_strdup(&arg_ipv4_address, opts.arg) < 0) return log_oom(); break; - case ARG_IPV4_GATEWAY: - if (free_and_strdup(&arg_ipv4_gateway, optarg) < 0) + + OPTION_LONG("ipv4-gateway", "ADDR", "IPv4 gateway"): + if (free_and_strdup(&arg_ipv4_gateway, opts.arg) < 0) return log_oom(); break; - case ARG_IPV4_PREFIX: - r = safe_atou32(optarg, &arg_ipv4_prefix); + + OPTION_LONG("ipv4-prefix", "LEN", "IPv4 prefix length"): + r = safe_atou32(opts.arg, &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) + + OPTION_LONG("ipv6-address", "ADDR", "IPv6 address"): + if (free_and_strdup(&arg_ipv6_address, opts.arg) < 0) return log_oom(); break; - case ARG_IPV6_GATEWAY: - if (free_and_strdup(&arg_ipv6_gateway, optarg) < 0) + + OPTION_LONG("ipv6-gateway", "ADDR", "IPv6 gateway"): + if (free_and_strdup(&arg_ipv6_gateway, opts.arg) < 0) return log_oom(); break; - case ARG_IPV6_PREFIX: - r = safe_atou32(optarg, &arg_ipv6_prefix); + + OPTION_LONG("ipv6-prefix", "LEN", "IPv6 prefix length"): + r = safe_atou32(opts.arg, &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) From d5301b635f91226d4f1be9671bf79aff8f59899b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:02:27 +0200 Subject: [PATCH 1449/2155] networkd-wait-online: convert to OPTION macros Also fix a latent bug in parse_interface_with_operstate_range() where the global 'optarg' was used instead of the 'str' parameter when extracting the interface name; with getopt removed they would have diverged. The help strings are adjusted a bit to be grammatical and short so that the table formatting is easier. Co-developed-by: Claude Opus 4.7 --- src/network/wait-online/wait-online.c | 120 +++++++++----------------- 1 file changed, 42 insertions(+), 78 deletions(-) diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index b1d0b9cde212d..e566f89d02e57 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-event.h" @@ -8,11 +7,13 @@ #include "alloc-util.h" #include "build.h" #include "daemon-util.h" +#include "format-table.h" #include "hashmap.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "socket-util.h" #include "strv.h" #include "time-util.h" @@ -31,32 +32,22 @@ STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_freep); STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-networkd-wait-online.service", "8", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...]\n\n" - "Block until network is configured.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " -i --interface=INTERFACE[:MIN_OPERSTATE[:MAX_OPERSTATE]]\n" - " Block until at least these interfaces have appeared\n" - " --ignore=INTERFACE Don't take these interfaces into account\n" - " -o --operational-state=MIN_OPERSTATE[:MAX_OPERSTATE]\n" - " Required operational state\n" - " -4 --ipv4 Requires at least one IPv4 address\n" - " -6 --ipv6 Requires at least one IPv6 address\n" - " --any Wait until at least one of the interfaces is online\n" - " --timeout=SECS Maximum time to wait for network connectivity\n" - " --dns Requires at least one DNS server to be accessible\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + help_cmdline("[OPTIONS...]"); + help_abstract("Block until network is configured."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-networkd-wait-online.service", "8"); return 0; } @@ -78,7 +69,7 @@ static int parse_interface_with_operstate_range(const char *str) { if (r < 0) return log_error_errno(r, "Invalid operational state range: %s", p + 1); - ifname = strndup(optarg, p - optarg); + ifname = strndup(str, p - str); } else { *range = LINK_OPERSTATE_RANGE_INVALID; ifname = strdup(str); @@ -105,97 +96,70 @@ static int parse_interface_with_operstate_range(const char *str) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_IGNORE, - ARG_ANY, - ARG_TIMEOUT, - ARG_DNS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "interface", required_argument, NULL, 'i' }, - { "ignore", required_argument, NULL, ARG_IGNORE }, - { "operational-state", required_argument, NULL, 'o' }, - { "ipv4", no_argument, NULL, '4' }, - { "ipv6", no_argument, NULL, '6' }, - { "any", no_argument, NULL, ARG_ANY }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "dns", optional_argument, NULL, ARG_DNS }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hi:qo:46", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case 'q': + OPTION_COMMON_VERSION: + return version(); + + OPTION('q', "quiet", NULL, "Do not show status information"): arg_quiet = true; break; - case ARG_VERSION: - return version(); - - case 'i': - r = parse_interface_with_operstate_range(optarg); + OPTION('i', "interface", "IFNAME[:MIN[:MAX]]", + "Block until at least these interfaces have appeared, " + "in the operational state between MIN and MAX"): + r = parse_interface_with_operstate_range(opts.arg); if (r < 0) return r; break; - case ARG_IGNORE: - if (strv_extend(&arg_ignore, optarg) < 0) + OPTION_LONG("ignore", "IFNAME", "Don't take these interfaces into account"): + if (strv_extend(&arg_ignore, opts.arg) < 0) return log_oom(); - break; - case 'o': - r = parse_operational_state_range(optarg, &arg_required_operstate); + OPTION('o', "operational-state", "MIN[:MAX]", + "Require operational state between MIN and MAX"): + r = parse_operational_state_range(opts.arg, &arg_required_operstate); if (r < 0) - return log_error_errno(r, "Invalid operational state range '%s'", optarg); + return log_error_errno(r, "Invalid operational state range '%s'", opts.arg); break; - case '4': + OPTION('4', "ipv4", NULL, "Require at least one IPv4 address"): arg_required_family |= ADDRESS_FAMILY_IPV4; break; - case '6': + OPTION('6', "ipv6", NULL, "Require at least one IPv6 address"): arg_required_family |= ADDRESS_FAMILY_IPV6; break; - case ARG_ANY: + OPTION_LONG("any", NULL, "Wait until at least one of the interfaces is online"): arg_any = true; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SECS", "Maximum time to wait for network connectivity"): + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) return r; break; - case ARG_DNS: - r = parse_boolean_argument("--dns", optarg, &arg_requires_dns); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "dns", "BOOL", + "Require at least one DNS server to be accessible"): + r = parse_boolean_argument("--dns", opts.arg, &arg_requires_dns); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; From 6557a98705f67e39a2eb262b6a5d9e0641ef1e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:05:02 +0200 Subject: [PATCH 1450/2155] journal-upload: convert to OPTION macros The help strings are adjusted a tiny bit. Co-developed-by: Claude Opus 4.7 --- src/journal-remote/journal-upload.c | 189 +++++++++++----------------- 1 file changed, 72 insertions(+), 117 deletions(-) diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 23bb48f687620..f4b698ae415d6 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include @@ -17,10 +16,12 @@ #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" #include "hashmap.h" +#include "help-util.h" #include "journal-header-util.h" #include "journal-upload.h" #include "journal-util.h" @@ -28,9 +29,9 @@ #include "logs-show.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" -#include "pretty-print.h" #include "process-util.h" #include "string-util.h" #include "strv.h" @@ -683,194 +684,145 @@ 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-upload.service", "8", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%s -u URL {FILE|-}...\n\n" - "Upload journal events to a remote server.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -u --url=URL Upload to this address (default port " - STRINGIFY(DEFAULT_PORT) ")\n" - " --key=FILENAME Specify key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME Specify certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --system Use the system journal\n" - " --user Use the user journal for the current user\n" - " -m --merge Use all available journals\n" - " -M --machine=CONTAINER Operate on local container\n" - " --namespace=NAMESPACE Use journal files from namespace\n" - " -D --directory=PATH Use journal files from directory\n" - " --file=PATH Use this journal file\n" - " --cursor=CURSOR Start at the specified cursor\n" - " --after-cursor=CURSOR Start after the specified cursor\n" - " --follow[=BOOL] Do [not] wait for input\n" - " --save-state[=FILE] Save uploaded cursors (default \n" - " " STATE_FILE ")\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + help_cmdline("-u URL {FILE|-}..."); + help_abstract("Upload journal events to a remote server."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-journal-upload.service", "8"); 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_FILE, - ARG_CURSOR, - ARG_AFTER_CURSOR, - ARG_FOLLOW, - ARG_SAVE_STATE, - ARG_NAMESPACE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, 'u' }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "merge", no_argument, NULL, 'm' }, - { "machine", required_argument, NULL, 'M' }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - { "cursor", required_argument, NULL, ARG_CURSOR }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "follow", optional_argument, NULL, ARG_FOLLOW }, - { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'u': - r = free_and_strdup_warn(&arg_url, optarg); + OPTION('u', "url", "URL", + "Upload to this address (default port " STRINGIFY(DEFAULT_PORT) ")"): + r = free_and_strdup_warn(&arg_url, opts.arg); if (r < 0) return r; break; - case ARG_KEY: - r = free_and_strdup_warn(&arg_key, optarg); + OPTION_LONG("key", "FILENAME", + "Specify key in PEM format (default: \"" PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, opts.arg); if (r < 0) return r; break; - case ARG_CERT: - r = free_and_strdup_warn(&arg_cert, optarg); + OPTION_LONG("cert", "FILENAME", + "Specify certificate in PEM format (default: \"" CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, opts.arg); if (r < 0) return r; break; - case ARG_TRUST: - r = free_and_strdup_warn(&arg_trust, optarg); + OPTION_LONG("trust", "FILENAME|all", + "Specify CA certificate or disable checking (default: \"" TRUST_FILE "\")"): + r = free_and_strdup_warn(&arg_trust, opts.arg); if (r < 0) return r; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Use the system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Use the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'm': + OPTION('m', "merge", NULL, "Use all available journals"): arg_merge = true; break; - case 'M': - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION_COMMON_MACHINE: + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { + OPTION_LONG("namespace", "NAMESPACE", "Use journal files from namespace"): + if (streq(opts.arg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = mfree(arg_namespace); r = 0; - } else if (startswith(optarg, "+")) { + } else if (startswith(opts.arg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); - } else if (isempty(optarg)) { + r = free_and_strdup_warn(&arg_namespace, opts.arg + 1); + } else if (isempty(opts.arg)) { arg_namespace_flags = 0; arg_namespace = mfree(arg_namespace); r = 0; } else { arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); + r = free_and_strdup_warn(&arg_namespace, opts.arg); } if (r < 0) return r; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Use journal files from this directory"): + r = free_and_strdup_warn(&arg_directory, opts.arg); if (r < 0) return r; break; - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Use this journal file"): + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - case ARG_CURSOR: - case ARG_AFTER_CURSOR: - r = free_and_strdup_warn(&arg_cursor, optarg); + OPTION_LONG_DATA("after-cursor", "CURSOR", /* data= */ true, + "Start after the specified cursor"): {} + OPTION_LONG_DATA("cursor", "CURSOR", /* data= */ false, + "Start at the specified cursor"): + r = free_and_strdup_warn(&arg_cursor, opts.arg); if (r < 0) return r; - arg_after_cursor = c == ARG_AFTER_CURSOR; + arg_after_cursor = opts.opt->data; break; - case ARG_FOLLOW: - r = parse_boolean_argument("--follow", optarg, NULL); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "follow", "BOOL", + "Whether to wait for input"): + r = parse_boolean_argument("--follow", opts.arg, NULL); if (r < 0) return r; arg_follow = r; break; - case ARG_SAVE_STATE: - r = free_and_strdup_warn(&arg_save_state, optarg ?: STATE_FILE); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "save-state", "FILE", + "Save uploaded cursors (default " STATE_FILE ")"): + r = free_and_strdup_warn(&arg_save_state, opts.arg ?: STATE_FILE); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_url) @@ -881,10 +833,12 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --key= and --cert= must be used together."); - if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) + char **args = option_parser_get_args(&opts); + if (!strv_isempty(args) && (arg_directory || arg_file || arg_machine || arg_journal_type)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Input arguments make no sense with journal input."); + *ret_args = args; return 1; } @@ -911,6 +865,7 @@ static int open_journal(sd_journal **j) { static int run(int argc, char **argv) { _cleanup_(destroy_uploader) Uploader u = {}; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; + char **args = NULL; bool use_journal; int r; @@ -920,7 +875,7 @@ static int run(int argc, char **argv) { if (r < 0) return r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -947,7 +902,7 @@ static int run(int argc, char **argv) { log_debug("%s running as pid "PID_FMT, program_invocation_short_name, getpid_cached()); - use_journal = optind >= argc; + use_journal = strv_isempty(args); if (use_journal) { sd_journal *j; r = open_journal(&j); @@ -965,7 +920,7 @@ static int run(int argc, char **argv) { "STATUS=Processing input...", NOTIFY_STOPPING_MESSAGE); - for (;;) { + for (size_t i = 0;;) { r = sd_event_get_state(u.event); if (r < 0) return r; @@ -978,11 +933,11 @@ static int run(int argc, char **argv) { r = check_journal_input(&u); } else if (u.input < 0 && !use_journal) { - if (optind >= argc) + if (!args[i]) return 0; - log_debug("Using %s as input.", argv[optind]); - r = open_file_for_upload(&u, argv[optind++]); + log_debug("Using %s as input.", args[i]); + r = open_file_for_upload(&u, args[i++]); } if (r < 0) return r; From d9fbe513a9700a52845dd2ebbf0f583bab7519f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:07:29 +0200 Subject: [PATCH 1451/2155] mstack-tool: convert to OPTION macros Both the main parser and the util-linux mount-helper-mode parser (invoked as mount.mstack) are converted with "systmed-mstack" and "mount.mstack" as namespaces. The latter has no help. For systemd-mstack, Commands are listed first, and then Options. And --no-pager, --no-legend, --json= are moved to the end. Co-developed-by: Claude Opus 4.7 --- src/mstack/mstack-tool.c | 232 +++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 133 deletions(-) diff --git a/src/mstack/mstack-tool.c b/src/mstack/mstack-tool.c index 2e8946ab72a7e..244e7dc682dcd 100644 --- a/src/mstack/mstack-tool.c +++ b/src/mstack/mstack-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "argv-util.h" @@ -11,13 +10,14 @@ #include "extract-word.h" #include "fd-util.h" #include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "string-util.h" static enum { @@ -41,191 +41,155 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); 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; - r = terminal_urlify_man("systemd-mstack", "1", &link); + r = option_parser_get_help_table_ns("systemd-mstack", &options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] WHAT\n" - "%1$s [OPTIONS...] --mount WHAT WHERE\n" - "%1$s [OPTIONS...] --umount WHERE\n" - "\n%5$sInspect or apply mount stack.%6$s\n\n" - "%3$sOptions:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers\n" - " --json=pretty|short|off Generate JSON output\n" - " -r --read-only Mount read-only\n" - " --mkdir Make mount directory before mounting, if missing\n" - " --rmdir Remove mount directory after unmounting\n" - " --image-policy=POLICY\n" - " Specify image dissection policy\n" - " --image-filter=FILTER\n" - " Specify image dissection filter\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the mstack 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" - "\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_full("systemd-mstack", "Commands", &commands); + if (r < 0) + return r; + (void) table_sync_column_widths(0, options, commands); + + help_cmdline("[OPTIONS...] WHAT"); + help_cmdline("[OPTIONS...] --mount WHAT WHERE"); + help_cmdline("[OPTIONS...] --umount WHERE"); + help_abstract("Inspect or apply mount stack."); + + help_section("Commands"); + r = table_print_or_warn(commands); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-mstack", "1"); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_MKDIR, - ARG_RMDIR, - ARG_IMAGE_POLICY, - ARG_IMAGE_FILTER, - }; - - 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' }, - { "json", required_argument, NULL, ARG_JSON }, - { "read-only", no_argument, NULL, 'r' }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hmMuUr", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "systemd-mstack" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + OPTION_NAMESPACE("systemd-mstack"): {} - case ARG_NO_LEGEND: - arg_legend = false; + OPTION('r', "read-only", NULL, "Mount read-only"): + arg_mstack_flags |= MSTACK_RDONLY; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - + OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"): + arg_mstack_flags |= MSTACK_MKDIR; break; - case 'r': - arg_mstack_flags |= MSTACK_RDONLY; + OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"): + arg_rmdir = 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(opts.arg, &arg_image_policy); if (r < 0) return r; 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(opts.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", opts.arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); break; } - case ARG_MKDIR: - arg_mstack_flags |= MSTACK_MKDIR; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_RMDIR: - arg_rmdir = true; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case 'm': + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"): arg_action = ACTION_MOUNT; break; - case 'M': - /* Shortcut combination of --mkdir + --mount */ + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): arg_action = ACTION_MOUNT; arg_mstack_flags |= MSTACK_MKDIR; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): arg_action = ACTION_UMOUNT; break; - case 'U': - /* Shortcut combination of --rmdir + --umount */ + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): arg_action = ACTION_UMOUNT; arg_rmdir = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); switch (arg_action) { case ACTION_INSPECT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; break; case ACTION_MOUNT: - if (optind + 2 != argc) + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; break; case ACTION_UMOUNT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_where); if (r < 0) return r; @@ -239,47 +203,49 @@ static int parse_argv(int argc, char *argv[]) { } static int parse_argv_as_mount_helper(int argc, char *argv[]) { - const char *options = NULL; + const char *mount_options = NULL; bool fake = false; - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.mstack" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.mstack"): {} + + OPTION_SHORT('f', NULL, NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", NULL): + mount_options = opts.arg; break; - case 't': - if (!streq(optarg, "mstack")) - log_debug("Unexpected file system type '%s', ignoring.", optarg); + OPTION_SHORT('t', "TYPE", NULL): + if (!streq(opts.arg, "mstack")) + log_debug("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, NULL): {} /* sloppy mount options, fall-through */ + OPTION_SHORT('n', NULL, NULL): {} /* aka --no-mtab, fall-through */ + OPTION_SHORT('v', NULL, NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NAMESPACE", NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected an image file path and target directory as only argument."); + "Expected an image file path and target directory as arguments."); - for (const char *p = options;;) { + for (const char *p = mount_options;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); @@ -300,11 +266,11 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; From c97084d5b0cf19e4895b05469e0c595333b9574b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:10:00 +0200 Subject: [PATCH 1452/2155] networkctl: convert to OPTION and VERB macros --help output is identical except for common options strings and whitespace. Co-developed-by: Claude Opus 4.7 --- src/network/networkctl.c | 261 ++++++++++++++++----------------------- 1 file changed, 109 insertions(+), 152 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 7fe34ac1eb778..e77950bbe8c4f 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "alloc-util.h" #include "build.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" @@ -16,10 +16,10 @@ #include "networkctl-lldp.h" #include "networkctl-misc.h" #include "networkctl-status-link.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "string-util.h" #include "verbs.h" @@ -38,144 +38,146 @@ bool arg_ask_password = true; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); +VERB_SCOPE(, verb_list_links, "list", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List links"); +VERB_SCOPE(, verb_link_status, "status", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show link status"); +VERB_SCOPE(, verb_link_lldp_status, "lldp", "[PATTERN...]", VERB_ANY, VERB_ANY, 0, + "Show LLDP neighbors"); +VERB_SCOPE(, verb_list_address_labels, "label", NULL, 1, 1, 0, + "Show current address label entries in the kernel"); +VERB_SCOPE(, verb_link_delete, "delete", "DEVICES...", 2, VERB_ANY, 0, + "Delete virtual netdevs"); +VERB_SCOPE(, verb_link_varlink_simple_method, "up", "DEVICES...", 2, VERB_ANY, 0, + "Bring devices up"); +VERB_SCOPE(, verb_link_varlink_simple_method, "down", "DEVICES...", 2, VERB_ANY, 0, + "Bring devices down"); +VERB_SCOPE(, verb_link_varlink_simple_method, "renew", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Renew dynamic configurations"); +VERB_SCOPE(, verb_link_varlink_simple_method, "forcerenew", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Trigger DHCP reconfiguration of all connected clients"); +VERB_SCOPE(, verb_link_varlink_simple_method, "reconfigure", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Reconfigure interfaces"); +VERB_SCOPE(, verb_reload, "reload", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reload .network and .netdev files"); +VERB_SCOPE(, verb_edit, "edit", "FILES|DEVICES...", 2, VERB_ANY, 0, + "Edit network configuration files"); +VERB_SCOPE(, verb_cat, "cat", "[FILES|DEVICES...]", 1, VERB_ANY, 0, + "Show network configuration files"); +VERB_SCOPE(, verb_mask, "mask", "FILES...", 2, VERB_ANY, 0, + "Mask network configuration files"); +VERB_SCOPE(, verb_unmask, "unmask", "FILES...", 2, VERB_ANY, 0, + "Unmask network configuration files"); +VERB_SCOPE(, verb_persistent_storage, "persistent-storage", "BOOL", 2, 2, 0, + "Notify systemd-networkd if persistent storage is ready"); + static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("networkctl", "1", &link); + 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); + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Query and control the networking subsystem."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); if (r < 0) - return log_oom(); - - printf("%s [OPTIONS...] COMMAND\n\n" - "%sQuery and control the networking subsystem.%s\n" - "\nCommands:\n" - " list [PATTERN...] List links\n" - " status [PATTERN...] Show link status\n" - " lldp [PATTERN...] Show LLDP neighbors\n" - " label Show current address label entries in the kernel\n" - " delete DEVICES... Delete virtual netdevs\n" - " up DEVICES... Bring devices up\n" - " down DEVICES... Bring devices down\n" - " renew DEVICES... Renew dynamic configurations\n" - " forcerenew DEVICES... Trigger DHCP reconfiguration of all connected clients\n" - " reconfigure DEVICES... Reconfigure interfaces\n" - " reload Reload .network and .netdev files\n" - " edit FILES|DEVICES... Edit network configuration files\n" - " cat [FILES|DEVICES...] Show network configuration files\n" - " mask FILES... Mask network configuration files\n" - " unmask FILES... Unmask network configuration files\n" - " persistent-storage BOOL\n" - " Notify systemd-networkd if persistent storage is ready\n" - "\nOptions:\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 prompt for password\n" - " -a --all Show status for all links\n" - " -s --stats Show detailed link statistics\n" - " -l --full Do not ellipsize output\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --no-reload Do not reload systemd-networkd or systemd-udevd\n" - " after editing network config\n" - " --drop-in=NAME Edit specified drop-in instead of main config file\n" - " --runtime Edit runtime config files\n" - " --stdin Read new contents of edited file from stdin\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + help_man_page_reference("networkctl", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_JSON, - ARG_NO_RELOAD, - ARG_DROP_IN, - ARG_RUNTIME, - ARG_STDIN, - }; - - 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 }, - { "all", no_argument, NULL, 'a' }, - { "stats", no_argument, NULL, 's' }, - { "full", no_argument, NULL, 'l' }, - { "lines", required_argument, NULL, 'n' }, - { "json", required_argument, NULL, ARG_JSON }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "drop-in", required_argument, NULL, ARG_DROP_IN }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "stdin", no_argument, NULL, ARG_STDIN }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hasln:", options, NULL)) >= 0) { + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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_RELOAD: - arg_no_reload = true; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION('a', "all", NULL, "Show status for all links"): + arg_all = true; break; - case ARG_RUNTIME: - arg_runtime = true; + OPTION('s', "stats", NULL, "Show detailed link statistics"): + arg_stats = true; break; - case ARG_STDIN: - arg_stdin = true; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; + break; + + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", opts.arg); break; - case ARG_DROP_IN: - if (isempty(optarg)) + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_LONG("no-reload", NULL, + "Do not reload systemd-networkd or systemd-udevd after editing network config"): + arg_no_reload = true; + break; + + OPTION_LONG("drop-in", "NAME", + "Edit specified drop-in instead of main config file"): + if (isempty(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name."); - if (!endswith(optarg, ".conf")) { + if (!endswith(opts.arg, ".conf")) { char *conf; - conf = strjoin(optarg, ".conf"); + conf = strjoin(opts.arg, ".conf"); if (!conf) return log_oom(); free_and_replace(arg_drop_in, conf); } else { - r = free_and_strdup(&arg_drop_in, optarg); + r = free_and_strdup(&arg_drop_in, opts.arg); if (r < 0) return log_oom(); } @@ -186,77 +188,32 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'a': - arg_all = true; - break; - - case 's': - arg_stats = true; - break; - - case 'l': - arg_full = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + OPTION_LONG("runtime", NULL, "Edit runtime config files"): + arg_runtime = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; + OPTION_LONG("stdin", NULL, "Read new contents of edited file from stdin"): + arg_stdin = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + *remaining_args = option_parser_get_args(&opts); return 1; } -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_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 }, - {} - }; - - 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; journal_browse_prepare(); - return networkctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 203409bb9ffe78aae0c2a0a71eb4e910484a47c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 09:20:02 +0200 Subject: [PATCH 1453/2155] networkctl: use proper errno in mesage $ build/networkctl --lines=4883284838483883838383 Failed to parse --lines setting '4883284838483883838383': Numerical result out of range --- src/network/networkctl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index e77950bbe8c4f..b64c8b17fc7d0 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -147,9 +147,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): - if (safe_atou(opts.arg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", opts.arg); + r = safe_atou(opts.arg, &arg_lines); + if (r < 0) + return log_error_errno(r, "Failed to parse --lines value '%s': %m", opts.arg); break; OPTION_COMMON_JSON: From 83436d4b7e9e2514bb32b89765ce33ad57f53224 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 13:55:00 +0100 Subject: [PATCH 1454/2155] scsi_id: fix memory leak of option_get_synopsis() return value option_get_synopsis() returns a heap-allocated string. Capture it in a _cleanup_free_ variable so it is freed after being used in the log message. CID#1657828 Follow-up for 05fea7df1bd6579dc382455626e0e84acb2a8912 --- src/udev/scsi_id/scsi_id.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 295819351d19b..93b65816e497e 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -328,10 +328,13 @@ static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum r = parse_page_code(opts.arg, page_code); if (r < 0) return r; - } else + } else { + _cleanup_free_ char *synopsis = + option_get_synopsis(opts.opt, "/", /* show_metavar=*/ false); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option %s not supported in the config file.", - strnull(option_get_synopsis(opts.opt, "/", /* show_metavar=*/ false))); + strnull(synopsis)); + } return 0; } From ea07d7fec36d738dfd9f03bd6dce58051e58e739 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 13:12:44 +0200 Subject: [PATCH 1455/2155] bootctl: allow extra files on 'link' be specified as literal data --- src/bootctl/bootctl-link.c | 49 ++++++++++++++------- src/shared/varlink-io.systemd.BootControl.c | 6 ++- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/bootctl/bootctl-link.c b/src/bootctl/bootctl-link.c index 6358189e7d2f3..b8372752b5649 100644 --- a/src/bootctl/bootctl-link.c +++ b/src/bootctl/bootctl-link.c @@ -25,6 +25,7 @@ #include "hashmap.h" #include "id128-util.h" #include "io-util.h" +#include "iovec-util.h" #include "json-util.h" #include "kernel-image.h" #include "log.h" @@ -44,6 +45,7 @@ typedef struct ExtraFile { /* The source and the temporary file we copy it into */ int source_fd, temp_fd; char *filename, *temp_filename; + struct iovec data; /* Alternative to 'source_fd': literal data */ } ExtraFile; #define EXTRA_FILE_NULL \ @@ -115,6 +117,7 @@ static void extra_file_done(ExtraFile *x) { x->temp_fd = safe_close(x->temp_fd); x->filename = mfree(x->filename); x->temp_filename = mfree(x->temp_filename); + iovec_done(&x->data); } static void profile_done(Profile *p) { @@ -366,7 +369,8 @@ static int link_context_pick_entry_token(LinkContext *c) { } static int begin_copy_file( - int source_fd, + int source_fd, /* Either the source fd is specified, or the 'data' below, not both */ + const struct iovec *data, const char *filename, int target_dir_fd, int *ret_tmpfile_fd, @@ -374,7 +378,6 @@ static int begin_copy_file( int r; - assert(source_fd >= 0); assert(filename); assert(target_dir_fd >= 0); assert(ret_tmpfile_fd); @@ -398,11 +401,18 @@ static int begin_copy_file( CLEANUP_TMPFILE_AT(target_dir_fd, t); - 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 into '%s': %m", filename); + if (source_fd >= 0) { + 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 into '%s': %m", filename); + + (void) copy_times(source_fd, write_fd, /* flags= */ 0); + } else if (iovec_is_set(data)) { + r = loop_write(write_fd, data->iov_base, data->iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data into '%s': %m", filename); + } - (void) copy_times(source_fd, write_fd, /* flags= */ 0); (void) fchmod(write_fd, 0644); *ret_tmpfile_fd = TAKE_FD(write_fd); @@ -824,6 +834,7 @@ static int run_link_now(LinkContext *c) { r = begin_copy_file( c->kernel_fd, + /* data= */ NULL, c->kernel_filename, c->entry_token_dir_fd, &c->kernel_temp_fd, @@ -834,6 +845,7 @@ static int run_link_now(LinkContext *c) { FOREACH_ARRAY(x, c->extra, c->n_extra) { r = begin_copy_file( x->source_fd, + &x->data, x->filename, c->entry_token_dir_fd, &x->temp_fd, @@ -1043,7 +1055,8 @@ static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatc static const sd_json_dispatch_field dispatch_table[] = { { "filename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename), SD_JSON_MANDATORY }, - { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), SD_JSON_MANDATORY }, + { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), 0 }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(ExtraParameters, extra_file.data), 0 }, {}, }; @@ -1051,17 +1064,21 @@ static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatc if (r < 0) return r; - xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index); - if (xp.extra_file.source_fd < 0) - return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m"); - - r = fd_verify_safe_flags(xp.extra_file.source_fd); - if (r < 0) + if (iovec_is_set(&xp.extra_file.data) == (xp.fd_index != UINT_MAX)) return sd_varlink_error_invalid_parameter_name(c->link, name); + if (xp.fd_index != UINT_MAX) { + xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index); + if (xp.extra_file.source_fd < 0) + return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m"); - r = fd_verify_regular(xp.extra_file.source_fd); - if (r < 0) - return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m"); + r = fd_verify_safe_flags(xp.extra_file.source_fd); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(c->link, name); + + r = fd_verify_regular(xp.extra_file.source_fd); + if (r < 0) + return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m"); + } if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1)) return log_oom(); diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index 920b9479db0a4..62306f5d79377 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -153,8 +153,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( BootEntryExtraFile, SD_VARLINK_FIELD_COMMENT("The name of the extra file"), SD_VARLINK_DEFINE_FIELD(filename, SD_VARLINK_STRING, 0), - SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file."), - SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, 0)); + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file to copy in. Either this or the 'data' field below must be set – not both, not neither."), + SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Literal data to place in the extra file."), + SD_VARLINK_DEFINE_FIELD(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Link, From 5fbc7a7b126d37e1179e5651ae72d831d92eb464 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Aug 2025 11:51:11 +0200 Subject: [PATCH 1456/2155] sysinstall: new component --- man/rules/meson.build | 4 + man/systemd-sysinstall.xml | 292 +++++ meson.build | 3 + meson_options.txt | 2 + shell-completion/bash/meson.build | 1 + shell-completion/bash/systemd-sysinstall | 90 ++ shell-completion/zsh/_systemd-sysinstall | 29 + shell-completion/zsh/meson.build | 1 + src/basic/time-util.c | 10 + src/basic/time-util.h | 1 + src/sysinstall/meson.build | 10 + src/sysinstall/sysinstall.c | 1425 ++++++++++++++++++++++ units/meson.build | 6 + units/system-install.target | 15 + units/systemd-sysinstall.service | 22 + 15 files changed, 1911 insertions(+) create mode 100644 man/systemd-sysinstall.xml create mode 100644 shell-completion/bash/systemd-sysinstall create mode 100644 shell-completion/zsh/_systemd-sysinstall create mode 100644 src/sysinstall/meson.build create mode 100644 src/sysinstall/sysinstall.c create mode 100644 units/system-install.target create mode 100644 units/systemd-sysinstall.service diff --git a/man/rules/meson.build b/man/rules/meson.build index 719838064c02f..c42a8d47f8e27 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1218,6 +1218,10 @@ manpages = [ 'systemd-sysext-sysroot.service', 'systemd-sysext.service'], 'ENABLE_SYSEXT'], + ['systemd-sysinstall', + '8', + ['systemd-sysinstall.service'], + 'ENABLE_SYSINSTALL'], ['systemd-system-update-generator', '8', [], ''], ['systemd-system.conf', '5', diff --git a/man/systemd-sysinstall.xml b/man/systemd-sysinstall.xml new file mode 100644 index 0000000000000..228e7e2bff17f --- /dev/null +++ b/man/systemd-sysinstall.xml @@ -0,0 +1,292 @@ + + + + + + + + systemd-sysinstall + systemd + + + + systemd-sysinstall + 8 + + + + systemd-sysinstall + systemd-sysinstall.service + Simple OS installer + + + + + systemd-sysinstall + OPTIONS + BLOCKDEVICE + + + systemd-sysinstall.service + + + + Description + + systemd-sysinstall is a simple terminal and command line based operating system + installer tool. Its primary use-case is to act as an automatically started interactive interface when + booting from an installer medium (e.g. a USB stick), in order to install an OS onto a target + disk. However, it may also be invoked directly from a shell. It executes the following steps: + + + It prompts the user for the target disk to install the OS on. (Unless the block device + is already specified on the command line.) + + It validates whether the disk is suitable (i.e. large enough, and with enough + free/unpartitioned space) for an OS installation. If it is generally suitable the user is prompted if they + want to erase the disk before installation, or if the OS shall be added to the existing partitions on + the disk (the latter only if enough free/unpartitioned disk space is available). + + It prompts the user whether to register the newly installed OS with the firmware boot option menu. + + It requests confirmation from the user, after showing a summary of the planned OS installation. + + It invokes + systemd-creds1's + encrypt command in order to generate encrypted (TPM locked, if available) system + credential files for a few, very basic system settings of the currently booted system (locale, keymap, + timezone), which it will install on the target disk, parameterizing the invoked kernel. (Or in other + words, it prepares that some settings already in effect on the installer system are propagated securely + onto the new installation.) + + It invokes + systemd-repart8 with + a definitions directory of /usr/lib/repart.sysinstall.d/ (only if populated – if + not will use the default of /usr/lib/repart.d/). This is supposed to set up the + basic OS partition structure on the target disk and copies in basic OS partitions (most importantly the + /usr/ hierarchy). + + It invokes + bootctl1's + link command to install an OS kernel image onto the target disk's ESP/XBOOTLDR, + together with the credential files prepared earlier. + + It invokes + bootctl1's + install command to install the + systemd-boot7 boot + loader onto the target disk's ESP. + + After confirmation, it reboots the system. + + + Note that the prompts/confirmation may be disabled via the command line, enabling fully automatic, + non-interactive installation. See below. + + Note this tool does not interactively query the user for a user to create or a root password to be + set on the target system, under the assumption these questions are better prompted from within the newly + installed system's first boot process, for example via the + systemd-firstboot1 or + systemd-homed-firstboot.service components. Note that if required such settings + may be propagated explicitly via the switch below. + + + + Options + + The following options are understood: + + + + + + + Overrides the directory where systemd-repart shall read its + partition definitions from, in place of the default of + /usr/lib/repart.sysinstall.d/. + + + + + + + + Takes a boolean argument. Controls whether to show the brief welcome text normally + displayed at the beginning of the installation. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to show the colored bars at the top and + bottom of the terminal interface. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to erase the current contents of the + target disk. If this switch is not used the user is prompted. + + + + + + + + Takes a boolean argument. Controls whether to interactively query the user for + confirmation before initiating the OS installation. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to reboot the system after completing the + installation. Defaults to false. + + + + + + + + Takes a boolean argument. Controls whether to register the installed boot loader in + the firmware's boot options database. If not specified the user will be prompted. + + + + + + + + Takes a boolean argument. Controls whether to show a summary of the choices made + before asking for confirmation to proceed with the OS installation. Defaults to true. + + + + + + + + Takes a path to a unified kernel image (UKI). Explicitly selects the kernel image to + install on the target disk. If unspecified the currently booted kernel image is installed on the + target disk. + + + + + + + + Accepts an additional system credential to encrypt (with a key generated on the local + TPM, if available, and the null key otherwise) and place next to the installed kernel image in the + ESP. This may be used to parameterize the installed kernel with arbitrary system credentials. Do not + use this switch for sensitive data (such as passwords), use + instead, see below. May be used multiple times to configure multiple credentials. + + Note that three system credentials are propagated in similar fashion to the target system: + the locale, keymap and timezone. This may be controlled by the relevant + , and + options below. + + See + systemd.system-credentials7 + for a list of well-known system credentials that may be propagated this way. (Note that you may pass + arbitrary additional credentials this way, that can be consumed by any service of your + choice, via the usual system credentials logic.) + + + + + + + + Similar to but reads the credential value from a + file on disk or an AF_UNIX socket in the file system. This is generally + preferable for sensitive data, such as passwords. + + + + + + + + + + These options take boolean parameters. They control whether the indicated system + settings shall be propagated from the currently running system into the new target OS + installation. These options default to true. + + Typically, these three settings are the minimal settings that need to be configured during early + boot of an installer medium in order to make the installer tool accessible to the user. The + systemd-firstboot1 + tool may be used to query the user interactively when the OS install medium is booted for these + properties. By propagating these settings to the target installation via system credentials they do + not need to be queried again on first boot of the new installation. + + + + + + + + Takes a boolean argument. Controls whether to disable kernel and service manager log + output to the console the installer is invoked on temporarily while running, in order to avoid + interleaved output. Defaults to false. + + + + + + + + + + + Exit status + + On success, 0 is returned, and a non-zero failure code otherwise. + + + + Example + + + Invoke the tool for a fully automatic non-interactive OS installation + + systemd-sysinstall \ + /dev/disk/by-id/nvme-Micron_MTFDKBA1T0TFH_214532D0CDA5 \ + --erase=yes \ + --confirm=no \ + --variables=yes \ + --load-credential=ssh.authorized_keys.root:my-ssh-key + + + This installs the OS on the selected disk, erasing any previous contents, without confirmation, + registers it in the firmware, and drops in the SSH key for the root user, read from the + my-ssh-key file in the current directory. + + + + + See Also + + systemd1 + systemd-creds1 + systemd-repart8 + bootctl1 + systemd-firstboot1 + systemd-boot7 + systemd.system-credentials7 + + + + diff --git a/meson.build b/meson.build index 325b954a78b24..d6fbd7c2b7ea6 100644 --- a/meson.build +++ b/meson.build @@ -1582,6 +1582,7 @@ foreach tuple : [ ['rfkill'], ['smack'], ['sysext'], + ['sysinstall'], ['sysusers'], ['timedated'], ['timesyncd'], @@ -2144,6 +2145,7 @@ subdir('src/storagetm') subdir('src/sulogin-shell') subdir('src/sysctl') subdir('src/sysext') +subdir('src/sysinstall') subdir('src/system-update-generator') subdir('src/systemctl') subdir('src/sysupdate') @@ -2928,6 +2930,7 @@ foreach tuple : [ ['resolve'], ['rfkill'], ['sysext'], + ['sysinstall'], ['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1], ['sysupdate'], ['sysupdated'], diff --git a/meson_options.txt b/meson_options.txt index d61afac519d84..1917268d2ce4d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -109,6 +109,8 @@ option('sysupdate', type : 'feature', deprecated : { 'true' : 'enabled', 'false' option('sysupdated', type: 'combo', value : 'auto', choices : ['auto', 'enabled', 'disabled'], description : 'install the systemd-sysupdated service') +option('sysinstall', type : 'boolean', + description : 'install the systemd-sysinstall tool') option('coredump', type : 'boolean', description : 'install the coredump handler') diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index b0e56608e8f37..cddf742059d51 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -54,6 +54,7 @@ foreach item : [ ['systemd-resolve', 'ENABLE_RESOLVE'], ['systemd-run', ''], ['systemd-sysext', 'ENABLE_SYSEXT'], + ['systemd-sysinstall', 'ENABLE_SYSINSTALL'], ['systemd-vmspawn', 'ENABLE_VMSPAWN'], ['systemd-vpick', ''], ['timedatectl', 'ENABLE_TIMEDATED'], diff --git a/shell-completion/bash/systemd-sysinstall b/shell-completion/bash/systemd-sysinstall new file mode 100644 index 0000000000000..600e2aa939542 --- /dev/null +++ b/shell-completion/bash/systemd-sysinstall @@ -0,0 +1,90 @@ +# shellcheck shell=bash +# systemd-sysinstall(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 +} + +__get_block_devices() { + systemd-repart --list-devices 2>/dev/null +} + +_systemd_sysinstall() { + local comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword + local -A OPTS=( + [STANDALONE]='-h --help --version' + [ARG]='--welcome + --chrome + --erase + --confirm + --summary + --reboot + --variables + --mute-console + --copy-locale + --copy-keymap + --copy-timezone + --definitions + --kernel + --set-credential + --load-credential' + ) + + _init_completion || return + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --welcome|--chrome|--confirm|--summary|--reboot|--mute-console|--copy-locale|--copy-keymap|--copy-timezone) + comps='yes no' + ;; + --erase|--variables) + comps='yes no auto' + ;; + --definitions) + comps=$(compgen -A directory -- "$cur") + compopt -o filenames + ;; + --kernel|--load-credential) + comps=$(compgen -A file -- "$cur") + compopt -o filenames + ;; + --set-credential) + comps='' + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + comps=$(__get_block_devices) + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + compopt -o filenames + return 0 +} + +complete -F _systemd_sysinstall systemd-sysinstall diff --git a/shell-completion/zsh/_systemd-sysinstall b/shell-completion/zsh/_systemd-sysinstall new file mode 100644 index 0000000000000..039e4bf2a7912 --- /dev/null +++ b/shell-completion/zsh/_systemd-sysinstall @@ -0,0 +1,29 @@ +#compdef systemd-sysinstall +# SPDX-License-Identifier: LGPL-2.1-or-later + +(( $+functions[_systemd-sysinstall_devices] )) || +_systemd-sysinstall_devices() { + local -a _devices + _devices=( ${(f)"$(systemd-repart --list-devices 2>/dev/null)"} ) + _wanted devices expl 'block device' compadd -a _devices +} + +_arguments \ + '(- *)'{-h,--help}'[Show help text]' \ + '(- *)--version[Show package version]' \ + '--welcome=[Show welcome text]:boolean:(yes no)' \ + '--chrome=[Show colored bars at top and bottom of the terminal]:boolean:(yes no)' \ + '--erase=[Erase target disk before installation]:boolean:(yes no auto)' \ + '--confirm=[Query for confirmation before installation]:boolean:(yes no)' \ + '--summary=[Show summary before installation]:boolean:(yes no)' \ + '--reboot=[Reboot system after installation]:boolean:(yes no)' \ + '--variables=[Register installation in firmware variables]:boolean:(yes no auto)' \ + '--mute-console=[Mute kernel/PID 1 console output during installation]:boolean:(yes no)' \ + '--copy-locale=[Copy current locale to target system]:boolean:(yes no)' \ + '--copy-keymap=[Copy current keymap to target system]:boolean:(yes no)' \ + '--copy-timezone=[Copy current timezone to target system]:boolean:(yes no)' \ + '--definitions=[Find partition definitions in directory]:directory:_directories' \ + '--kernel=[Kernel image to install]:kernel image:_files' \ + '--set-credential=[Install a credential with a literal value]: : _message "ID:VALUE"' \ + '--load-credential=[Load credential from a file or AF_UNIX socket]: : _message "ID:PATH"' \ + '*::block device:_systemd-sysinstall_devices' diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index 6cc8a2d57f83e..f10ba7be617cc 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -43,6 +43,7 @@ foreach item : [ ['_systemd-nspawn', ''], ['_systemd-path', ''], ['_systemd-run', ''], + ['_systemd-sysinstall', 'ENABLE_SYSINSTALL'], ['_systemd-tmpfiles', 'ENABLE_TMPFILES'], ['_timedatectl', 'ENABLE_TIMEDATED'], ['_udevadm', ''], diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 78c33c7553ce6..eb74de32c2db8 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1697,6 +1697,16 @@ int get_timezone(char **ret) { return strdup_to(ret, e); } +int get_timezone_prefer_env(char **ret) { + assert(ret); + + const char *e = getenv("TZ"); + if (e && e[0] == ':' && timezone_is_valid(e + 1, LOG_DEBUG)) + return strdup_to(ret, e + 1); + + return get_timezone(ret); +} + const char* etc_localtime(void) { static const char *cached = NULL; diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 9a66a90859d67..fdaf11edcbf6c 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -178,6 +178,7 @@ bool clock_supported(clockid_t clock); usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to); int get_timezone(char **ret); +int get_timezone_prefer_env(char **ret); const char* etc_localtime(void); int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret); diff --git a/src/sysinstall/meson.build b/src/sysinstall/meson.build new file mode 100644 index 0000000000000..1d8be6036564b --- /dev/null +++ b/src/sysinstall/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-sysinstall', + 'public' : true, + 'conditions' : ['ENABLE_SYSINSTALL'], + 'sources' : files('sysinstall.c'), + }, +] diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c new file mode 100644 index 0000000000000..d8f5cbee3c93f --- /dev/null +++ b/src/sysinstall/sysinstall.c @@ -0,0 +1,1425 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "ansi-color.h" +#include "blockdev-list.h" +#include "build.h" +#include "build-path.h" +#include "chase.h" +#include "conf-files.h" +#include "constants.h" +#include "efi-loader.h" +#include "efivars.h" +#include "env-file.h" +#include "escape.h" +#include "fd-util.h" +#include "find-esp.h" +#include "format-table.h" +#include "format-util.h" +#include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" +#include "image-policy.h" +#include "json-util.h" +#include "locale-setup.h" +#include "log.h" +#include "loop-util.h" +#include "machine-credential.h" +#include "main-func.h" +#include "mount-util.h" +#include "options.h" +#include "os-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "prompt-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "varlink-util.h" + +static char *arg_node = NULL; +static bool arg_welcome = true; +static int arg_erase = -1; /* tri-state */ +static bool arg_confirm = true; +static bool arg_summary = true; +static char **arg_definitions = NULL; +static char *arg_kernel_image = NULL; +static bool arg_reboot = false; +static int arg_touch_variables = -1; /* tri-state */ +static MachineCredentialContext arg_credentials = {}; +static bool arg_copy_locale = true; +static bool arg_copy_keymap = true; +static bool arg_copy_timezone = true; +static bool arg_chrome = true; +static bool arg_mute_console = false; + +STATIC_DESTRUCTOR_REGISTER(arg_node, freep); +STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); + +static int help(void) { + int r; + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] [DEVICE]"); + help_abstract("Installs the OS to another block device."); + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-sysinstall", "8"); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("welcome", "no", "Disable the welcome text"): + r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome); + if (r < 0) + return r; + + break; + + OPTION_LONG("erase", "BOOL", "Whether to erase the target disk"): + r = parse_tristate_argument_with_auto("--erase=", opts.arg, &arg_erase); + if (r < 0) + return r; + break; + + OPTION_LONG("confirm", "no", "Disable query for confirmation"): + r = parse_boolean_argument("--confirm=", opts.arg, &arg_confirm); + if (r < 0) + return r; + break; + + OPTION_LONG("summary", "no", "Disable summary before beginning operation"): + r = parse_boolean_argument("--summary=", opts.arg, &arg_summary); + 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(opts.arg, /* suppress_root= */ false, &path); + if (r < 0) + return r; + if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) + return log_oom(); + break; + } + + OPTION_LONG("reboot", "BOOL", "Whether to reboot after installation is complete"): + r = parse_boolean_argument("--reboot=", opts.arg, &arg_reboot); + if (r < 0) + return r; + break; + + OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"): + r = parse_tristate_argument_with_auto("--variables=", opts.arg, &arg_touch_variables); + if (r < 0) + return r; + break; + + OPTION_LONG("kernel", "IMAGE", "Explicitly pick kernel image to install"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_kernel_image); + if (r < 0) + return r; + break; + + OPTION_LONG("set-credential", "ID:VALUE", "Install a credential with literal value to target system"): + r = machine_credential_set(&arg_credentials, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("load-credential", "ID:PATH", "Load a credential to install to new system from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, opts.arg); + if (r < 0) + return r; + + break; + + OPTION_LONG("copy-locale", "no", "Don't copy current locale to target system"): + r = parse_boolean_argument("--copy-locale=", opts.arg, &arg_copy_locale); + if (r < 0) + return r; + break; + + OPTION_LONG("copy-keymap", "no", "Don't copy current keymap to target system"): + r = parse_boolean_argument("--copy-keymap=", opts.arg, &arg_copy_keymap); + if (r < 0) + return r; + break; + + OPTION_LONG("copy-timezone", "no", "Don't copy current timezone to target system"): + r = parse_boolean_argument("--copy-timezone=", opts.arg, &arg_copy_timezone); + if (r < 0) + return r; + break; + + OPTION_LONG("chrome", "no", "Whether to show a color bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome); + if (r < 0) + return r; + + break; + + OPTION_LONG("mute-console", "BOOL", "Whether to disallow kernel/PID 1 writes to the console while running"): + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); + if (r < 0) + return r; + break; + } + + char **args = option_parser_get_args(&opts); + + if (strv_length(args) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (!strv_isempty(args)) { + arg_node = strdup(args[0]); + if (!arg_node) + return log_oom(); + } + + return 1; +} + +static int print_welcome(sd_varlink **mute_console_link) { + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; + const char *pn, *ac; + int r; + + assert(mute_console_link); + + if (!*mute_console_link && arg_mute_console) + (void) mute_console(mute_console_link); + + if (!arg_welcome) + return 0; + + r = parse_os_release( + /* root= */ NULL, + "PRETTY_NAME", &pretty_name, + "NAME", &os_name, + "ANSI_COLOR", &ansi_color); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + + pn = os_release_pretty_name(pretty_name, os_name); + ac = isempty(ansi_color) ? "0" : ansi_color; + + if (colors_enabled()) + printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", ac, pn); + else + printf("Welcome to the %s Installer!\n", pn); + + putchar('\n'); + + return 0; +} + +static int connect_to_repart(sd_varlink **link) { + int r; + + assert(link); + + if (*link) { + /* Reset the time-out to default here, since we are reusing the connection, but might enqueue + * a different operation */ + r = sd_varlink_set_relative_timeout(*link, 0); + if (r < 0) + return r; + + return 0; + } + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *repart = NULL; + fd = pin_callout_binary("systemd-repart", &repart); + if (fd < 0) + return log_error_errno(fd, "Failed to find systemd-repart binary: %m"); + + r = sd_varlink_connect_exec(link, repart, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-repart: %m"); + + return 1; +} + +static int acquire_device_list( + sd_varlink **link, + char ***ret_menu, + char ***ret_accepted) { + int r; + + r = connect_to_repart(link); + if (r < 0) + return r; + + _cleanup_strv_free_ char **menu = NULL, **accepted = NULL; + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_collectbo( + *link, + "io.systemd.Repart.ListCandidateDevices", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true)); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.Repart.NoCandidateDevices")) + log_debug("No candidate devices found."); + else if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m"); + + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %s", error_id); + } else { + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, reply) { + _cleanup_(block_device_done) BlockDevice bd = BLOCK_DEVICE_NULL; + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(BlockDevice, node), SD_JSON_MANDATORY }, + { "symlinks", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(BlockDevice, symlinks), 0 }, + {} + }; + + r = sd_json_dispatch(i, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &bd); + if (r < 0) + return r; + + if (strv_extend(&accepted, bd.node) < 0) + return log_oom(); + if (strv_extend_strv(&accepted, bd.symlinks, /* filter_duplicates= */ true) < 0) + return log_oom(); + + /* Prefer the by-id and by-loop-ref because they typically contain the strings most + * directly understood by the user */ + const char *n = strv_find_prefix(bd.symlinks, "/dev/disk/by-id/"); + if (!n) + n = strv_find_prefix(bd.symlinks, "/dev/disk/by-loop-ref/"); + if (!n) + n = bd.node; + + if (strv_extend(&menu, n) < 0) + return log_oom(); + } + } + + *ret_menu = TAKE_PTR(menu); + *ret_accepted = TAKE_PTR(accepted); + return 0; +} + +static int device_is_valid(const char *node, void *userdata) { + + if (!path_is_valid(node) || !path_is_absolute(node)) { + log_error("Not a valid absolute file system path, refusing: %s", node); + return false; + } + + struct stat st; + if (stat(node, &st) < 0) { + log_error_errno(errno, "Failed to check if '%s' is a valid block device node: %m", node); + return false; + } + if (!S_ISBLK(st.st_mode)) { + log_error("Path '%s' does not refer to a valid block device node, refusing.", node); + return false; + } + + return true; +} + +static int refresh_devices(char ***ret_menu, char ***ret_accepted, void *userdata) { + sd_varlink **repart_link = ASSERT_PTR(userdata); + + (void) acquire_device_list(repart_link, ret_menu, ret_accepted); + return 0; +} + +static int prompt_block_device(sd_varlink **repart_link, char **ret_node) { + int r; + + putchar('\n'); + + _cleanup_strv_free_ char **menu = NULL, **accepted = NULL; + (void) acquire_device_list(repart_link, &menu, &accepted); + + r = prompt_loop("Please enter target disk device", + GLYPH_COMPUTER_DISK, + menu, + accepted, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 1, + /* column_width= */ 80, + device_is_valid, + refresh_devices, + /* userdata= */ repart_link, + PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_MAY_SKIP|PROMPT_HIDE_SKIP_HINT|PROMPT_HIDE_MENU_HINT, + ret_node); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + return 0; +} + +static int read_space_metrics( + sd_json_variant *v, + uint64_t *min_size, + uint64_t *current_size, + uint64_t *need_free) { + + int r; + + struct { + uint64_t min_size; + uint64_t current_size; + uint64_t need_free; + } p = { + .min_size = UINT64_MAX, + .current_size = UINT64_MAX, + .need_free = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size), 0 }, + { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 }, + { "needFreeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free), 0 }, + {} + }; + + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (min_size) + *min_size = p.min_size; + if (current_size) + *current_size = p.current_size; + if (need_free) + *need_free = p.need_free; + + return 0; +} + +static int invoke_repart( + sd_varlink **link, + const char *node, + bool erase, + bool dry_run, + uint64_t *min_size, /* initialized both on success and error */ + uint64_t *current_size, /* ditto */ + uint64_t *need_free) { /* ditto */ + + int r; + + assert(link); + + /* Note, if dry_run is true, then ENOSPC, E2BIG, EHWPOISON will not be logged about beyond LOG_DEBUG, + * but all other errors will be */ + + r = connect_to_repart(link); + if (r < 0) { + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + return r; + } + + if (!dry_run) { + /* Seeding the partitions might be very slow, disable timeout */ + r = sd_varlink_set_relative_timeout(*link, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to disable IPC timeout: %m"); + } + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + *link, + "io.systemd.Repart.Run", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("node", node), + SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"), + SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run), + SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true)); + if (r < 0) { + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + } + if (error_id) { + if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) { + (void) read_space_metrics(reply, min_size, current_size, need_free); + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(ENOSPC), + "Not enough free space on disk, cannot install."); + } + if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) { + (void) read_space_metrics(reply, min_size, current_size, need_free); + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(E2BIG), + "Disk too small for installation, cannot install."); + } + + /* For all other errors reset the metrics */ + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + + if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(EHWPOISON), + "A conflicting disk label is already present on the target disk, cannot install unless disk is erased."); + + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id); + } + + (void) read_space_metrics(reply, min_size, current_size, need_free); + + return 0; +} + +static int prompt_erase( + bool can_add, + int *ret_erase) { + int r; + + assert(ret_erase); + + putchar('\n'); + + char **l = can_add ? STRV_MAKE("keep", "erase") : STRV_MAKE("erase"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop(can_add ? + "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" : + "Please type 'erase' to confirm that all data on the disk shall be erased", + GLYPH_BROOM, + /* menu= */ l, + /* accepted= */ l, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + if (streq(reply, "erase")) + *ret_erase = true; + else if (streq(reply, "keep")) + *ret_erase = false; + else + assert_not_reached(); + + return 0; +} + +static int prompt_touch_variables(void) { + int r; + + if (arg_touch_variables >= 0) + return 0; + + putchar('\n'); + + char **l = STRV_MAKE("yes", "no"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise", + GLYPH_ROCKET, + /* menu= */ l, + /* accepted= */ l, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + r = parse_boolean(reply); + if (r < 0) + return log_error_errno(r, "Failed to parse reply: %s", reply); + + arg_touch_variables = r; + + return 0; +} + +static int prompt_confirm(void) { + int r; + + if (!arg_confirm) + return 0; + + putchar('\n'); + + char **l = STRV_MAKE("yes", "no"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" : + "Please type 'yes' to begin the installation", + GLYPH_WARNING_SIGN, + /* menu= */ l, + /* accepted= */ l, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + if (!streq(reply, "yes")) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling."); + + return 0; +} + +static int validate_run(sd_varlink **repart_link, const char *node) { + int r; + + assert(repart_link); + assert(node); + + /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */ + bool try_erase = arg_erase > 0, conflicting_disk_label = false; + for (;;) { + uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX; + r = invoke_repart( + repart_link, + node, + try_erase, + /* dry_run= */ true, + &min_size, + ¤t_size, + &need_free); + if (r == -ENOSPC) { + /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */ + if (try_erase || arg_erase >= 0) + return log_error_errno(r, "The selected disk is big enough for the installation but does not have enough free space."); + + log_notice("The selected disk is big enough for the installation but does not have enough free space. Installation will require erasing."); + if (need_free != UINT64_MAX) + log_info("Required free space is %s.", FORMAT_BYTES(need_free)); + + try_erase = true; + } else if (r == -E2BIG) { + /* Won't fit, whatever we do */ + log_error_errno(r, "The selected disk is not large enough for an OS installation."); + if (current_size != UINT64_MAX) + log_info("The size of the selected disk is %s, but a minimal size of %s is required.", + FORMAT_BYTES(current_size), + FORMAT_BYTES(min_size)); + return r; + } else if (r == -EHWPOISON) { + if (try_erase || arg_erase >= 0) + return log_error_errno(r, "The selected disk contains a conflicting disk label, refusing."); + + log_debug("Disk contains a conflicting disk label, checking if we could install the OS after erasing it."); + try_erase = true; + conflicting_disk_label = true; + continue; + } else if (r < 0) + /* invoke_repart() already logged about all other errors */ + return r; + else + /* Nice, we can add the OS to the disk, without erasing anything. */ + log_info("The selected disk has enough free space for an installation of the OS."); + + if (conflicting_disk_label) + log_warning("A conflicting disk label has been found, and must be erased for installation."); + + if (arg_erase < 0) { + r = prompt_erase(/* can_add= */ !try_erase, &arg_erase); + if (r < 0) + return r; + } + + return 0; + } +} + +static int show_summary(void) { + int r; + + if (!arg_summary) + return 0; + + printf("\n" + "%sSummary:%s\n", ansi_underline(), ansi_normal()); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Selected Disk", + TABLE_STRING, arg_node, + TABLE_FIELD, "Erase Disk", + TABLE_BOOLEAN, arg_erase, + TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL, + TABLE_FIELD, "Register in Firmware", + TABLE_BOOLEAN, arg_touch_variables); + if (r < 0) + return table_log_add_error(r); + + static const char * const map[] = { + "firstboot.keymap", "Keyboard Map", + "firstboot.locale", "Locale", + "firstboot.locale-messages", "Locale (Messages)", + "firstboot.timezone", "Timezone", + NULL + }; + + STRV_FOREACH_PAIR(id, text, map) { + MachineCredential *c = machine_credential_find(&arg_credentials, *id); + if (!c) + continue; + + _cleanup_free_ char *escaped = cescape_length(c->data, c->size); + if (!escaped) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, *text, + TABLE_STRING, escaped); + if (r < 0) + return table_log_add_error(r); + } + + unsigned n_extra_credentials = 0; + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + bool covered = false; + + STRV_FOREACH_PAIR(id, text, map) + if (streq(*id, cred->id)) { + covered = true; + break; + } + + if (!covered) + n_extra_credentials++; + } + + if (n_extra_credentials > 0) { + r = table_add_many( + table, + TABLE_FIELD, "Extra Credentials", + TABLE_UINT, n_extra_credentials); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(table); + if (r < 0) + return r; + + return 0; +} + +static int find_current_kernel( + char **ret_filename, + int *ret_fd) { + + int r; + + sd_id128_t uuid; + r = efi_stub_get_device_part_uuid(&uuid); + if (r == -ENOENT) + return log_error_errno(r, "Cannot find current kernel, no stub partition UUID passed via EFI variables."); + if (r < 0) + return log_error_errno(r, "Unable to determine stub partition UUID: %m"); + + _cleanup_free_ char *image = NULL; + r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &image); + if (r == -ENOENT) + return log_error_errno(r, "Cannot find current kernel, no stub EFI binary path passed."); + if (r < 0) + return log_error_errno(r, "Unable to determine stub EFI binary path: %m"); + + /* Note: we search for the *host* ESP here (i.e. the one the current EFI paths relate to), not the + * one of the target image */ + + _cleanup_free_ char *partition_path = NULL; + _cleanup_close_ int partition_fd = -EBADF; + sd_id128_t partition_uuid; + r = find_esp_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &partition_path, + &partition_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &partition_uuid, + /* ret_devid= */ NULL); + if (r < 0 && r != -ENOKEY) + return r; + if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) { + partition_path = mfree(partition_path); + partition_fd = safe_close(partition_fd); + + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &partition_path, + &partition_fd, + &partition_uuid, + /* ret_devid= */ NULL); + if (r < 0 && r != -ENOKEY) + return r; + + if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Unable to find UKI on ESP/XBOOTLDR partitions."); + } + + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = chase_and_openat( + /* root_fd= */ partition_fd, + /* dir_fd= */ partition_fd, + image, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, + O_RDONLY|O_CLOEXEC, + &resolved); + if (fd < 0) + return log_error_errno(fd, "Failed to find EFI binary '%s' on partition '%s': %m", image, partition_path); + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(resolved, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract UKI file name from '%s': %m", resolved); + + if (ret_filename) + *ret_filename = TAKE_PTR(fn); + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 0; +} + +static int connect_to_bootctl(sd_varlink **link) { + int r; + + assert(link); + + if (*link) + return 0; + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *bootctl = NULL; + fd = pin_callout_binary("bootctl", &bootctl); + if (fd < 0) + return log_error_errno(fd, "Failed to find bootctl binary: %m"); + + r = sd_varlink_connect_exec(link, bootctl, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to bootctl: %m"); + + r = sd_varlink_set_allow_fd_passing_output(*link, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing to bootctl: %m"); + + return 1; +} + +static int invoke_bootctl_install( + sd_varlink **link, + const char *root_dir, + int root_fd) { + int r; + + assert(link); + assert(root_dir); + assert(root_fd >= 0); + + r = connect_to_bootctl(link); + if (r < 0) + return r; + + int fd_idx = sd_varlink_push_dup_fd(*link, root_fd); + if (fd_idx < 0) + return log_error_errno(fd_idx, "Failed to submit root fd onto Varlink connection: %m"); + + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.BootControl.Install", + /* reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_STRING("operation", "new"), + SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx), + SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir), + SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables)); + if (r < 0) + return r; + + return 0; +} + +static int invoke_bootctl_link( + sd_varlink **link, + const char *root_dir, + int root_fd, + char **encrypted_credentials) { + int r; + + assert(link); + assert(root_dir); + assert(root_fd >= 0); + + r = connect_to_bootctl(link); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + STRV_FOREACH_PAIR(name, value, encrypted_credentials) { + _cleanup_free_ char *j = strjoin(*name, ".cred"); + if (!j) + return log_oom(); + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_STRING("filename", j), + SD_JSON_BUILD_PAIR_BASE64("data", *value, strlen(*value))); + if (r < 0) + return log_error_errno(r, "Failed to append credential to message: %m"); + } + + int root_fd_idx = sd_varlink_push_dup_fd(*link, root_fd); + if (root_fd_idx < 0) + return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m"); + + _cleanup_free_ char *kernel_filename = NULL; + _cleanup_close_ int kernel_fd = -EBADF; + if (arg_kernel_image) { + r = path_extract_filename(arg_kernel_image, &kernel_filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image); + + kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (kernel_fd < 0) + return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image); + + } else { + r = find_current_kernel(&kernel_filename, &kernel_fd); + if (r < 0) + return r; + } + + int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd); + if (kernel_fd_idx < 0) + return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m"); + + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.BootControl.Link", + /* reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", root_fd_idx), + SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir), + JSON_BUILD_PAIR_STRING_NON_EMPTY("kernelFilename", kernel_filename), + SD_JSON_BUILD_PAIR_INTEGER("kernelFileDescriptor", kernel_fd_idx), + SD_JSON_BUILD_PAIR_CONDITION(!!array, "extraFiles", SD_JSON_BUILD_VARIANT(array))); + if (r < 0) + return r; + + return 0; +} + +static int maybe_reboot(void) { + int r; + + if (!arg_reboot) + return 0; + + log_notice("%s%sSystem will reboot now.", + emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : ""); + + if (!any_key_to_proceed()) + return 0; + + log_notice("%s%sInitiating reboot.", + emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Shutdown"); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-logind: %m"); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + link, + "io.systemd.Shutdown.Reboot", + &reply, + &error_id); + if (r < 0) + return r; + + return 0; +} + +static int read_credential_locale(void) { + int r; + + if (!arg_copy_locale) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.locale") || + machine_credential_find(&arg_credentials, "firstboot.locale-messages")) + return 0; + + /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since + * it seems to be one of the most fundamental ones, and is not LC_MESSAGES for which we have a + * separate setting after all */ + const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL); + if (l) { + r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + const char *m = setlocale(LC_MESSAGES, NULL); + if (m && !streq_ptr(m, l)) { + r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credential_keymap(void) { + int r; + + if (!arg_copy_keymap) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.keymap")) + return 0; + + _cleanup_free_ char *keymap = NULL; + r = parse_env_file( + /* f= */ NULL, + etc_vconsole_conf(), + "KEYMAP", &keymap); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf()); + + if (!isempty(keymap)) { + r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credential_timezone(void) { + int r; + + if (!arg_copy_timezone) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.timezone")) + return 0; + + _cleanup_free_ char *tz = NULL; + r = get_timezone_prefer_env(&tz); + if (r < 0) + log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m"); + else { + r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credentials(void) { + int r; + + r = read_credential_locale(); + if (r < 0) + return r; + + r = read_credential_keymap(); + if (r < 0) + return r; + + r = read_credential_timezone(); + if (r < 0) + return r; + + return 0; +} + +static int connect_to_creds(sd_varlink **link) { + int r; + + assert(link); + + if (*link) + return 0; + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *creds = NULL; + fd = pin_callout_binary("systemd-creds", &creds); + if (fd < 0) + return log_error_errno(fd, "Failed to find systemd-creds binary: %m"); + + r = sd_varlink_connect_exec(link, creds, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-creds: %m"); + + return 1; +} + +static int encrypt_one_credential(sd_varlink **link, const MachineCredential *input, char ***encrypted) { + int r; + + assert(link); + assert(input); + assert(encrypted); + + log_info("Encrypting credential '%s'...", input->id); + + r = connect_to_creds(link); + if (r < 0) + return r; + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.Credentials.Encrypt", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", input->id), + SD_JSON_BUILD_PAIR_BASE64("data", input->data, input->size), + SD_JSON_BUILD_PAIR_STRING("scope", "system"), + /* We pick the 'auto_initrd' key for this, since we want TPM if available, but are fine with NULL if not */ + SD_JSON_BUILD_PAIR_STRING("withKey", "auto_initrd")); + if (r < 0) + return r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "blob", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + + const char *blob = NULL; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &blob); + if (r < 0) + return r; + + r = strv_extend_many(encrypted, input->id, blob); + if (r < 0) + return r; + + return 0; +} + +static int encrypt_credentials(sd_varlink **link, char ***encrypted) { + int r; + + assert(link); + assert(encrypted); + + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + r = encrypt_one_credential(link, cred, encrypted); + if (r < 0) + return r; + } + + return 0; +} + +static const ImagePolicy image_policy = { + .n_policies = 4, + .policies = { + /* We mount / and /usr/ so that we can get access to /etc/machine-id and /etc/kernel/ */ + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + }, + .default_flags = PARTITION_POLICY_IGNORE, +}; + +static int settle_definitions(void) { + int r; + + if (arg_definitions) + return 0; + + /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition + * files */ + + _cleanup_strv_free_ char **files = NULL; + r = conf_files_list_strv( + &files, + ".conf", + /* root= */ NULL, + CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN|CONF_FILES_DONT_PREFIX_ROOT, + (const char**) CONF_PATHS_STRV("repart.sysinstall.d")); + if (r < 0) + return log_error_errno(r, "Failed to enumerate *.conf files: %m"); + + if (!strv_isempty(files)) { + arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d")); + if (!arg_definitions) + return log_oom(); + } + + return 0; +} + +static int run(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + log_setup(); + + r = settle_definitions(); + if (r < 0) + return r; + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + if (arg_welcome) { + if (arg_mute_console) + (void) mute_console(&mute_console_link); + + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); + + if (arg_chrome) + chrome_show("Operating System Installer", /* bottom= */ NULL); + } + + DEFER_VOID_CALL(chrome_hide); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL; + if (arg_node) { + r = print_welcome(&mute_console_link); + if (r < 0) + return r; + + r = validate_run(&repart_link, arg_node); + if (r < 0) + return r; + } else { + /* Determine the minimum disk size */ + uint64_t min_size = UINT64_MAX; + r = invoke_repart( + &repart_link, + /* node= */ NULL, + /* erase= */ true, + /* dry_run= */ true, + &min_size, + /* current_size= */ NULL, + /* need_free= */ NULL); + if (r < 0) + return r; + + r = print_welcome(&mute_console_link); + if (r < 0) + return r; + + log_info("Required minimal installation disk size is %s.", FORMAT_BYTES(min_size)); + + for (;;) { + _cleanup_free_ char *node = NULL; + r = prompt_block_device(&repart_link, &node); + if (r < 0) + return r; + + r = validate_run(&repart_link, node); + if (IN_SET(r, -ENOSPC, -E2BIG, -EHWPOISON)) /* Device is no fit, pick other */ + continue; + if (r < 0) + return r; + + arg_node = TAKE_PTR(node); + break; + } + } + + r = prompt_touch_variables(); + if (r < 0) + return r; + + r = read_credentials(); + if (r < 0) + return r; + + /* Verify we have everything we need */ + assert(arg_node); + assert(arg_erase >= 0); + assert(arg_touch_variables >= 0); + + r = show_summary(); + if (r < 0) + return r; + + r = prompt_confirm(); + if (r < 0) + return r; + + putchar('\n'); + + log_notice("%s%sEncrypting credentials...", + emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL; + _cleanup_strv_free_ char **encrypted_credentials = NULL; + r = encrypt_credentials(&creds_link, &encrypted_credentials); + if (r < 0) + return r; + + log_notice("%s%sInstalling partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + /* Do the main part of the installation */ + r = invoke_repart( + &repart_link, + arg_node, + arg_erase, + /* dry_run= */ false, + /* min_size= */ NULL, + /* current_size= */ NULL, + /* need_free= */ NULL); + if (r < 0) + return r; + + log_notice("%s%sMounting partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_freep) char *root_dir = NULL; + _cleanup_close_ int root_fd = -EBADF; + r = mount_image_privately_interactively( + arg_node, + &image_policy, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + DISSECT_IMAGE_DISCARD_ANY | + DISSECT_IMAGE_GPT_ONLY | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES, + &root_dir, + &root_fd, + &loop_device); + if (r < 0) + return log_error_errno(r, "Failed to mount new image: %m"); + + log_notice("%s%sInstalling kernel...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL; + r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, encrypted_credentials); + if (r < 0) + return r; + + log_notice("%s%sInstalling boot loader...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd); + if (r < 0) + return r; + + log_notice("%s%sUnmounting partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + root_fd = safe_close(root_fd); + r = umount_recursive(root_dir, /* flags= */ 0); + if (r < 0) + log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m"); + loop_device = loop_device_unref(loop_device); + sync(); + + log_notice("%s%sInstallation succeeded.", + emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : ""); + + r = maybe_reboot(); + if (r < 0) + return r; + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/units/meson.build b/units/meson.build index 0f7ce75bd8967..fca299aa8465a 100644 --- a/units/meson.build +++ b/units/meson.build @@ -240,6 +240,7 @@ units = [ }, { 'file' : 'sysinit.target' }, { 'file' : 'syslog.socket' }, + { 'file' : 'system-install.target' }, { 'file' : 'system-systemd\\x2dcryptsetup.slice', 'conditions' : ['HAVE_LIBCRYPTSETUP'], @@ -778,6 +779,11 @@ units = [ 'file' : 'systemd-sysext@.service', 'conditions' : ['ENABLE_SYSEXT'], }, + { + 'file' : 'systemd-sysinstall.service', + 'conditions' : ['ENABLE_SYSINSTALL'], + 'symlinks' : ['system-install.target.wants/'], + }, { 'file' : 'systemd-sysupdate-reboot.service.in', 'conditions' : ['ENABLE_SYSUPDATE'], diff --git a/units/system-install.target b/units/system-install.target new file mode 100644 index 0000000000000..660110dcea36a --- /dev/null +++ b/units/system-install.target @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=System Installer +Documentation=man:systemd-sysinstall(8) +Requires=sysinit.target +After=sysinit.target +AllowIsolate=yes diff --git a/units/systemd-sysinstall.service b/units/systemd-sysinstall.service new file mode 100644 index 0000000000000..a330db2ef3054 --- /dev/null +++ b/units/systemd-sysinstall.service @@ -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=System Install Tool +Documentation=man:systemd-sysinstall(8) +Wants=systemd-logind.service +After=systemd-logind.service + +[Service] +ExecStart=systemd-sysinstall --variables=yes --reboot=yes --mute-console=yes +StandardOutput=tty +StandardInput=tty +StandardError=tty +TTYReset=yes +FailureAction=halt From ca5b4f3f705ebceb136baea74dd45e625edb8dcc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Apr 2026 21:49:58 +0200 Subject: [PATCH 1457/2155] ci: add CI test for systemd-sysinstall --- test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh diff --git a/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh new file mode 100755 index 0000000000000..d4ca6e0a0d163 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v systemd-sysinstall >/dev/null; then + echo "systemd-sysinstall not found, skipping." + exit 0 +fi + +if ! command -v systemd-repart >/dev/null; then + echo "systemd-repart not found, skipping." + exit 0 +fi + +if ! command -v bootctl >/dev/null; then + echo "bootctl not found, skipping." + exit 0 +fi + +if ! command -v ukify >/dev/null; then + echo "ukify not found, skipping." + exit 0 +fi + +if [[ ! -d /usr/lib/systemd/boot/efi ]]; then + echo "sd-boot is not installed, skipping." + exit 0 +fi + +# We need a real environment to fiddle with loop devices. +if systemd-detect-virt -cq; then + echo "Running in a container, skipping." + exit 0 +fi + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)" +LOOPDEV="" +MOUNTED=0 + +cleanup() { + set +e + if [[ "$MOUNTED" -eq 1 ]]; then + umount -R "$WORKDIR/mnt" + MOUNTED=0 + fi + if [[ -n "$LOOPDEV" ]]; then + systemd-dissect --detach "$LOOPDEV" + LOOPDEV="" + fi + rm -rf "$WORKDIR" +} +trap cleanup EXIT + +# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via +# the repart.sysinstall.d definitions: CopyFiles= seeds the new root +# partition with these files. +SOURCE_ROOT="$WORKDIR/sourceroot" +mkdir -p "$SOURCE_ROOT/usr/lib" "$SOURCE_ROOT/etc" + +cat >"$SOURCE_ROOT/usr/lib/os-release" <<'EOF' +ID=testos +NAME="Test OS" +PRETTY_NAME="Test OS for systemd-sysinstall" +VERSION_ID=1 +EOF +ln -s ../usr/lib/os-release "$SOURCE_ROOT/etc/os-release" + +# 2) Build a minimal UKI. bootctl link only requires a valid PE with .osrel and +# the systemd-stub SBAT marker, so the .linux/.initrd contents do not need +# to be a real kernel. +echo "fake-kernel" >"$WORKDIR/vmlinuz" +echo "fake-initrd" >"$WORKDIR/initrd" + +ukify build \ + --linux "$WORKDIR/vmlinuz" \ + --initrd "$WORKDIR/initrd" \ + --os-release "@$SOURCE_ROOT/usr/lib/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet" \ + --output "$WORKDIR/testuki.efi" + +# 3) Build a sysinstall partition definition: a single ESP plus a root +# partition seeded from the fake source tree. +DEFS="$WORKDIR/sysinstall.d" +mkdir -p "$DEFS" + +cat >"$DEFS/10-esp.conf" <"$DEFS/20-root.conf" </dev/null + +# The UKI file referenced in the entry must exist on the ESP. +UKI_PATH=$(awk '/^uki / { print $2 }' "$ENTRY") +test -n "$UKI_PATH" +test -f "$ESP$UKI_PATH" + +# bootctl install should have placed sd-boot on the ESP. +find "$ESP/EFI/systemd" -type f -iname 'systemd-boot*.efi' | grep . >/dev/null + +# The credential we passed via --set-credential= must have been encrypted and +# placed next to the UKI, and must be referenced as 'extra' from the entry. +UKI_DIR="$(dirname "$ESP$UKI_PATH")" +TOKEN_DIR="$(basename "$UKI_DIR")" +test -s "$UKI_DIR/marker.cred" +grep -E "^extra /$TOKEN_DIR/marker\.cred$" "$ENTRY" >/dev/null + +# Locale/keymap/timezone propagation is off, so those .cred files must NOT +# exist on the ESP. +test ! -e "$UKI_DIR/firstboot.locale.cred" +test ! -e "$UKI_DIR/firstboot.keymap.cred" +test ! -e "$UKI_DIR/firstboot.timezone.cred" + +# 8) The seeded files from the fake source tree must end up in the new root. +test -f "$MNT/usr/lib/os-release" +grep '^ID=testos$' "$MNT/usr/lib/os-release" >/dev/null From 1cac85a6af7b748fc055ebbaef6d8334457b57e7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 12:34:48 +0200 Subject: [PATCH 1458/2155] update TODO --- TODO.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 588ff720d49ee..bcfe489151b5a 100644 --- a/TODO.md +++ b/TODO.md @@ -186,6 +186,17 @@ SPDX-License-Identifier: LGPL-2.1-or-later use as additional search condition. Use case: images that combined a sysext partition with a portable service partition in one. +- **systemd-sysinstall:** + - make systemd-sysinstall itself a varlink service + - read installation definition from json file + - polkit support in sysinstall + - sysinstall: permit driving installer via credentials + - add --offline=no mode where we talk to socket based services rather than forking off + - if a user doesn't pick a locale during boot into installer, don't ask again after install, because we suppressed credential propagation + +- repart: add MatchLabel= which matches against partition label, so that we + truly can install different images in parallel + - 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. @@ -1200,14 +1211,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - introduce a new group to own TPM devices -- 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. - - 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. @@ -2491,10 +2494,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later 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. From 5eb256c99b309a86f7836a8397622662376b50f7 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 13:55:54 +0100 Subject: [PATCH 1459/2155] vmspawn-qmp: take temporary ref in drive_info_add_fail drive_info_add_fail() calls bridge_unregister_drive() followed by drive_info_unref(), then continues to access the DriveInfo object. While all current callers hold their own reference, it is a bit fragile and it trips static analyzers. Take a local reference. CID#1655804 Follow-up for 1d0a8e5dbd267c803e100d9030d70d327eddf8b1 --- src/vmspawn/vmspawn-qmp.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 621c5e781a7a6..d09576213a163 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -730,23 +730,26 @@ static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc) 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; + /* Pin the object alive across bridge_unregister_drive() + drive_info_unref() below. */ + _cleanup_(drive_info_unrefp) DriveInfo *ref = drive_info_ref(d); - if (bridge_unregister_drive(d->bridge, d)) - drive_info_unref(d); + vmspawn_qmp_block_device_teardown(ref->bridge->qmp, ref->qmp_node_name, ref->state); + ref->state = BLOCK_DEVICE_STATE_ADD_FAILED; - if (d->link) { - (void) reply_qmp_error(d->link, error_desc, error); - d->link = sd_varlink_unref(d->link); + if (bridge_unregister_drive(ref->bridge, ref)) + drive_info_unref(ref); + + if (ref->link) { + (void) reply_qmp_error(ref->link, error_desc, error); + ref->link = sd_varlink_unref(ref->link); return 0; } log_error_errno(error, "Block device '%s' setup failed: %s", - strna(d->id), strna(error_desc)); + strna(ref->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); + return sd_event_exit(qmp_client_get_event(ref->bridge->qmp), error); } /* Rolls back the up-front registry insert on a sync error path. */ From 5d7d54fc30eb91d9e225d54e5437691a2184ffd3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 10:43:45 +0100 Subject: [PATCH 1460/2155] test: make TEST-64 btrfs_basic cleanup robust against reruns The LUKS subtest in testcase_btrfs_basic leaves stale LUKS headers on the underlying SCSI devices, so if the VM is rebooted the test fails because the LUKS signature is still there and blkid finds it. [ 7.683] + udevadm lock ... mkfs.btrfs -f -L btrfs_root -U deadbeef-dead-dead-beef-000000000000 /dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs0 [ 7.729] Label: btrfs_root [ 7.729] UUID: deadbeef-dead-dead-beef-000000000000 [ 7.743] + udevadm wait --settle --timeout=30 /dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs0 /dev/disk/by-uuid/deadbeef-dead-dead-beef-000000000000 /dev/disk/by-label/btrfs_root [ 7.788] sda: ... SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}": Added device node symlink "disk/by-label/encdisk0". [ 37.998] Timed out for waiting devices being initialized. [ 38.002] TEST-64-UDEV-STORAGE-btrfs_basic.service: Main process exited, code=exited, status=1/FAILURE Likewise for the BTRFS UUID: ERROR: non-unique UUID: deadbeef-dead-dead-beef-000000000001 So wipe that too. --- test/units/TEST-64-UDEV-STORAGE.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/units/TEST-64-UDEV-STORAGE.sh b/test/units/TEST-64-UDEV-STORAGE.sh index de2d7d267212f..e175f6c3d7848 100755 --- a/test/units/TEST-64-UDEV-STORAGE.sh +++ b/test/units/TEST-64-UDEV-STORAGE.sh @@ -849,6 +849,10 @@ EOF btrfs filesystem show helper_check_device_symlinks helper_check_device_units + # Wipe the btrfs signature from each partition first, otherwise the superblocks remain inside + # the disk's data area and would be discovered again as duplicate UUIDs after re-partitioning, + # which breaks subsequent runs of this test (e.g. after a VM reboot). + udevadm lock --timeout=30 --device="${devices[0]}" wipefs -a /dev/disk/by-partlabel/diskpart{1..4} udevadm lock --timeout=30 --device="${devices[0]}" wipefs -a "${devices[0]}" udevadm wait --settle --timeout=30 --removed /dev/disk/by-partlabel/diskpart{1..4} @@ -866,6 +870,12 @@ EOF btrfs filesystem show helper_check_device_symlinks helper_check_device_units + # Wipe the btrfs signatures so that subsequent sections (and runs of the test, e.g. after a VM + # reboot) don't see the stale UUID. + for ((i = 0; i < ${#devices[@]}; i++)); do + udevadm lock --timeout=30 --device="${devices[$i]}" wipefs -a "${devices[$i]}" + done + udevadm settle --timeout=30 echo "Multiple devices: using LUKS encrypted disks, data: raid1, metadata: raid1, mixed mode" uuid="deadbeef-dead-dead-beef-000000000003" @@ -941,7 +951,13 @@ EOF sed -i "/${mpoint##*/}/d" /etc/fstab : >/etc/crypttab rm -fr "$mpoint" + rm -f /etc/btrfs_keyfile systemctl daemon-reload + # Wipe LUKS headers from the underlying devices, so that if the VM is rebooted the disks don't retain + # stale LUKS signatures that would interfere with a re-run of the test. + for ((i = 0; i < ${#devices[@]}; i++)); do + udevadm lock --timeout=30 --device="${devices[$i]}" wipefs -a "${devices[$i]}" + done udevadm settle --timeout=30 } From d508bfb51dd0cc827138d74bb25281f69c458d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 00:53:20 +0200 Subject: [PATCH 1461/2155] man/systemd-report: document --url= and other options 5bbbe210a4e3856385d95e16074d8aa98cff909b added the options but not the documentation. --- man/systemd-report.xml | 75 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index f14600dfe5d32..0974244a8f563 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -1,6 +1,9 @@ - + + "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ + +%entities; +]> + + + + + Upload the collected report to the specified address instead of writing it to + standard output. URL must point to a server accepting POST requests with + a JSON-formatted report body. + + Note: both http:// and https:// URLs are supported, but + connections over plain HTTP are made without encryption. Thus, this mode should + only be used in specific situations where integrity and confidentiality of the report is not required + or is ensured through some other means. Using https:// is recommended. + + + + + + + + Takes a path to a SSL key file in PEM format, used for client certificate + authentication when uploading. Can also be set to -, to disable client certificate + authentication. Defaults to + &CERTIFICATE_ROOT;/private/systemd-report.pem. + + + + + + + + Takes a path to a SSL certificate file in PEM format, used for client certificate + authentication when uploading. Defaults to + &CERTIFICATE_ROOT;/certs/systemd-report.pem. + + + + + + + + Takes a path to a SSL CA certificate file in PEM format used to verify the server + certificate, or the literal string all to disable certificate checking + entirely. Defaults to &CERTIFICATE_ROOT;/ca/trusted.pem. + + + + + + + + Timeout for the network upload operation. Takes a value in seconds (or in other + time units if suffixed with ms, min, h, + etc.); see + systemd.time7 + for details. Defaults to 30 seconds. + + + + + + + + Inject an additional HTTP header into the upload request. May be specified multiple + times to add several headers. Passing an empty string clears any headers added by previous + uses. + + + From fc8d547642808be3aee9a9a01cf1c85add2460b3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 5 May 2026 12:43:42 +0200 Subject: [PATCH 1462/2155] udev-builtin-tpm2-id: gracefully skip tpm2 identification if tss2-libs are not installed Fixes: #41714 --- src/udev/udev-builtin-tpm2_id.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/udev/udev-builtin-tpm2_id.c b/src/udev/udev-builtin-tpm2_id.c index 6edf618e11112..968677a7342bc 100644 --- a/src/udev/udev-builtin-tpm2_id.c +++ b/src/udev/udev-builtin-tpm2_id.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "device-util.h" +#include "errno-util.h" #include "string-util.h" #include "tpm2-util.h" #include "udev-builtin.h" @@ -20,6 +21,10 @@ static int builtin_tpm2_id(UdevEvent *event, int argc, char *argv[]) { _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; r = tpm2_context_new(dn, &c); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_device_debug_errno(dev, r, "Full TPM2 support is not available, skipping identification of TPM2 device '%s'.", dn); + return 0; + } if (r < 0) return log_device_error_errno(dev, r, "Failed to open device node '%s': %m", dn); From 5d17215c7ee46c82401b10b765e7b6840e9d61c7 Mon Sep 17 00:00:00 2001 From: Diego Viola Date: Tue, 5 May 2026 08:22:55 -0300 Subject: [PATCH 1463/2155] treewide: fix typos in the entire codebase Signed-off-by: Diego Viola --- NEWS | 8 ++++---- TODO.md | 10 +++++----- catalog/systemd.catalog.in | 2 +- man/sd_id128_to_string.xml | 2 +- po/es.po | 2 +- rules.d/82-net-auto-link-local.rules | 2 +- shell-completion/zsh/_resolvectl | 2 +- src/core/dbus.c | 2 +- src/core/ipe-setup.c | 2 +- src/home/homework-luks.c | 2 +- src/libsystemd/sd-varlink/sd-varlink-idl.c | 2 +- src/locale/localed-util.c | 2 +- src/network/netdev/bareudp.c | 2 +- src/network/networkd-wwan-bus.c | 4 ++-- src/network/wait-online/wait-online-manager.c | 2 +- src/nspawn/nspawn-network.c | 2 +- src/nsresourced/nsresourcework.c | 2 +- src/pcrlock/pcrlock.c | 2 +- src/portable/portable.c | 2 +- src/resolve/resolved-dns-cache.c | 2 +- src/resolve/test-dns-packet-extract.c | 2 +- src/run/run.c | 2 +- src/shared/bus-polkit.c | 2 +- src/shared/bus-unit-util.c | 2 +- src/shared/calendarspec.c | 2 +- src/shared/copy.c | 2 +- src/shared/dissect-image.c | 2 +- src/shared/firewall-util.c | 2 +- src/shared/ipvlan-util.c | 2 +- src/shared/ipvlan-util.h | 2 +- src/shared/libfido2-util.c | 2 +- src/shared/specifier.c | 2 +- src/shared/tests.c | 2 +- src/shared/tpm2-util.c | 2 +- src/shared/user-record.c | 2 +- src/shared/varlink-io.systemd.Import.c | 2 +- src/systemctl/systemctl-util.c | 2 +- test/test-network/systemd-networkd-tests.py | 2 +- test/units/TEST-36-NUMAPOLICY.sh | 2 +- .../TEST-53-TIMER.RandomizedDelaySec-persistent.sh | 2 +- test/units/TEST-87-AUX-UTILS-VM.pstore.sh | 2 +- 41 files changed, 49 insertions(+), 49 deletions(-) diff --git a/NEWS b/NEWS index 49061c5e11a22..bc7ee1e2bf702 100644 --- a/NEWS +++ b/NEWS @@ -3433,7 +3433,7 @@ CHANGES WITH 257: systemd-importd: - * A new generator sytemd-import-generator has been added to synthesize + * A new generator systemd-import-generator has been added to synthesize image download jobs. This provides functionality similar to importctl, but is configured via the kernel command line and system credentials. It may be used to automatically download sysext, @@ -4610,7 +4610,7 @@ CHANGES WITH 256: OpenSSH 9.4 or newer. * systemd-sysext gained support for enabling system extensions in - mutable fashion, where a writeable upperdir is stored under + mutable fashion, where a writable upperdir is stored under /var/lib/extensions.mutable/, and a new --mutable= option to configure this behaviour. An "ephemeral" mode is not also supported where the mutable layer is configured to be a tmpfs that is @@ -15312,7 +15312,7 @@ CHANGES WITH 231: * The InaccessableDirectories=, ReadOnlyDirectories= and ReadWriteDirectories= unit file settings have been renamed to - InaccessablePaths=, ReadOnlyPaths= and ReadWritePaths= and may now be + InaccessiblePaths=, ReadOnlyPaths= and ReadWritePaths= and may now be applied to all kinds of file nodes, and not just directories, with the exception of symlinks. Specifically these settings may now be used on block and character device nodes, UNIX sockets and FIFOS as @@ -20923,7 +20923,7 @@ CHANGES WITH 189: udev_device_new_from_device_id() call. * The logic for file system namespace (ReadOnlyDirectory=, - ReadWriteDirectoy=, PrivateTmp=) has been reworked not to + ReadWriteDirectories=, PrivateTmp=) has been reworked not to require pivot_root() anymore. This means fewer temporary directories are created below /tmp for this feature. diff --git a/TODO.md b/TODO.md index bcfe489151b5a..b785ac6178cdb 100644 --- a/TODO.md +++ b/TODO.md @@ -222,8 +222,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later - download list + report updates in motd – but do not auto update - download list + download new version – but do not apply it - download list + download new version + apply it – but do not reboot - - download list + donwload new version + apply it + reboot - Other things the policy shoudl contain is when to place the reboot. + - download list + download new version + apply it + reboot + Other things the policy should contain is when to place the reboot. This would all decouple the updating of the package list from the application of it. Which is great for "countme" style stuff. @@ -359,7 +359,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later - add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) -- Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add +- Add ConditionDirectoryNotEmpty= handle non-absolute 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. @@ -1546,7 +1546,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later 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 + set Slice=protected.slice, RefuseManualStart=yes, RefuseManualStop=yes and a couple of other things. - maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for @@ -1840,7 +1840,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later - 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 + "importctl pull-oci" which focuses on the "OCI image spec", i.e. downloads from the web (i.e. acquiring via URLs). - oci: add support for blake hashes for layers diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index 68ef28d1974c0..30da7bd5aa98b 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -455,7 +455,7 @@ Support: %SUPPORT_URL% The directory @WHERE@ is specified as the mount point (second field in /etc/fstab or Where= field in systemd unit file) and is not empty. -This does not interfere with mounting, but the pre-exisiting files in +This does not interfere with mounting, but the pre-existing files in this directory become inaccessible. To see those over-mounted files, please manually mount the underlying file system to a secondary location. diff --git a/man/sd_id128_to_string.xml b/man/sd_id128_to_string.xml index 1d6301ec61581..f5b6c9490ff2b 100644 --- a/man/sd_id128_to_string.xml +++ b/man/sd_id128_to_string.xml @@ -72,7 +72,7 @@ sd_id128_to_uuid_string() and SD_ID128_TO_UUID_STRING() are similar to these two functions/macros, but format the 128-bit values as RFC4122 UUIDs, i.e. a series - of 36 lowercase hexadeciaml digits and dashes, terminated by a NUL byte. + of 36 lowercase hexadecimal digits and dashes, terminated by a NUL byte. sd_id128_from_string() implements the reverse operation: it takes a 33 character string with 32 hexadecimal digits (either lowercase or uppercase, terminated by diff --git a/po/es.po b/po/es.po index 6e6304658278f..7be1c80413602 100644 --- a/po/es.po +++ b/po/es.po @@ -1054,7 +1054,7 @@ msgstr "Necesita autenticarse para restablecer la configuración de DNS." #: src/network/org.freedesktop.network1.policy:143 msgid "DHCP server sends force renew message" -msgstr "El servidor DCHP envía un mensaje de renovación forzada" +msgstr "El servidor DHCP envía un mensaje de renovación forzada" #: src/network/org.freedesktop.network1.policy:144 #, fuzzy diff --git a/rules.d/82-net-auto-link-local.rules b/rules.d/82-net-auto-link-local.rules index 88ac7bc1be05e..88e581c16ca8e 100644 --- a/rules.d/82-net-auto-link-local.rules +++ b/rules.d/82-net-auto-link-local.rules @@ -4,7 +4,7 @@ ACTION=="remove", GOTO="net_link_local_end" SUBSYSTEM!="net", GOTO="net_link_local_end" # Network interfaces for which only Link-Local communication (i.e. IPv4LL, …) -# makes sense, because they almost certainy will point to another host, not an +# makes sense, because they almost certainly will point to another host, not an # internet router. # (Note: matches against VID/PID go into 82-net-auto-link-local.hwdb instead) diff --git a/shell-completion/zsh/_resolvectl b/shell-completion/zsh/_resolvectl index 3d28f1b410e04..dc9a9f1e06dfa 100644 --- a/shell-completion/zsh/_resolvectl +++ b/shell-completion/zsh/_resolvectl @@ -56,7 +56,7 @@ query:"Resolve domain names, IPv4 and IPv6 addresses" reset-server-features:"Flushes all feature level information the resolver has learned about specific servers" reset-statistics:"Resets the statistics counter shown in statistics to zero" - revert:"Revert the per-interfce DNS configuration" + revert:"Revert the per-interface DNS configuration" service:"Resolve DNS-SD and SRV services" show-cache:"Show the current cache contents" show-server-state:"Show servers state" diff --git a/src/core/dbus.c b/src/core/dbus.c index dba79b860266f..659965e4e6c50 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -1060,7 +1060,7 @@ int bus_fdset_add_all(Manager *m, FDSet *fds) { /* When we are about to reexecute we add all D-Bus fds to the * set to pass over to the newly executed systemd. They won't - * be used there however, except thatt they are closed at the + * be used there however, except that they are closed at the * very end of deserialization, those making it possible for * clients to synchronously wait for systemd to reexec by * simply waiting for disconnection */ diff --git a/src/core/ipe-setup.c b/src/core/ipe-setup.c index f263117018ef3..7b684e34b72a3 100644 --- a/src/core/ipe-setup.c +++ b/src/core/ipe-setup.c @@ -23,7 +23,7 @@ int ipe_setup(void) { _cleanup_strv_free_ char **policies = NULL; int r; - /* Very quick smoke tests first: this is in the citical, sequential boot path, and in most cases it + /* Very quick smoke tests first: this is in the critical, sequential boot path, and in most cases it * is unlikely this will be configured, so do the fastest existence checks first and immediately * return if there's nothing to do. */ diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index e85153d61dbc2..9cd0f2dc00438 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -631,7 +631,7 @@ static int fs_validate( sd_id128_t *ret_found_uuid) { _cleanup_free_ char *fstype = NULL; - sd_id128_t u = SD_ID128_NULL; /* avoid false maybe-unitialized warning */ + sd_id128_t u = SD_ID128_NULL; /* avoid false maybe-uninitialized warning */ int r; assert(dm_node); diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index be66fb34afc39..55f9b8b1d0f4e 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -389,7 +389,7 @@ static int varlink_idl_format_symbol( * https://github.com/varlink/varlink.github.io/issues/26 – but for now export this as a * comment. * - * Until this is resolved upsteam, consider this comment part of the API (i.e. don't change + * Until this is resolved upstream, 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); diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 4cddfc32d9103..25c361d400409 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -412,7 +412,7 @@ static bool locale_encoding_is_utf8_or_unspecified(const char *locale) { } static int locale_gen_locale_supported(const char *locale_entry) { - /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported, + /* Returns an error value <= 0 if the locale-gen entry is invalid or unsupported, * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case * the distributor has not provided us with a SUPPORTED file to check * locale for validity. */ diff --git a/src/network/netdev/bareudp.c b/src/network/netdev/bareudp.c index 9dd70296bc6c6..2183e11661c77 100644 --- a/src/network/netdev/bareudp.c +++ b/src/network/netdev/bareudp.c @@ -49,7 +49,7 @@ static int netdev_bare_udp_verify(NetDev *netdev, const char *filename) { if (u->dest_port == 0) return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "%s: BareUDP DesinationPort= is not set. Ignoring.", filename); + "%s: BareUDP DestinationPort= is not set. Ignoring.", filename); if (u->iftype == _BARE_UDP_PROTOCOL_INVALID) return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index 19fcf081ae188..13a860e2dc012 100644 --- a/src/network/networkd-wwan-bus.c +++ b/src/network/networkd-wwan-bus.c @@ -1182,7 +1182,7 @@ int manager_match_mm_signals(Manager *manager) { /* install_callback= */ NULL, manager); if (r < 0) - return log_error_errno(r, "Failed to request signal for IntefaceAdded"); + return log_error_errno(r, "Failed to request signal for InterfaceAdded"); r = sd_bus_match_signal_async( manager->bus, @@ -1195,7 +1195,7 @@ int manager_match_mm_signals(Manager *manager) { /* install_callback= */ NULL, manager); if (r < 0) - return log_error_errno(r, "Failed to request signal for IntefaceRemoved"); + return log_error_errno(r, "Failed to request signal for InterfaceRemoved"); /* N.B. We need "path_namespace" for bearers, not "path", */ r = sd_bus_add_match_async( diff --git a/src/network/wait-online/wait-online-manager.c b/src/network/wait-online/wait-online-manager.c index b5ca38cbe2403..70e2b45f00d20 100644 --- a/src/network/wait-online/wait-online-manager.c +++ b/src/network/wait-online/wait-online-manager.c @@ -68,7 +68,7 @@ static const LinkOperationalStateRange* get_state_range(Manager *m, Link *l, con if (operational_state_range_is_valid(range)) return range; - /* l->requred_operstate should be always valid. */ + /* l->required_operstate should be always valid. */ assert_not_reached(); } diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index 6949ecac724a9..ad506cfaf4b68 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -499,7 +499,7 @@ static int netns_child_begin(int netns_fd, int *ret_original_netns_fd) { if (r < 0) return log_error_errno(r, "Failed to mount sysfs on /sys/: %m"); - /* udev_avaliable() might be called previously and the result may be cached. + /* udev_available() might be called previously and the result may be cached. * Now, we (re-)mount sysfs. Hence, we need to reset the cache. */ reset_cached_udev_availability(); diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index 3b2450529c3ac..91b3645809a92 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -1925,7 +1925,7 @@ static void hash_ether_addr(UserNamespaceInfo *userns_info, const char *ifname, siphash24_compress_byte(0, &state); /* separator */ siphash24_compress_string(strempty(ifname), &state); siphash24_compress_byte(0, &state); /* separator */ - n = htole64(n); /* add the 'index' to the mix in an endianess-independent fashion */ + n = htole64(n); /* add the 'index' to the mix in an endianness-independent fashion */ siphash24_compress_typesafe(n, &state); h = htole64(siphash24_finalize(&state)); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 09f49b2ed250e..63af144fe0327 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1947,7 +1947,7 @@ static int event_log_match_component_variant( return r; if (assign) { - /* Take ownership (Note we allow multiple components and variants to take owneship of the same record!) */ + /* Take ownership (Note we allow multiple components and variants to take ownership of the same record!) */ if (!GREEDY_REALLOC(el->records[i]->mapped, el->records[i]->n_mapped+1)) return log_oom(); diff --git a/src/portable/portable.c b/src/portable/portable.c index 3f3c0cda48891..f9f47f2e0fd89 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -2431,7 +2431,7 @@ int portable_detach( portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, md, NULL); } - /* Now, also drop any image symlink or copy, for images outside of the sarch path */ + /* Now, also drop any image symlink or copy, for images outside of the search path */ SET_FOREACH(item, markers) { _cleanup_free_ char *target = NULL; diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 5540ce5c0593d..6a7967842dbe4 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -68,7 +68,7 @@ struct DnsCacheItem { }; /* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect" - * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can aso prove + * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can also prove * wildcard expansion, non-existence and such, while entries that were created as "side-effect" just contain * immediate RR data for the specified RR key, but nothing else. */ #define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer) diff --git a/src/resolve/test-dns-packet-extract.c b/src/resolve/test-dns-packet-extract.c index ca90afb7eb1dc..28a510dea6467 100644 --- a/src/resolve/test-dns-packet-extract.c +++ b/src/resolve/test-dns-packet-extract.c @@ -400,7 +400,7 @@ TEST(packet_validate_query_too_many_questions) { ASSERT_ERROR(dns_packet_validate_query(packet), EBADMSG); } -TEST(packet_validate_query_with_anwser) { +TEST(packet_validate_query_with_answer) { _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); diff --git a/src/run/run.c b/src/run/run.c index afae5b2d94af4..60f3bf7405ebd 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -962,7 +962,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT; log_debug("Using %s stdio mode.", arg_stdio == ARG_STDIO_PTY ? "pty" : "direct"); if (arg_pty_late < 0) - arg_pty_late = arg_ask_password; /* for run0 this defaults to on, except if --no-ask-pasword is used */ + arg_pty_late = arg_ask_password; /* for run0 this defaults to on, except if --no-ask-password is used */ arg_expand_environment = false; arg_send_sighup = true; diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index 78e9ed377f384..1373dba0ed213 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -346,7 +346,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (r < 0) return r; - /* Now, let's dispatch the original message a second time be re-enqueing. This will then traverse the + /* Now, let's dispatch the original message a second time be re-enqueuing. This will then traverse the * whole message processing again, and thus re-validating and re-retrieving the "userdata" field * again. * diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 440c6ced290ea..7076322d2d315 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2144,7 +2144,7 @@ static int bus_append_protect_hostname(sd_bus_message *m, const char *field, con int r; /* The command-line field is called "ProtectHostname". We also accept "ProtectHostnameEx" as the - * field name for backward compatibility. We set ProtectHostame or ProtectHostnameEx. */ + * field name for backward compatibility. We set ProtectHostname or ProtectHostnameEx. */ r = parse_boolean(eq); if (r >= 0) diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 771363517b39e..225d811d19280 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1127,7 +1127,7 @@ static int find_matching_component( assert(val); - /* Finds the *earliest* matching time specified by one of the CalendarCompoment items in chain c. + /* Finds the *earliest* matching time specified by one of the CalendarComponent items in chain c. * If no matches can be found, returns -ENOENT. * Otherwise, updates *val to the matching time. 1 is returned if *val was changed, 0 otherwise. */ diff --git a/src/shared/copy.c b/src/shared/copy.c index 3ac05c15b7b2a..a8e2b68db1414 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -652,7 +652,7 @@ static int hardlink_context_setup( * <= 0, because in that case we will not actually allocate the hardlink inode lookup table directory * on disk (we do so lazily, when the first candidate with .n_link > 1 is seen). This means, in the * common case where hardlinks are not used at all or only for few files the fact that we store the - * table on disk shouldn't matter perfomance-wise. */ + * table on disk shouldn't matter performance-wise. */ if (!FLAGS_SET(copy_flags, COPY_HARDLINKS)) return 0; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e69c644f58eeb..fcd809b0e61b1 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2298,7 +2298,7 @@ int partition_pick_mount_options( case PARTITION_XBOOTLDR: flags |= MS_NOSUID|MS_NOEXEC|MS_NOSYMFOLLOW; - /* The ESP might contain a pre-boot random seed. Let's make this unaccessible to regular + /* The ESP might contain a pre-boot random seed. Let's make this inaccessible to regular * userspace. ESP/XBOOTLDR is almost certainly VFAT, hence if we don't know assume it is. */ if (!fstype || fstype_can_fmask_dmask(fstype)) if (!strextend_with_separator(&options, ",", "fmask=0177,dmask=0077")) diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 4693972ff2752..441e137ec03cd 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -1026,7 +1026,7 @@ int fw_nftables_add_masquerade( * Note that this doesn't protect against external sabotage such as a * 'while true; nft flush ruleset; done'. There is nothing that could be done about that short * of extending the kernel to allow tables to be owned by stystemd-networkd and making them - * non-deleteable except by the 'owning process'. */ + * non-deletable except by the 'owning process'. */ r = fw_nftables_init_family(nfnl, af); if (r < 0) diff --git a/src/shared/ipvlan-util.c b/src/shared/ipvlan-util.c index 1906c8026f470..5c271ba4b688b 100644 --- a/src/shared/ipvlan-util.c +++ b/src/shared/ipvlan-util.c @@ -12,7 +12,7 @@ static const char* const ipvlan_mode_table[_NETDEV_IPVLAN_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(ipvlan_mode, IPVlanMode); static const char* const ipvlan_flags_table[_NETDEV_IPVLAN_FLAGS_MAX] = { - [NETDEV_IPVLAN_FLAGS_BRIGDE] = "bridge", + [NETDEV_IPVLAN_FLAGS_BRIDGE] = "bridge", [NETDEV_IPVLAN_FLAGS_PRIVATE] = "private", [NETDEV_IPVLAN_FLAGS_VEPA] = "vepa", }; diff --git a/src/shared/ipvlan-util.h b/src/shared/ipvlan-util.h index 4cb74f3dcbb92..194e5cea04289 100644 --- a/src/shared/ipvlan-util.h +++ b/src/shared/ipvlan-util.h @@ -14,7 +14,7 @@ typedef enum IPVlanMode { } IPVlanMode; typedef enum IPVlanFlags { - NETDEV_IPVLAN_FLAGS_BRIGDE, + NETDEV_IPVLAN_FLAGS_BRIDGE, NETDEV_IPVLAN_FLAGS_PRIVATE = IPVLAN_F_PRIVATE, NETDEV_IPVLAN_FLAGS_VEPA = IPVLAN_F_VEPA, _NETDEV_IPVLAN_FLAGS_MAX, diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index cc00006af9d54..c25019e6b01d8 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -361,7 +361,7 @@ static int fido2_is_cred_in_specific_token( /* According to CTAP 2.1 specification, to do pre-flight we need to set up option to false * with optionally pinUvAuthParam in assertion[1]. But for authenticator that doesn't support * user presence, once up option is present, the authenticator may return CTAP2_ERR_UNSUPPORTED_OPTION[2]. - * So we simplely omit the option in that case. + * So we simply omit the option in that case. * Reference: * 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight * 2: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion (in step 5) diff --git a/src/shared/specifier.c b/src/shared/specifier.c index 595160352225b..9d2066899f2fe 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -347,7 +347,7 @@ int specifier_user_name(char specifier, const void *data, const char *root, cons * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed. * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain - * consistent with specifer_user_id() below. + * consistent with specifier_user_id() below. */ t = uid_to_name(uid); diff --git a/src/shared/tests.c b/src/shared/tests.c index 8111d481e4d6a..84945343a4650 100644 --- a/src/shared/tests.c +++ b/src/shared/tests.c @@ -361,7 +361,7 @@ const char* ci_environment(void) { if (getenv("SALSA_CI_IMAGES")) return (ans = "salsa-ci"); - FOREACH_STRING(var, "CI", "CONTINOUS_INTEGRATION") { + FOREACH_STRING(var, "CI", "CONTINUOUS_INTEGRATION") { /* Those vars are booleans according to Semaphore and Travis docs: * https://docs.travis-ci.com/user/environment-variables/#default-environment-variables * https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#ci diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 091a4dba89ac3..4557ad3f41456 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -819,7 +819,7 @@ static bool tpm2_supports_tpmt_sym_def(Tpm2Context *c, const TPMT_SYM_DEF *param assert(c); assert(parameters); - /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBEJECT are separately defined, even though they are + /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBJECT are separately defined, even though they are * functionally identical. */ TPMT_SYM_DEF_OBJECT object = { .algorithm = parameters->algorithm, diff --git a/src/shared/user-record.c b/src/shared/user-record.c index cf33d92215b8d..191870c8ea62d 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2489,7 +2489,7 @@ int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) * `selfModifiableFields` fields are unset in their record. * 2) This user crafts a request to add the following to their record: * { "memberOf": ["wheel"], "selfModifiableFields": ["memberOf", "selfModifiableFields"] } - * 3) We remove the `mebmerOf` and `selfModifiabileFields` fields from `incoming` + * 3) We remove the `memberOf` and `selfModifiabileFields` fields from `incoming` * 4) `current` and `incoming` compare as equal, so we let the change happen * 5) the user has granted themselves administrator privileges */ diff --git a/src/shared/varlink-io.systemd.Import.c b/src/shared/varlink-io.systemd.Import.c index b4e2ab03e2d4e..13d239b9dacb2 100644 --- a/src/shared/varlink-io.systemd.Import.c +++ b/src/shared/varlink-io.systemd.Import.c @@ -87,7 +87,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_INPUT_BY_TYPE(verify, ImageVerify, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, an existing image by the local name is deleted. Defaults to false."), SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults ot false."), + SD_VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults to false."), SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether to keep a pristine copy of the download separate from the locally installed image. Defaults to false."), SD_VARLINK_DEFINE_INPUT(keepDownload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c index b278f784ba3ec..8e8d7181121e3 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -508,7 +508,7 @@ int unit_find_paths( * Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is * found, and sets: * - * - the path to the unit in *ret_frament_path, if it exists on disk, + * - the path to the unit in *ret_fragment_path, if it exists on disk, * * - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins * were found. diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 0db4830848535..6917c2f697b72 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -9332,7 +9332,7 @@ def get_dhcp_6rd_prefix(link): # ipv4masklen: 8 # 6rd-prefix: 2001:db8::/32 - # br-addresss: 10.0.0.1 + # br-address: 10.0.0.1 start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', ipv4_range='10.100.100.100,10.100.100.200', diff --git a/test/units/TEST-36-NUMAPOLICY.sh b/test/units/TEST-36-NUMAPOLICY.sh index 71fcdb4524870..1e2d7ac8ac133 100755 --- a/test/units/TEST-36-NUMAPOLICY.sh +++ b/test/units/TEST-36-NUMAPOLICY.sh @@ -34,7 +34,7 @@ testUnitNUMAConf="$testUnitFile.d/numa.conf" sleepAfterStart=3 # Journal cursor for easier navigation -journalCursorFile="jounalCursorFile" +journalCursorFile="journalCursorFile" startStrace() { coproc strace -qq -p 1 -o "$straceLog" -e set_mempolicy -s 1024 ${1:+"$1"} diff --git a/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh b/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh index af22daecc7668..e3005fe0f5df3 100755 --- a/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh +++ b/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later # -# Persistent timers (i.e. timers with Persitent=yes) save their last trigger timestamp to a persistent +# Persistent timers (i.e. timers with Persistent=yes) save their last trigger timestamp to a persistent # storage (a stamp file), which is loaded during subsequent boots. As mentioned in the man page, such timers # should be still affected by RandomizedDelaySec= during boot even if they already elapsed and would be then # triggered immediately. diff --git a/test/units/TEST-87-AUX-UTILS-VM.pstore.sh b/test/units/TEST-87-AUX-UTILS-VM.pstore.sh index be5297fa52da9..d05c99b15076c 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.pstore.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.pstore.sh @@ -44,7 +44,7 @@ random_efi_timestamp() { printf "%0.10d" "$((1000000000 + RANDOM))"; } # The dmesg- filename contains the backend-type and the Common Platform Error Record, CPER, # record id, a 64-bit number. # -# Files are processed in reverse lexigraphical order so as to properly reconstruct original dmesg. +# Files are processed in reverse lexicographical order so as to properly reconstruct original dmesg. prepare_efi_logs() { local file="${1:?}" From 5afd344438833535b541beb8065b4ce98fc73d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 15:26:47 +0200 Subject: [PATCH 1464/2155] bootctl,mute-console,pcrextend,pcrlock,repart: allow connections from self MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With SD_VARLINK_SERVER_ROOT_ONLY, we refuse all unprivileged operations. This is silly, the user can and should be able to do anything that doesn't require privileges. E.g.: $ SYSTEMD_LOG_LEVEL=debug varlinkctl introspect /usr/lib/systemd/systemd-pcrextend Forking off Varlink child process '/usr/lib/systemd/systemd-pcrextend'. Successfully forked off '(sd-vlexec)' as PID 568993. varlink: Setting state idle-client json-stream: Sending message: {"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"io.systemd.PCRExtend"}} Skipping PR_SET_MM, as we don't have privileges. varlink: Changing state idle-client → calling varlink: Unprivileged client attempted connection, refusing. Failed to run Varlink event loop: Operation not permitted json-stream: Got POLLHUP from socket. varlink: Changing state calling → pending-disconnect varlink: Connection was closed. Failed to issue org.varlink.service.GetInterfaceDescription() varlink call: Connection reset by peer This and similar commands now work, e.g. $ SYSTEMD_LOG_LEVEL=debug varlinkctl call --more ./build/bootctl io.systemd.BootControl.ListBootEntries {} ... Failed to open directory "/efi": No such file or directory File system "/boot" is not a FAT EFI System Partition (ESP) file system. ... Method call failed: Permission denied { "origin" : "linux", "errno" : 13, "errnoName" : "EACCES" } Which is fine — we lack privileges to actually return a useful answer, but the call itself should go through. I didn't touch udevd, which refuses to run if it is not root, and does a lot of privileged setup, so would refuse to start even if the check was removed. --- src/bootctl/bootctl.c | 4 +++- src/mute-console/mute-console.c | 5 +++-- src/pcrextend/pcrextend.c | 4 +++- src/pcrlock/pcrlock.c | 4 +++- src/repart/repart.c | 7 +++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index c627a7dd077d5..881bdfb60ffe0 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -817,7 +817,9 @@ static int vl_server(void) { r = varlink_server_new( &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, + SD_VARLINK_SERVER_ROOT_ONLY | + SD_VARLINK_SERVER_MYSELF_ONLY | + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index d5788de09b3b9..b64c66e14f07f 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -308,8 +308,9 @@ static int vl_server(void) { r = varlink_server_new( &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY| - SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_ROOT_ONLY | + SD_VARLINK_SERVER_MYSELF_ONLY | + SD_VARLINK_SERVER_HANDLE_SIGINT | SD_VARLINK_SERVER_HANDLE_SIGTERM, /* userdata= */ NULL); if (r < 0) diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index f452363209d66..278b3b730c6a4 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -427,7 +427,9 @@ static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; int r; - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, /* userdata= */ NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 63af144fe0327..4ebe3f995bdd8 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5480,7 +5480,9 @@ static int run(int argc, char *argv[]) { /* Invocation as Varlink service */ - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); diff --git a/src/repart/repart.c b/src/repart/repart.c index ad19f0ab1ec7a..84aaf60b5f790 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11094,10 +11094,9 @@ static int vl_server(void) { /* Invocation as Varlink service */ - r = varlink_server_new( - &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY, - /* userdata= */ NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); From 63eb3cf57b67ca8abc22e61d9dda1f572f4f7562 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 5 May 2026 17:24:48 +0200 Subject: [PATCH 1465/2155] update TODO --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.md b/TODO.md index b785ac6178cdb..61114235f3290 100644 --- a/TODO.md +++ b/TODO.md @@ -132,6 +132,10 @@ SPDX-License-Identifier: LGPL-2.1-or-later systems for SBC. Should be doing what sysinstall does with the credentials, and maybe even *be* sysinstall. +- make sure we always pass O_NOFOLLOW on O_CREAT + +- xopenat(): maybe imply O_NOFOLLOW on O_CREAT + - StorageProvider interface + storagectl - hook-up in systemd-nspawn - hook-up in systemd-vmspawn From 4de3f59774b7afeaa6a70d6f072aeb5e5e1222f0 Mon Sep 17 00:00:00 2001 From: Simran Singh Date: Sun, 3 May 2026 00:22:10 +0530 Subject: [PATCH 1466/2155] man: EnvironmentFile= honors %h, not \$HOME --- man/systemd.exec.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 809cc285fdce1..6524ba631a7cc 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3294,6 +3294,10 @@ SystemCallErrorNumber=EPERM If the empty string is assigned to this option, the list of files to read is reset, all prior assignments have no effect. + Note that shell variables such as $HOME are not expanded in this path. + Use %-specifiers instead; for example, %h expands to the + user's home directory. + The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated. This means you can generate these files in one unit state, and read it with this option in the next. The files are read from the file From c0aa351ba966bb89bab11673cae72ccba2e16bc4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 15:33:49 +0100 Subject: [PATCH 1467/2155] test-oomd: fix flakiness under sanitizers The test asserts that pgscan is 0, but under sanitizers this sometimes fails and shows up as 1. We cannot control what the kernel scans, and with sanitizers the runtime can be slow enough it's possible that the kernel does a pass on the cgroup of the unit test. Instead of asserting that it's 0, assert that it's between 0 and 9, which seems a reasonable range. Fixes https://github.com/systemd/systemd/issues/37710 --- src/oom/test-oomd-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oom/test-oomd-util.c b/src/oom/test-oomd-util.c index 7332f532a8aaa..c78315c56b6b0 100644 --- a/src/oom/test-oomd-util.c +++ b/src/oom/test-oomd-util.c @@ -291,7 +291,7 @@ TEST(oomd_cgroup_context_acquire_and_insert) { ASSERT_EQ(ctx->memory_low, 0u); ASSERT_EQ(ctx->swap_usage, 0u); ASSERT_EQ(ctx->last_pgscan, 0u); - ASSERT_EQ(ctx->pgscan, 0u); + ASSERT_LT(ctx->pgscan, 10u); ASSERT_NULL(ctx = oomd_cgroup_context_unref(ctx)); ASSERT_OK(oomd_cgroup_context_acquire("", &ctx)); From fc05165fce386c8cf991f18abecc5098e7261b6f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 5 May 2026 10:45:14 +0200 Subject: [PATCH 1468/2155] terminal-util: when prompting for a choice from a list, preselect longest prefix If all entries of a menu prompt start with the same prefix, let's preselect the prefix to enhance user experience. This is particularly relevant when prompting for a disk to install things on, as typically they all start with the same prefix /dev/, and if there's only a single target medium discoverable, then we can even fill it out fully. --- src/basic/terminal-util.c | 25 +++++++++++++++++++++---- src/basic/terminal-util.h | 8 +++++++- src/home/homectl.c | 21 +++++++++++++-------- src/shared/prompt-util.c | 30 ++++++++++++++++++++++-------- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index e5e66a1864777..d241f5e7f8998 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -293,17 +293,33 @@ int ask_string_full( assert(ret); assert(text); + _cleanup_free_ char *string = NULL; + size_t n = 0; + + if (get_completions) { + /* Figure out what string to preselect the query with */ + _cleanup_strv_free_ char **completions = NULL; + r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata); + if (r < 0) + return r; + + CompletionResult cr = pick_completion(string, completions, &string); + if (cr < 0) + return cr; + + n = strlen_ptr(string); + } + /* Output the prompt */ fputs(ansi_highlight(), stdout); va_start(ap, text); vprintf(text, ap); va_end(ap); fputs(ansi_normal(), stdout); + if (string) + fputs(string, stdout); fflush(stdout); - _cleanup_free_ char *string = NULL; - size_t n = 0; - /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows * swapping out stdin/stdout. */ @@ -344,7 +360,7 @@ int ask_string_full( _cleanup_strv_free_ char **completions = NULL; if (get_completions) { - r = get_completions(string, &completions, userdata); + r = get_completions(string, /* flags= */ 0, &completions, userdata); if (r < 0) return r; } @@ -450,6 +466,7 @@ int ask_string_full( fallback: /* A simple fallback without TTY magic */ + string = mfree(string); r = read_line(stdin, LONG_LINE_MAX, &string); if (r < 0) return r; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 7ac5661104159..abf999e6d5562 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -88,7 +88,13 @@ int chvt(int vt); int read_one_char(FILE *f, char *ret, usec_t timeout, bool echo, bool *need_nl); int ask_char(char *ret, const char *replies, const char *fmt, ...) _printf_(3, 4); -typedef int (*GetCompletionsCallback)(const char *key, char ***ret_list, void *userdata); +typedef enum GetCompletionsFlags { + /* Only return the items subject to preselection: typically you want to suppress meta entries such as + * "list" or alias entries if this flag is set. */ + GET_COMPLETIONS_PRESELECT = 1 << 0, +} GetCompletionsFlags; + +typedef int (*GetCompletionsCallback)(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata); int ask_string_full(char **ret, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(4, 5); #define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, text, ##__VA_ARGS__) diff --git a/src/home/homectl.c b/src/home/homectl.c index 271e03587502b..37714aa2b0185 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2697,7 +2697,7 @@ static int acquire_group_list(char ***ret) { return !!*ret; } -static int group_completion_callback(const char *key, char ***ret_list, void *userdata) { +static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { char ***available = userdata; int r; @@ -2711,9 +2711,11 @@ static int group_completion_callback(const char *key, char ***ret_list, void *us if (!l) return -ENOMEM; - r = strv_extend(&l, "list"); - if (r < 0) - return r; + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(&l, "list"); + if (r < 0) + return r; + } *ret_list = TAKE_PTR(l); return 0; @@ -2745,10 +2747,13 @@ static int prompt_groups(const char *username, char ***ret_groups) { } _cleanup_free_ char *s = NULL; - r = ask_string_full(&s, - group_completion_callback, &available, - "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", - glyph(GLYPH_LABEL), username); + r = ask_string_full( + &s, + group_completion_callback, + &available, + "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", + glyph(GLYPH_LABEL), + username); if (r < 0) return log_error_errno(r, "Failed to query user for auxiliary group: %m"); diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 9811cb1527873..7cead706fd95f 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -16,27 +16,41 @@ #include "strv.h" #include "terminal-util.h" +typedef struct CompletionData { + char **menu; /* What to show in menu */ + char **accepted; /* What to accept (usually larger than the menu, but may be NULL if same) */ +} CompletionData; + static int get_completions( const char *key, + GetCompletionsFlags flags, char ***ret_list, void *userdata) { + CompletionData *data = ASSERT_PTR(userdata); int r; assert(ret_list); - if (!userdata) { + /* Figure out the list to operate on. We'll generally work based on the "accepted" list, if it is + * set. If not we'll operate with the full menu. When doing pre-selection we'll also pick the menu */ + char **l = data->accepted && !FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT) ? data->accepted : data->menu; + + if (strv_isempty(l)) { *ret_list = NULL; return 0; } - _cleanup_strv_free_ char **copy = strv_copy(userdata); + _cleanup_strv_free_ char **copy = strv_copy(l); if (!copy) return -ENOMEM; - r = strv_extend(©, "list"); - if (r < 0) - return r; + /* Never consider "list" for preselecting an item, but do consider it when doing a regular completion */ + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(©, "list"); + if (r < 0) + return r; + } *ret_list = TAKE_PTR(copy); return 0; @@ -45,8 +59,8 @@ static int get_completions( int prompt_loop( const char *text, Glyph emoji, - char **menu, /* if non-NULL: choices to suggest */ - char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ + char **menu, /* if non-NULL: choices to suggest */ + char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ unsigned ellipsize_percentage, size_t n_columns, size_t column_width, @@ -102,7 +116,7 @@ int prompt_loop( r = ask_string_full( &p, get_completions, - accepted ?: menu, + &(CompletionData) { menu, accepted }, "%s%s%s%s: ", emoji >= 0 ? glyph(emoji) : "", emoji >= 0 ? " " : "", From afa3eb821d4442d3b9d9e693be5322ffbdf594ad Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 16:21:16 +0100 Subject: [PATCH 1469/2155] test: drop more 'grep -q' instances Follow-up for ee6b3d1aa2329cddb5867bbc86a4b62983ee56fe --- test/units/TEST-04-JOURNAL.journalctl-varlink.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh index 2d7b19990c815..16cce6f0d4718 100755 --- a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh +++ b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh @@ -38,14 +38,14 @@ systemd-run --unit="$UNIT_NAME_2" --wait bash -c 'echo hello-from-varlink-test-2 journalctl --sync # single unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep -q "hello-from-varlink-test-1" +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-1" >/dev/null (! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-2") # multi unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-1" -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-2" +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep "hello-from-varlink-test-1" >/dev/null +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep "hello-from-varlink-test-2" >/dev/null # check priority filter: priority 4 (warning) should include our warning message -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep -q "varlink-test-warning" +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep "varlink-test-warning" >/dev/null # check priority filter: priority 3 (error) should NOT include our warning (priority 4) (! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 3, "limit": 1000}' | grep "varlink-test-warning") From 88cc10e507f637fd073e13cda5d9177e36761159 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 16:50:40 +0100 Subject: [PATCH 1470/2155] test: reduce number of identical io.systemd.JournalAccess.GetEntries calls This test is sometimes flaky under sanitizers, and it does repeated calls with the same parameters to run through different greps, and the second one sometimes fails. Store the result and grep it twice instead to try and reduce flakiness. [ 2089.891152] TEST-04-JOURNAL.sh[22392]: + systemd-run --unit=test-journalctl-varlink-2-18237.service --wait bash -c 'echo hello-from-varlink-test-2' [ 2090.066050] TEST-04-JOURNAL.sh[22460]: + varlinkctl call --more /run/systemd/io.systemd.JournalAccess io.systemd.JournalAccess.GetEntries '{"units": ["test-journalctl-varlink-1-22690.service"]}' [ 2090.067075] TEST-04-JOURNAL.sh[22461]: + grep -q hello-from-varlink-test-1 [ 2090.384551] TEST-04-JOURNAL.sh[22466]: + varlinkctl call --more /run/systemd/io.systemd.JournalAccess io.systemd.JournalAccess.GetEntries '{"units": ["test-journalctl-varlink-1-22690.service"]}' [ 2090.385373] TEST-04-JOURNAL.sh[22467]: + grep hello-from-varlink-test-2 [ 2090.723461] TEST-04-JOURNAL.sh[22474]: + grep -q hello-from-varlink-test-1 [ 2090.724294] TEST-04-JOURNAL.sh[22473]: + varlinkctl call --more /run/systemd/io.systemd.JournalAccess io.systemd.JournalAccess.GetEntries '{"units": ["test-journalctl-varlink-1-22690.service", "test-journalctl-varlink-2-18237.service"]}' [ 2091.135655] TEST-04-JOURNAL.sh[22480]: + varlinkctl call --more /run/systemd/io.systemd.JournalAccess io.systemd.JournalAccess.GetEntries '{"units": ["test-journalctl-varlink-1-22690.service", "test-journalctl-varlink-2-18237.service"]}' [ 2091.136605] TEST-04-JOURNAL.sh[22481]: + grep -q hello-from-varlink-test-2 [ 2091.479930] TEST-04-JOURNAL.sh[22480]: Method call failed: io.systemd.JournalAccess.NoEntries --- test/units/TEST-04-JOURNAL.journalctl-varlink.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh index 16cce6f0d4718..4f86fa2a541ff 100755 --- a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh +++ b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh @@ -38,11 +38,13 @@ systemd-run --unit="$UNIT_NAME_2" --wait bash -c 'echo hello-from-varlink-test-2 journalctl --sync # single unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-1" >/dev/null -(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-2") +SINGLE_OUTPUT="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}")" +grep "hello-from-varlink-test-1" >/dev/null <<<"$SINGLE_OUTPUT" +(! grep "hello-from-varlink-test-2" >/dev/null <<<"$SINGLE_OUTPUT") # multi unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep "hello-from-varlink-test-1" >/dev/null -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep "hello-from-varlink-test-2" >/dev/null +MULTI_OUTPUT="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}")" +grep "hello-from-varlink-test-1" >/dev/null <<<"$MULTI_OUTPUT" +grep "hello-from-varlink-test-2" >/dev/null <<<"$MULTI_OUTPUT" # check priority filter: priority 4 (warning) should include our warning message varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep "varlink-test-warning" >/dev/null From a2186070b79b2dcce332465696ad31241f27cf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 23:55:48 +0200 Subject: [PATCH 1471/2155] report: upload reports using a "varlink socket directory" Two new verbs are added: "generate" and "upload". The first one just creates a "report", i.e. puts the metrics into a structured JSON object that in the future is intended to carry additional data like a signature: $ build/systemd-report generate io.systemd.Manager.UnitsTotal { "mediaType" : "application/vnd.io.systemd.report", "timestamp" : "Tue 2026-04-28 22:30:09 UTC", "metrics" : [ { "name" : "io.systemd.Manager.UnitsTotal", "value" : 520 } ] } The second verb can be used to upload or otherwise process the report. It builds on the code added in 0a8560eed873a5f89487630a19db550fdbee3c15. In /run/systemd/metrics-upload/ we expect a set of sockets. We'll call out to each one of them. This allows the data to be processed in custom ways, incl. writing to storage or sending over the network. Each socket must provide a single interface: io.systemd.Metrics.Upload {"report":$data} --- man/systemd-report.xml | 25 ++++++ src/report/report-upload.c | 105 +++++++++++++++++++++---- src/report/report.c | 30 +++---- src/report/report.h | 9 ++- test/units/TEST-74-AUX-UTILS.report.sh | 8 +- 5 files changed, 145 insertions(+), 32 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index 0974244a8f563..560d9406240f0 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -71,6 +71,31 @@ + + generate MATCH + + Acquire a list of metrics and build a JSON report. + + Match expressions supported by metrics are supported here too. + + + + + + upload MATCH + + This command can be used to send the report built by generate + to an external server. Two upload mechanisms are supported. If an http:// or + https:// URL is specified with , an HTTP upload will be + performed to the specified location. Otherwise, any sockets under + /run/systemd/metrics-upload/ will be used to call + io.systemd.Report.Upload(). + + Match expressions supported by metrics are supported here too. + + + + list-sources diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 486e815e8d857..fce0c0e551396 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -2,12 +2,15 @@ #include "sd-json.h" +#include "alloc-util.h" +#include "errno-util.h" #include "log.h" #include "report.h" #include "string-util.h" #include "strv.h" #include "time-util.h" #include "utf8.h" +#include "varlink-util.h" #include "version.h" #if HAVE_LIBCURL @@ -49,6 +52,7 @@ static size_t output_callback(char *buf, return nmemb; } +#endif static int build_json_report(Context *context, sd_json_variant **ret) { /* Convert the variant array to a JSON report. */ @@ -60,6 +64,7 @@ static int build_json_report(Context *context, sd_json_variant **ret) { int r; r = sd_json_buildo(ret, + SD_JSON_BUILD_PAIR_STRING("mediaType", "application/vnd.io.systemd.report"), SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), SD_JSON_BUILD_PAIR("metrics", @@ -68,9 +73,8 @@ static int build_json_report(Context *context, sd_json_variant **ret) { return log_error_errno(r, "Failed to build JSON data: %m"); return 0; } -#endif -int upload_collected(Context *context) { +static int http_upload_collected(Context *context, sd_json_variant *report) { #if HAVE_LIBCURL _cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL; char error[CURL_ERROR_SIZE] = {}; @@ -81,19 +85,11 @@ int upload_collected(Context *context) { 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. */ - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL; - r = build_json_report(context, &vl); - if (r < 0) - return r; + /* Upload a JSON report in text form as a single JSON object, instead of a JSON-SEQ list. */ - r = sd_json_variant_format(vl, /* flags= */ 0, &json); - if (r < 0) - return log_error_errno(r, "Failed to format JSON data: %m"); - } + r = sd_json_variant_format(report, /* 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", @@ -206,3 +202,84 @@ int upload_collected(Context *context) { "Compiled without libcurl."); #endif } + +static int execute_dir_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + assert(link); + + Context *context = ASSERT_PTR(userdata); + int r; + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + RET_GATHER(context->upload_result, r); + return log_error_errno(r, "Upload via Varlink failed: %s", error_id); + } + + printf("Upload via Varlink was successful; reply: "); + // TODO: once we know what we want to put in the reply, replace the JSON dump by + // some formatted output. + r = sd_json_variant_dump(reply, arg_json_format_flags, stderr, /* prefix= */ ">>> "); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + + return 0; +} + +static int upload_collected(Context *context, sd_json_variant *report) { + int r; + + if (arg_url) + return http_upload_collected(context, report); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR_VARIANT("report", report)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + ssize_t jobs = varlink_execute_directory( + REPORT_UPLOAD_DIR, + "io.systemd.Report.Upload", + params, + /* more= */ false, + arg_network_timeout_usec, + execute_dir_reply, + /* userdata= */ context); + if (jobs < 0) + return log_error_errno(jobs, "Failed to execute upload via %s: %m", REPORT_UPLOAD_DIR); + if (jobs == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "No upload mechanism found via %s.", REPORT_UPLOAD_DIR); + if (context->upload_result < 0) + /* The details were printed at error level by execute_dir_reply above. */ + return log_debug_errno(context->upload_result, "Upload via %s failed: %m", REPORT_UPLOAD_DIR); + + log_debug("Upload via %s finished successfully.", REPORT_UPLOAD_DIR); + return 0; +} + +/* Make a structured report and either print it or upload it. */ +int report_collected(Context *context) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + int r; + + r = build_json_report(context, &report); + if (r < 0) + return r; + + if (context->action == ACTION_UPLOAD) + return upload_collected(context, report); + + /* Just print the report for now. */ + assert(context->action == ACTION_GENERATE); + r = sd_json_variant_dump(report, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + return 0; +} diff --git a/src/report/report.c b/src/report/report.c index feedcaa43a5b8..c23417afb5b29 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -20,7 +20,6 @@ #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" @@ -35,8 +34,8 @@ static PagerFlags arg_pager_flags = 0; 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; +sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; char *arg_url = NULL; char *arg_key = NULL; char *arg_cert = NULL; @@ -84,13 +83,6 @@ 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", -}; - -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; @@ -300,7 +292,9 @@ static int call_collect(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 = ASSERT_PTR(action_method_to_string(context->action)); + const char *method = context->action == ACTION_DESCRIBE_METRICS ? + "io.systemd.Metrics.Describe" : + "io.systemd.Metrics.List"; /* This is the method for all other actions. */ r = sd_varlink_observe(vl, method, /* parameters= */ NULL); if (r < 0) @@ -591,16 +585,22 @@ VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_L "Acquire list of metrics and their values"); VERB_FULL(verb_metrics, "describe", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, "Describe available metrics"); +VERB_FULL(verb_metrics, "generate", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_GENERATE, + "Build a report with metrics"); +VERB_FULL(verb_metrics, "upload", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_UPLOAD, + "Upload a report with metrics"); 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_METRICS, ACTION_DESCRIBE_METRICS)); + assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS, ACTION_GENERATE, ACTION_UPLOAD)); - /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ - arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + if (IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) + /* Enable JSON-SEQ mode for the first two verbs, since we'll dump a large series of JSON + * objects. In the report format, we return a single JSON object, so don't do this. */ + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; r = parse_metrics_matches(argv + 1); if (r < 0) @@ -651,8 +651,8 @@ 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"); - if (arg_url) - r = upload_collected(&context); + if (IN_SET(action, ACTION_GENERATE, ACTION_UPLOAD)) + r = report_collected(&context); else r = output_collected(&context); if (r < 0) diff --git a/src/report/report.h b/src/report/report.h index 4adb20349514a..196a3daf577d5 100644 --- a/src/report/report.h +++ b/src/report/report.h @@ -9,6 +9,9 @@ #define REPORT_CERT_FILE CERTIFICATE_ROOT "/certs/systemd-report.pem" #define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" +#define REPORT_UPLOAD_DIR "/run/systemd/metrics-upload" + +extern sd_json_format_flags_t arg_json_format_flags; extern char *arg_url, *arg_key, *arg_cert, *arg_trust; extern char **arg_extra_headers; extern usec_t arg_network_timeout_usec; @@ -16,6 +19,8 @@ extern usec_t arg_network_timeout_usec; typedef enum Action { ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS, + ACTION_GENERATE, + ACTION_UPLOAD, _ACTION_MAX, _ACTION_INVALID = -EINVAL, } Action; @@ -27,7 +32,9 @@ typedef struct Context { Set *link_infos; sd_json_variant **metrics; /* Collected metrics for sorting */ size_t n_metrics, n_skipped_metrics, n_invalid_metrics; + + int upload_result; struct iovec_wrapper upload_answer; } Context; -int upload_collected(Context *context); +int report_collected(Context *context); diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 73678fcabf1f8..e9332806b3791 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -60,7 +60,11 @@ trap at_exit EXIT systemd-run -p Type=notify --unit=fake-report-server "$FAKE_SERVER" systemctl status fake-report-server -"$REPORT" metrics --url=http://localhost:8089/ +"$REPORT" generate io.systemd.Manager.UnitsTotal + +"$REPORT" generate io.systemd.Manager.UnitsTotal | jq . + +"$REPORT" upload --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" \ @@ -70,5 +74,5 @@ 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" upload --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ --extra-header='Authorization: Bearer magic string' From 9c336ef6eb9357de71df3163c7a511c9fb470474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 11:42:00 +0200 Subject: [PATCH 1472/2155] report: set description on varlink sockets We make multiple connections and without this it's hard to know socket we're talking to. --- src/report/report.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/report/report.c b/src/report/report.c index c23417afb5b29..c0cd92042617a 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -280,6 +280,10 @@ static int call_collect(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Unable to connect to %s: %m", path); + r = sd_varlink_set_description(vl, name); + if (r < 0) + return log_error_errno(r, "Failed to set varlink description: %m"); + r = sd_varlink_set_relative_timeout(vl, TIMEOUT_USEC); if (r < 0) return log_error_errno(r, "Failed to set varlink timeout: %m"); From ffd4e7ab0148f67c685f18c585ac220fa562079b Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 17:52:50 +0200 Subject: [PATCH 1473/2155] report: when a report fails, print the json error details When a report upload fails the backend often provides useful details via the varlink error. Show them as part of the upload error message. For now we just dump the json because we have no structure that the backends should follow. We may want to consider adding one (like check for an "error_message" key in the json). But for now this is a nice step forward. --- src/report/report-upload.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/report/report-upload.c b/src/report/report-upload.c index fce0c0e551396..471bcb760600e 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -218,7 +218,11 @@ static int execute_dir_reply( if (error_id) { r = sd_varlink_error_to_errno(error_id, reply); RET_GATHER(context->upload_result, r); - return log_error_errno(r, "Upload via Varlink failed: %s", error_id); + log_error_errno(r, "Upload via Varlink failed: %s", error_id); + if (reply) + (void) sd_json_variant_dump(reply, arg_json_format_flags, + /* f= */ NULL, /* prefix= */ NULL); + return r; } printf("Upload via Varlink was successful; reply: "); From 628ab0040e163e650a5094e6678a56a2a6b234b8 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 5 May 2026 14:55:18 +0200 Subject: [PATCH 1474/2155] report: fold io.systemd.Basic facts into metrics We removed the concept of facts, so we need to update the existing io.systemd.Basic facts provider to metrics. This commit does just that. Its mostly mechanical. This also means that facts.{c,h} and varlink-io.systemd.Facts.{c,h} are gone now. --- src/report/report-basic-server.c | 13 ++- src/report/report-basic.c | 74 +++++++----- src/report/report-basic.h | 6 +- src/shared/facts.c | 151 ------------------------- src/shared/facts.h | 31 ----- src/shared/meson.build | 2 - src/shared/varlink-io.systemd.Facts.c | 37 ------ src/shared/varlink-io.systemd.Facts.h | 6 - test/units/TEST-74-AUX-UTILS.report.sh | 6 +- units/systemd-report-basic.socket | 2 +- units/systemd-report-basic@.service.in | 2 +- 11 files changed, 63 insertions(+), 267 deletions(-) delete mode 100644 src/shared/facts.c delete mode 100644 src/shared/facts.h delete mode 100644 src/shared/varlink-io.systemd.Facts.c delete mode 100644 src/shared/varlink-io.systemd.Facts.h diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index 1e2eca31eae68..51de33efe1783 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -4,12 +4,12 @@ #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-io.systemd.Metrics.h" #include "varlink-util.h" static int vl_server(void) { @@ -20,9 +20,16 @@ static int vl_server(void) { 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); + r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics); if (r < 0) - return log_error_errno(r, "Failed to register Facts varlink interface: %m"); + 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) diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 381262dfd4909..50a4dfaaf1301 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -8,21 +8,22 @@ #include "alloc-util.h" #include "architecture.h" -#include "facts.h" #include "hostname-setup.h" +#include "metrics.h" #include "report-basic.h" #include "virt.h" -static int architecture_generate(FactFamilyContext *context, void *userdata) { +static int architecture_generate(MetricFamilyContext *context, void *userdata) { assert(context); - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - architecture_to_string(uname_architecture())); + architecture_to_string(uname_architecture()), + /* fields= */ NULL); } -static int boot_id_generate(FactFamilyContext *context, void *userdata) { +static int boot_id_generate(MetricFamilyContext *context, void *userdata) { sd_id128_t id; int r; @@ -32,13 +33,14 @@ static int boot_id_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - SD_ID128_TO_STRING(id)); + SD_ID128_TO_STRING(id), + /* fields= */ NULL); } -static int hostname_generate(FactFamilyContext *context, void *userdata) { +static int hostname_generate(MetricFamilyContext *context, void *userdata) { _cleanup_free_ char *hostname = NULL; int r; @@ -48,26 +50,28 @@ static int hostname_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - hostname); + hostname, + /* fields= */ NULL); } -static int kernel_version_generate(FactFamilyContext *context, void *userdata) { +static int kernel_version_generate(MetricFamilyContext *context, void *userdata) { struct utsname u; assert(context); assert_se(uname(&u) >= 0); - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - u.release); + u.release, + /* fields= */ NULL); } -static int machine_id_generate(FactFamilyContext *context, void *userdata) { +static int machine_id_generate(MetricFamilyContext *context, void *userdata) { sd_id128_t id; int r; @@ -77,13 +81,14 @@ static int machine_id_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - SD_ID128_TO_STRING(id)); + SD_ID128_TO_STRING(id), + /* fields= */ NULL); } -static int virtualization_generate(FactFamilyContext *context, void *userdata) { +static int virtualization_generate(MetricFamilyContext *context, void *userdata) { Virtualization v; assert(context); @@ -92,51 +97,58 @@ static int virtualization_generate(FactFamilyContext *context, void *userdata) { if (v < 0) return v; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - virtualization_to_string(v)); + virtualization_to_string(v), + /* fields= */ NULL); } -static const FactFamily fact_family_table[] = { - /* Keep facts ordered alphabetically */ +static const MetricFamily metric_family_table[] = { + /* Keep entries ordered alphabetically */ { - .name = FACT_IO_SYSTEMD_BASIC "Architecture", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Architecture", .description = "CPU architecture", + .type = METRIC_FAMILY_TYPE_STRING, .generate = architecture_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "BootID", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "BootID", .description = "Current boot ID", + .type = METRIC_FAMILY_TYPE_STRING, .generate = boot_id_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "Hostname", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", .description = "System hostname", + .type = METRIC_FAMILY_TYPE_STRING, .generate = hostname_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "KernelVersion", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "KernelVersion", .description = "Kernel version", + .type = METRIC_FAMILY_TYPE_STRING, .generate = kernel_version_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "MachineID", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID", .description = "Machine ID", + .type = METRIC_FAMILY_TYPE_STRING, .generate = machine_id_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "Virtualization", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", .description = "Virtualization type", + .type = METRIC_FAMILY_TYPE_STRING, .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_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(metric_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); +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_list(metric_family_table, link, parameters, flags, userdata); } diff --git a/src/report/report-basic.h b/src/report/report-basic.h index b24613edb62ff..8f123cb17fe3e 100644 --- a/src/report/report-basic.h +++ b/src/report/report-basic.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -#define FACT_IO_SYSTEMD_BASIC "io.systemd.Basic." +#define METRIC_IO_SYSTEMD_BASIC_PREFIX "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); +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/src/shared/facts.c b/src/shared/facts.c deleted file mode 100644 index fa16c7b7cb8ff..0000000000000 --- a/src/shared/facts.c +++ /dev/null @@ -1,151 +0,0 @@ -/* 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" - -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_VARIANT("value", 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 deleted file mode 100644 index 8a8a94cd91f77..0000000000000 --- a/src/shared/facts.h +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 84acaf698b9c4..3072bf2dc7124 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -80,7 +80,6 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'factory-reset.c', - 'facts.c', 'fdisk-util.c', 'fdset.c', 'fido2-util.c', @@ -219,7 +218,6 @@ 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 deleted file mode 100644 index dad1271c7248b..0000000000000 --- a/src/shared/varlink-io.systemd.Facts.c +++ /dev/null @@ -1,37 +0,0 @@ -/* 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.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"), - 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, 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)); - -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 deleted file mode 100644 index ce07de32fb9df..0000000000000 --- a/src/shared/varlink-io.systemd.Facts.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_Facts; diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index e9332806b3791..321d8b1b79a87 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -43,8 +43,12 @@ 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 +# test io.systemd.Basic Metrics systemctl start systemd-report-basic.socket +varlinkctl info /run/systemd/report/io.systemd.Basic +varlinkctl list-methods /run/systemd/report/io.systemd.Basic +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.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 diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket index bfa4ea72568fe..ba5d88c8e7e21 100644 --- a/units/systemd-report-basic.socket +++ b/units/systemd-report-basic.socket @@ -7,7 +7,7 @@ # 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 +Description=Report System Basic Metrics Socket DefaultDependencies=no Before=sockets.target diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in index 043324b5c3987..1e4798ac04bc0 100644 --- a/units/systemd-report-basic@.service.in +++ b/units/systemd-report-basic@.service.in @@ -7,7 +7,7 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. [Unit] -Description=Report System Basic Facts +Description=Report System Basic Metrics DefaultDependencies=no Conflicts=shutdown.target From 4ffb60319bcea88b09afe24736208bd0e0e03618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 30 Apr 2026 20:27:43 +0200 Subject: [PATCH 1475/2155] sysupdate: Ensure that end of the MatchPattern is matched correctly An error snuck into the pattern parsing of the `MatchPattern` key in the sysupdate transfer files. If there's two files "part1-v2.raw", and "part1-v2.raw.tar" in the source folder, and MatchPattern="part1-@v.raw", sysupdate will incorrectly choose "part1-v2.raw.tar" instead of "part1-v2.raw". While the pattern matching works perfectly fine, after the full pattern is successfully matched to the string, we don't ensure that the string actually ends when the pattern just did. This means we can end up choosing a wrong file for the update, if the filename/path happens to start with the same MatchPattern. Fix it by ensuring the string ends after our match pattern ended. --- src/sysupdate/sysupdate-pattern.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sysupdate/sysupdate-pattern.c b/src/sysupdate/sysupdate-pattern.c index 76155dcd2924d..58aa524a5d1ba 100644 --- a/src/sysupdate/sysupdate-pattern.c +++ b/src/sysupdate/sysupdate-pattern.c @@ -426,6 +426,10 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) { p = n; } + /* We matched the whole pattern, but if the string continues over the end of the pattern, refuse */ + if (*p != '\0') + goto nope; + if (ret) { *ret = found; found = (InstanceMetadata) INSTANCE_METADATA_NULL; From a92b60ae17dc44e8f3777a933e3a901bca986ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 18:37:13 +0200 Subject: [PATCH 1476/2155] homectl: fix error handling in shell_is_ok() Fixes f233132a67a4c2c6dff053afac2385f570e8e3fe. --- src/home/homectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 37714aa2b0185..454aa5bfe6bb3 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2837,7 +2837,7 @@ static int shell_is_ok(const char *path, void *userdata) { return false; } - r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL) >= 0; + r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); if (r == -ENOENT) { log_error_errno(r, "Shell '%s' does not exist, try again.", path); return false; From 764d9d5ddbcc355c2f895b6e87c6916e5495ddca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 13:01:16 +0200 Subject: [PATCH 1477/2155] homectl: split out two prompt functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit homectl.c is too long… --- src/home/homectl-prompts.c | 244 ++++++++++++++++++++++++++++++++ src/home/homectl-prompts.h | 5 + src/home/homectl.c | 283 ++++--------------------------------- src/home/meson.build | 1 + 4 files changed, 277 insertions(+), 256 deletions(-) create mode 100644 src/home/homectl-prompts.c create mode 100644 src/home/homectl-prompts.h diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c new file mode 100644 index 0000000000000..71640377e3888 --- /dev/null +++ b/src/home/homectl-prompts.c @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "bitfield.h" +#include "chase.h" +#include "glyph-util.h" +#include "group-record.h" +#include "homectl-prompts.h" +#include "log.h" +#include "parse-util.h" +#include "prompt-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "userdb.h" + +static int acquire_group_list(char ***ret) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **groups = NULL; + UserDBMatch match = USERDB_MATCH_NULL; + int r; + + assert(ret); + + match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); + + r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); + if (r == -ENOLINK) + log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); + else if (r == -ESRCH) + log_debug_errno(r, "No groups found."); + else if (r < 0) + return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); + else + for (;;) { + _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; + + r = groupdb_iterator_get(iterator, &match, &gr); + if (r == -ESRCH) + break; + if (r < 0) + return log_debug_errno(r, "Failed to acquire next group: %m"); + + if (group_record_disposition(gr) == USER_REGULAR) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + /* Filter groups here that belong to a specific user, and are named like them */ + + UserDBMatch user_match = USERDB_MATCH_NULL; + user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + + r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); + if (r < 0 && r != -ESRCH) + return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); + + if (r >= 0 && user_record_gid(ur) == gr->gid) + continue; + } + + r = strv_extend(&groups, gr->group_name); + if (r < 0) + return log_oom(); + } + + strv_sort(groups); + + *ret = TAKE_PTR(groups); + return !!*ret; +} + +static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { + char ***available = userdata; + int r; + + if (!*available) { + r = acquire_group_list(available); + if (r < 0) + log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); + } + + _cleanup_strv_free_ char **l = strv_copy(*available); + if (!l) + return -ENOMEM; + + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(&l, "list"); + if (r < 0) + return r; + } + + *ret_list = TAKE_PTR(l); + return 0; +} + +int prompt_groups(const char *username, char ***ret_groups) { + int r; + + assert(username); + assert(ret_groups); + + _cleanup_strv_free_ char **available = NULL, **groups = NULL; + for (;;) { + strv_sort_uniq(groups); + + if (!strv_isempty(groups)) { + _cleanup_free_ char *j = strv_join(groups, ", "); + if (!j) + return log_oom(); + + log_info("Currently selected groups: %s", j); + } + + _cleanup_free_ char *s = NULL; + r = ask_string_full( + &s, + group_completion_callback, + &available, + "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", + glyph(GLYPH_LABEL), + username); + if (r < 0) + return log_error_errno(r, "Failed to query user for auxiliary group: %m"); + + if (isempty(s)) + break; + + if (streq(s, "list")) { + if (!available) { + r = acquire_group_list(&available); + if (r < 0) + log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); + if (r == 0) + log_notice("Did not find any available groups"); + if (r <= 0) + continue; + } + + r = show_menu(available, + /* n_columns= */ 3, + /* column_width= */ 20, + /* ellipsize_percentage= */ 60, + /* grey_prefix= */ NULL, + /* with_numbers= */ true); + if (r < 0) + return log_error_errno(r, "Failed to show menu: %m"); + + putchar('\n'); + continue; + } + + if (!strv_isempty(available)) { + unsigned u; + r = safe_atou(s, &u); + if (r >= 0) { + if (u <= 0 || u > strv_length(available)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", available[u-1]); + + r = strv_extend(&groups, available[u-1]); + if (r < 0) + return log_oom(); + + continue; + } + } + + if (!valid_user_group_name(s, /* flags= */ 0)) { + log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); + continue; + } + + r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); + if (r == -ESRCH) { + log_notice("Specified auxiliary group does not exist, try again: %s", s); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); + + log_info("Selected '%s'.", s); + + r = strv_extend(&groups, s); + if (r < 0) + return log_oom(); + } + + *ret_groups = TAKE_PTR(groups); + return 0; +} + +static int shell_is_ok(const char *path, void *userdata) { + int r; + + assert(path); + + if (!valid_shell(path)) { + log_error("String '%s' is not a valid path to a shell, refusing.", path); + return false; + } + + r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); + if (r == -ENOENT) { + log_error_errno(r, "Shell '%s' does not exist, try again.", path); + return false; + } + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_error_errno(r, "File '%s' is not executable, try again.", path); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); + + return true; +} + +int prompt_shell(const char *username, char **ret_shell) { + assert(username); + assert(ret_shell); + + _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); + if (!q) + return log_oom(); + + return prompt_loop( + q, + GLYPH_SHELL, + /* menu= */ NULL, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 0, + /* n_columns= */ 3, + /* column_width= */ 20, + shell_is_ok, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, + ret_shell); +} diff --git a/src/home/homectl-prompts.h b/src/home/homectl-prompts.h new file mode 100644 index 0000000000000..04d6460058279 --- /dev/null +++ b/src/home/homectl-prompts.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int prompt_groups(const char *username, char ***ret_groups); +int prompt_shell(const char *username, char **ret_shell); diff --git a/src/home/homectl.c b/src/home/homectl.c index 454aa5bfe6bb3..56e723fbfef9f 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -16,7 +16,6 @@ #include "capability-list.h" #include "capability-util.h" #include "cgroup-util.h" -#include "chase.h" #include "creds-util.h" #include "crypto-util.h" #include "dirent-util.h" @@ -35,6 +34,7 @@ #include "home-util.h" #include "homectl-fido2.h" #include "homectl-pkcs11.h" +#include "homectl-prompts.h" #include "homectl-recovery-key.h" #include "json-util.h" #include "libfido2-util.h" @@ -2643,245 +2643,6 @@ static int has_regular_user(void) { return true; } -static int acquire_group_list(char ***ret) { - _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - _cleanup_strv_free_ char **groups = NULL; - UserDBMatch match = USERDB_MATCH_NULL; - int r; - - assert(ret); - - match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); - - r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); - if (r == -ENOLINK) - log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); - else if (r == -ESRCH) - log_debug_errno(r, "No groups found."); - else if (r < 0) - return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); - else - for (;;) { - _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; - - r = groupdb_iterator_get(iterator, &match, &gr); - if (r == -ESRCH) - break; - if (r < 0) - return log_debug_errno(r, "Failed to acquire next group: %m"); - - if (group_record_disposition(gr) == USER_REGULAR) { - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - - /* Filter groups here that belong to a specific user, and are named like them */ - - UserDBMatch user_match = USERDB_MATCH_NULL; - user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); - - r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); - if (r < 0 && r != -ESRCH) - return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); - - if (r >= 0 && user_record_gid(ur) == gr->gid) - continue; - } - - r = strv_extend(&groups, gr->group_name); - if (r < 0) - return log_oom(); - } - - strv_sort(groups); - - *ret = TAKE_PTR(groups); - return !!*ret; -} - -static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { - char ***available = userdata; - int r; - - if (!*available) { - r = acquire_group_list(available); - if (r < 0) - log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); - } - - _cleanup_strv_free_ char **l = strv_copy(*available); - if (!l) - return -ENOMEM; - - if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { - r = strv_extend(&l, "list"); - if (r < 0) - return r; - } - - *ret_list = TAKE_PTR(l); - return 0; -} - -static int prompt_groups(const char *username, char ***ret_groups) { - int r; - - assert(username); - assert(ret_groups); - - if (!arg_prompt_groups) { - *ret_groups = NULL; - return 0; - } - - putchar('\n'); - - _cleanup_strv_free_ char **available = NULL, **groups = NULL; - for (;;) { - strv_sort_uniq(groups); - - if (!strv_isempty(groups)) { - _cleanup_free_ char *j = strv_join(groups, ", "); - if (!j) - return log_oom(); - - log_info("Currently selected groups: %s", j); - } - - _cleanup_free_ char *s = NULL; - r = ask_string_full( - &s, - group_completion_callback, - &available, - "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", - glyph(GLYPH_LABEL), - username); - if (r < 0) - return log_error_errno(r, "Failed to query user for auxiliary group: %m"); - - if (isempty(s)) - break; - - if (streq(s, "list")) { - if (!available) { - r = acquire_group_list(&available); - if (r < 0) - log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); - if (r == 0) - log_notice("Did not find any available groups"); - if (r <= 0) - continue; - } - - r = show_menu(available, - /* n_columns= */ 3, - /* column_width= */ 20, - /* ellipsize_percentage= */ 60, - /* grey_prefix= */ NULL, - /* with_numbers= */ true); - if (r < 0) - return log_error_errno(r, "Failed to show menu: %m"); - - putchar('\n'); - continue; - }; - - if (!strv_isempty(available)) { - unsigned u; - r = safe_atou(s, &u); - if (r >= 0) { - if (u <= 0 || u > strv_length(available)) { - log_error("Specified entry number out of range."); - continue; - } - - log_info("Selected '%s'.", available[u-1]); - - r = strv_extend(&groups, available[u-1]); - if (r < 0) - return log_oom(); - - continue; - } - } - - if (!valid_user_group_name(s, /* flags= */ 0)) { - log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); - continue; - } - - r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); - if (r == -ESRCH) { - log_notice("Specified auxiliary group does not exist, try again: %s", s); - continue; - } - if (r < 0) - return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); - - log_info("Selected '%s'.", s); - - r = strv_extend(&groups, s); - if (r < 0) - return log_oom(); - } - - *ret_groups = TAKE_PTR(groups); - return 0; -} - -static int shell_is_ok(const char *path, void *userdata) { - int r; - - assert(path); - - if (!valid_shell(path)) { - log_error("String '%s' is not a valid path to a shell, refusing.", path); - return false; - } - - r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); - if (r == -ENOENT) { - log_error_errno(r, "Shell '%s' does not exist, try again.", path); - return false; - } - if (ERRNO_IS_NEG_PRIVILEGE(r)) { - log_error_errno(r, "File '%s' is not executable, try again.", path); - return false; - } - if (r < 0) - return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); - - return true; -} - -static int prompt_shell(const char *username, char **ret_shell) { - assert(username); - assert(ret_shell); - - if (!arg_prompt_shell) { - *ret_shell = NULL; - return 0; - } - - putchar('\n'); - - _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); - if (!q) - return log_oom(); - - return prompt_loop( - q, - GLYPH_SHELL, - /* menu= */ NULL, - /* accepted= */ NULL, - /* ellipsize_percentage= */ 0, - /* n_columns= */ 3, - /* column_width= */ 20, - shell_is_ok, - /* refresh= */ NULL, - /* userdata= */ NULL, - PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, - ret_shell); -} - static int username_is_ok(const char *name, void *userdata) { int r; @@ -2963,30 +2724,40 @@ static int create_interactively(void) { if (r < 0) return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); - _cleanup_strv_free_ char **groups = NULL; - r = prompt_groups(username, &groups); - if (r < 0) - return r; + if (arg_prompt_groups) { + _cleanup_strv_free_ char **groups = NULL; - if (!strv_isempty(groups)) { - strv_sort_uniq(groups); + putchar('\n'); - r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + r = prompt_groups(username, &groups); if (r < 0) - return log_error_errno(r, "Failed to set memberOf field: %m"); + return r; + + if (!strv_isempty(groups)) { + strv_sort_uniq(groups); + + r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + if (r < 0) + return log_error_errno(r, "Failed to set memberOf field: %m"); + } } - _cleanup_free_ char *shell = NULL; - r = prompt_shell(username, &shell); - if (r < 0) - return r; + if (arg_prompt_shell) { + _cleanup_free_ char *shell = NULL; - if (!isempty(shell)) { - log_info("Selected %s as the shell for user %s", shell, username); + putchar('\n'); - r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + r = prompt_shell(username, &shell); if (r < 0) - return log_error_errno(r, "Failed to set shell field: %m"); + return r; + + if (!isempty(shell)) { + log_info("Selected %s as the shell for user %s", shell, username); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + if (r < 0) + return log_error_errno(r, "Failed to set shell field: %m"); + } } putchar('\n'); diff --git a/src/home/meson.build b/src/home/meson.build index 53c5675c83f88..8c644842c1470 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -47,6 +47,7 @@ systemd_homed_sources += [homed_gperf_c] homectl_sources = files( 'homectl-fido2.c', 'homectl-pkcs11.c', + 'homectl-prompts.c', 'homectl-recovery-key.c', 'homectl.c', ) From 392846b370dcde7142f854ac38c15292522966a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 13:21:30 +0200 Subject: [PATCH 1478/2155] test-homectl-prompts: add manual test to exercise prompt functionality The prompt for groups is nice. The prompt for a shell could use some love. Looking at this is much easier if we can invoke the code outside in isolation. I wrote this when looking at https://github.com/systemd/systemd/pull/41947, where I wanted to see how the homectl prompt works with the changes. --- src/home/meson.build | 11 +++- src/home/test-homectl-prompts.c | 106 ++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/home/test-homectl-prompts.c diff --git a/src/home/meson.build b/src/home/meson.build index 8c644842c1470..600f00b4ac997 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -47,11 +47,14 @@ systemd_homed_sources += [homed_gperf_c] homectl_sources = files( 'homectl-fido2.c', 'homectl-pkcs11.c', - 'homectl-prompts.c', 'homectl-recovery-key.c', 'homectl.c', ) +homectl_extract = files( + 'homectl-prompts.c', +) + pam_systemd_home_sources = files( 'home-util.c', 'pam_systemd_home.c', @@ -86,6 +89,7 @@ executables += [ 'name' : 'homectl', 'public' : true, 'sources' : homectl_sources, + 'extract' : homectl_extract, 'objects' : ['systemd-homed'], 'dependencies' : [ libdl, @@ -94,6 +98,11 @@ executables += [ threads, ], }, + test_template + { + 'sources' : files('test-homectl-prompts.c'), + 'objects' : ['homectl'], + 'type' : 'manual', + }, test_template + { 'sources' : files('test-homed-regression-31896.c'), 'type' : 'manual', diff --git a/src/home/test-homectl-prompts.c b/src/home/test-homectl-prompts.c new file mode 100644 index 0000000000000..aaa81cc78a89d --- /dev/null +++ b/src/home/test-homectl-prompts.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "format-table.h" +#include "help-util.h" +#include "homectl-prompts.h" +#include "main-func.h" +#include "options.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "verbs.h" + +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + 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); + + help_cmdline("[OPTIONS...] VERB [USERNAME]"); + help_abstract("Exercise homectl prompt functions in isolation."); + + help_section("Verbs"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + return table_print_or_warn(options); +} + +VERB(verb_groups, "groups", "[USER]", VERB_ANY, 2, 0, "Select groups"); +static int verb_groups(int argc, char *argv[], uintptr_t _data, void *userdata) { + assert(argv); + + const char *username = argv[1] ?: "test"; + int r; + + _cleanup_strv_free_ char **t = NULL; + + r = prompt_groups(username, &t); + if (r < 0) + return r; + + _cleanup_free_ char *s = ASSERT_PTR(strv_join(t, ", ")); + log_info("groups: %s → %s", username, s); + return 0; +} + +VERB(verb_shell, "shell", "[USER]", VERB_ANY, 2, 0, "Select shell"); +static int verb_shell(int argc, char *argv[], uintptr_t _data, void *userdata) { + assert(argv); + + const char *username = argv[1] ?: "test"; + int r; + + _cleanup_free_ char *s = NULL; + + r = prompt_shell(username, &s); + if (r < 0) + return r; + + log_info("shell: %s → %s", username, strnull(s)); + return 0; +} + +static int parse_argv(int argc, char **argv, char ***remaining_args) { + assert(argc >= 0); + assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + } + + *remaining_args = option_parser_get_args(&opts); + return 1; +} + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + char **args = NULL; + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + return dispatch_verb_with_args(args, /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); From c379621426a9a1b244320d5b331f634d9a46126d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 22:08:19 +0200 Subject: [PATCH 1479/2155] homectl: drop redunant sort Claude points out that prompt_groups() already does the sort in every loop, including the last. --- src/home/homectl.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 56e723fbfef9f..ce54da0d8c2b7 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2734,8 +2734,6 @@ static int create_interactively(void) { return r; if (!strv_isempty(groups)) { - strv_sort_uniq(groups); - r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); if (r < 0) return log_error_errno(r, "Failed to set memberOf field: %m"); From 852fcf5134db04fccc4988decc4c5e8a33914700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Sarasola?= Date: Tue, 5 May 2026 21:20:59 +0200 Subject: [PATCH 1480/2155] hwdb: add SOUND_FORM_FACTOR for Edifier M60 and Fractal Scape Dongle --- hwdb.d/70-sound-card.hwdb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hwdb.d/70-sound-card.hwdb b/hwdb.d/70-sound-card.hwdb index f9ceeacb7d79c..340e6b54e3c5e 100644 --- a/hwdb.d/70-sound-card.hwdb +++ b/hwdb.d/70-sound-card.hwdb @@ -20,6 +20,13 @@ # Allowed properties are: # SOUND_FORM_FACTOR +########################################################### +# Bestechnic (Edifier) +########################################################### +# Edifier M60 +usb:v2D99pA094* + SOUND_FORM_FACTOR=speaker + ########################################################### # Corsair ########################################################### @@ -27,6 +34,13 @@ usb:v1B1Cp0A51* SOUND_FORM_FACTOR=headset +########################################################### +# Fractal +########################################################### +# Fractal Scape Dongle +usb:v36BCp0001* + SOUND_FORM_FACTOR=headset + ########################################################### # Microsoft ########################################################### From fc68ee611886c4f2d7d5bccdcfd700c5f28ed2d1 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Tue, 5 May 2026 21:55:30 +0100 Subject: [PATCH 1481/2155] sd-boot: efi-log: fix `__stack_chk_guard` type In https://gcc.gnu.org/PR121911 `gcc` started enforcing the type of `__stack_chk_guard` to `uintptr_t` and broke `systemd` build as: ``` ../src/boot/efi-log.c:136:17: error: conflicting types for '__stack_chk_guard'; have 'intptr_t' {aka 'long int'} 136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; | ^~~~~~~~~~~~~~~~~ cc1: note: previous declaration of '__stack_chk_guard' with type 'long unsigned int' ../src/boot/efi-log.c:136:17: error: declaration of '__stack_chk_guard' shadows a global declaration [-Werror=shadow] 136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; | ^~~~~~~~~~~~~~~~~ ``` Let's match the declaration to unsigned type as suggested by upstream in https://gcc.gnu.org/PR121911#c6. --- src/boot/efi-log.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/efi-log.c b/src/boot/efi-log.c index ed0a2746933e0..520f985389c55 100644 --- a/src/boot/efi-log.c +++ b/src/boot/efi-log.c @@ -133,7 +133,7 @@ void log_wait(void) { } // NOLINTNEXTLINE(misc-use-internal-linkage) -_used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; +_used_ uintptr_t __stack_chk_guard = (uintptr_t) 0x70f6967de78acae3; /* We can only set a random stack canary if this function attribute is available, * otherwise this may create a stack check fail. */ @@ -144,7 +144,7 @@ void __stack_chk_guard_init(void) { (void) rng->GetRNG(rng, NULL, sizeof(__stack_chk_guard), (void *) &__stack_chk_guard); else /* Better than no extra entropy. */ - __stack_chk_guard ^= (intptr_t) __executable_start; + __stack_chk_guard ^= (uintptr_t) __executable_start; } #endif From eaa0073027b06c384b5f5e9cb57ec850ea024728 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 14:58:53 +0200 Subject: [PATCH 1482/2155] user-util,storagectl: introduce USERNS_RANGE_SIZE macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mount.storage helper open-codes the conventional 64K UID/GID delegation block size as 0x10000 / 0x10000U in four places. Several other places in the tree do the same (nspawn's arg_uid_range default, homed's mount setup, …), but with no shared name. Add USERNS_RANGE_SIZE in user-util.h alongside UID_NOBODY and friends, and switch storagectl over to it. Other call sites can adopt it incrementally. Signed-off-by: Christian Brauner (Amutable) --- src/basic/user-util.h | 4 ++++ src/shared/nsresource.h | 3 ++- src/storage/storagectl.c | 12 ++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 003420dbe3b0d..a8902aca6150b 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -90,6 +90,10 @@ int take_etc_passwd_lock(const char *root); #define UID_NOBODY ((uid_t) 65534U) #define GID_NOBODY ((gid_t) 65534U) +/* Conventional size of a user-namespace UID/GID delegation block (64K). + * Untyped so it can be used in both UID and GID contexts without casts. */ +#define USERNS_RANGE_SIZE 0x10000U + /* If REMOUNT_IDMAPPING_HOST_ROOT is set for remount_idmap() we'll include a mapping here that maps the host * root user accessing the idmapped mount to the this user ID on the backing fs. This is the last valid UID in * the *signed* 32-bit range. You might wonder why precisely use this specific UID for this purpose? Well, we diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h index 5633fd9bf35bc..c26dd4f8a553f 100644 --- a/src/shared/nsresource.h +++ b/src/shared/nsresource.h @@ -2,9 +2,10 @@ #pragma once #include "shared-forward.h" +#include "user-util.h" /* Helpful constants for the only numbers of UIDs that can currently be allocated */ -#define NSRESOURCE_UIDS_64K 0x10000U +#define NSRESOURCE_UIDS_64K USERNS_RANGE_SIZE #define NSRESOURCE_UIDS_1 1U int nsresource_connect(sd_varlink **ret); diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index f88dff29bc861..23d91d6ba12ee 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -723,25 +723,25 @@ static int run_as_mount_helper(int argc, char *argv[]) { if (!uid_is_valid(p.base_uid) || !gid_is_valid(p.base_gid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provider did not report base UID/GID, cannot mount."); - if (p.base_uid > UINT32_MAX - 0x10000U || - p.base_gid > UINT32_MAX - 0x10000U) + if (p.base_uid > UINT32_MAX - USERNS_RANGE_SIZE || + p.base_gid > UINT32_MAX - USERNS_RANGE_SIZE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Returned base UID/GID out of range."); r = stat_verify_directory(&st); if (r < 0) return log_error_errno(r, "File descriptor for directory volume is not a directory inode: %m"); - if (st.st_uid < p.base_uid || st.st_uid >= p.base_uid + 0x10000 || - st.st_gid < p.base_gid || st.st_gid >= p.base_gid + 0x10000) + if (st.st_uid < p.base_uid || st.st_uid >= p.base_uid + USERNS_RANGE_SIZE || + st.st_gid < p.base_gid || st.st_gid >= p.base_gid + USERNS_RANGE_SIZE) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File descriptor for directory volume is not owned by base UID/GID range, refusing."); /* Now move the mount into our own UID/GID range */ _cleanup_free_ char *uid_line = asprintf_safe( UID_FMT " " UID_FMT " " UID_FMT "\n", - p.base_uid, (uid_t) 0, (uid_t) 0x10000); + p.base_uid, (uid_t) 0, USERNS_RANGE_SIZE); _cleanup_free_ char *gid_line = asprintf_safe( GID_FMT " " GID_FMT " " GID_FMT "\n", - p.base_gid, (gid_t) 0, (gid_t) 0x10000); + p.base_gid, (gid_t) 0, USERNS_RANGE_SIZE); if (!uid_line || !gid_line) return log_oom(); From efb0a0fa7a4c4f25a5abb5df60f4813951279772 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:29:56 +0200 Subject: [PATCH 1483/2155] shared: move storage-util to libshared The storage backend providers (block, fs) and storagectl currently each extract storage-util.c into their target. Several upcoming consumers (machine-util's BindVolume parser, vmspawn's hotplug glue, machinectl's new bind-volume verbs) need the StorageProvider type/string-table helpers and a future shared Acquire client helper. Move storage-util.{c,h} to src/shared so libshared exports the symbols once and every consumer (storage providers, storagectl, libshared itself) picks them up by linking libshared. Drop the now-redundant 'extract'/'objects' wiring in src/storage/meson.build. No code changes; this is purely a relocation. Signed-off-by: Christian Brauner (Amutable) --- src/shared/meson.build | 1 + src/{storage => shared}/storage-util.c | 0 src/{storage => shared}/storage-util.h | 0 src/storage/meson.build | 3 --- 4 files changed, 1 insertion(+), 3 deletions(-) rename src/{storage => shared}/storage-util.c (100%) rename src/{storage => shared}/storage-util.h (100%) diff --git a/src/shared/meson.build b/src/shared/meson.build index 0529d35d26586..d4cd5ca431233 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -199,6 +199,7 @@ shared_sources = files( 'socket-netlink.c', 'specifier.c', 'ssl-util.c', + 'storage-util.c', 'switch-root.c', 'swtpm-util.c', 'tar-util.c', diff --git a/src/storage/storage-util.c b/src/shared/storage-util.c similarity index 100% rename from src/storage/storage-util.c rename to src/shared/storage-util.c diff --git a/src/storage/storage-util.h b/src/shared/storage-util.h similarity index 100% rename from src/storage/storage-util.h rename to src/shared/storage-util.h diff --git a/src/storage/meson.build b/src/storage/meson.build index 21456141dec8c..bcd68e612da9b 100644 --- a/src/storage/meson.build +++ b/src/storage/meson.build @@ -4,18 +4,15 @@ executables += [ libexec_template + { 'name' : 'systemd-storage-block', 'sources' : files('storage-block.c'), - 'extract' : files('storage-util.c') }, libexec_template + { 'name' : 'systemd-storage-fs', 'sources' : files('storage-fs.c'), - 'objects' : ['systemd-storage-block'], }, executable_template + { 'name' : 'storagectl', 'public' : true, 'sources' : files('storagectl.c'), - 'objects' : ['systemd-storage-block'], }, ] From 32a80416b67a44c8944d2b20496003caf6d9add3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:32:27 +0200 Subject: [PATCH 1484/2155] shared: add BindVolume parser in machine-util MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a universal parser for the colon-separated grammar 'PROVIDER:VOLUME[:CONFIG][:K=V,K=V,…]' that backs --bind-volume on systemd-vmspawn (next), machinectl bind-volume, and the future nspawn + service-manager BindVolume= integrations. The 'config' field is opaque to shared code and interpreted per backend (vmspawn: a DiskType name, future nspawn: a mount path). The trailing key=value list is parsed into the io.systemd.StorageProvider .Acquire() parameters (template, create, read-only/ro, size/create-size and request-as), with values validated against the existing storage-util enums and validators. Provider/volume names are checked with storage_provider_name_is_valid() and storage_volume_name_is_valid(); the combined ":" string is also validated as string_is_safe so it is safe to use as a QEMU device id. Add a test-machine-util unit test covering the happy paths plus a handful of malformed inputs. Signed-off-by: Christian Brauner (Amutable) --- src/shared/machine-util.c | 177 +++++++++++++++++++++++++++++++++++ src/shared/machine-util.h | 54 +++++++++++ src/test/meson.build | 1 + src/test/test-machine-util.c | 144 ++++++++++++++++++++++++++++ 4 files changed, 376 insertions(+) create mode 100644 src/test/test-machine-util.c diff --git a/src/shared/machine-util.c b/src/shared/machine-util.c index fa5e46ace1e53..43a4fdfdd81b9 100644 --- a/src/shared/machine-util.c +++ b/src/shared/machine-util.c @@ -4,7 +4,11 @@ #include "extract-word.h" #include "machine-util.h" #include "parse-argument.h" +#include "parse-util.h" +#include "storage-util.h" #include "string-table.h" +#include "string-util.h" +#include "strv.h" static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { [IMAGE_FORMAT_RAW] = "raw", @@ -13,6 +17,14 @@ static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); +static const char *const read_only_mode_table[_READ_ONLY_MAX] = { + [READ_ONLY_NO] = "no", + [READ_ONLY_YES] = "yes", + [READ_ONLY_AUTO] = "auto", +}; + +DEFINE_STRING_TABLE_LOOKUP(read_only_mode, ReadOnlyMode); + static const char *const disk_type_table[_DISK_TYPE_MAX] = { [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", @@ -100,3 +112,168 @@ int parse_disk_spec( *ret_path = TAKE_PTR(path); return 0; } + +BindVolume* bind_volume_free(BindVolume *v) { + if (!v) + return NULL; + + free(v->provider); + free(v->volume); + free(v->config); + free(v->template); + + return mfree(v); +} + +static int bind_volume_apply_extra(BindVolume *v, const char *key, const char *value) { + int r; + + assert(v); + assert(key); + assert(value); + + if (streq(key, "template")) { + if (v->template) + return -EINVAL; + if (!storage_template_name_is_valid(value)) + return -EINVAL; + r = free_and_strdup(&v->template, value); + if (r < 0) + return r; + return 0; + } + + if (streq(key, "create")) { + if (v->create_mode >= 0) + return -EINVAL; + CreateMode m = create_mode_from_string(value); + if (m < 0) + return m; + v->create_mode = m; + return 0; + } + + if (STR_IN_SET(key, "read-only", "ro")) { + if (v->read_only >= 0) + return -EINVAL; + ReadOnlyMode m = read_only_mode_from_string(value); + if (m < 0) { + r = parse_boolean(value); + if (r < 0) + return r; + m = r ? READ_ONLY_YES : READ_ONLY_NO; + } + v->read_only = m; + return 0; + } + + if (STR_IN_SET(key, "size", "create-size")) { + if (v->create_size_bytes != UINT64_MAX) + return -EINVAL; + uint64_t sz; + r = parse_size(value, 1024, &sz); + if (r < 0) + return r; + if (sz == 0) + return -EINVAL; + v->create_size_bytes = sz; + return 0; + } + + if (streq(key, "request-as")) { + if (v->request_as >= 0) + return -EINVAL; + VolumeType t = volume_type_from_string(value); + if (t < 0) + return t; + v->request_as = t; + return 0; + } + + return -EINVAL; +} + +int bind_volume_parse(const char *arg, BindVolume **ret) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + int r; + + assert(arg); + assert(ret); + + v = new(BindVolume, 1); + if (!v) + return -ENOMEM; + + *v = BIND_VOLUME_INIT; + + const char *p = arg; + _cleanup_free_ char *provider = NULL, *volume = NULL, *config = NULL; + + r = extract_first_word(&p, &provider, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(provider) || !storage_provider_name_is_valid(provider)) + return -EINVAL; + + r = extract_first_word(&p, &volume, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(volume) || !storage_volume_name_is_valid(volume)) + return -EINVAL; + + r = extract_first_word(&p, &config, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + + v->provider = TAKE_PTR(provider); + v->volume = TAKE_PTR(volume); + if (!isempty(config)) { + if (!string_is_safe(config, /* flags= */ 0)) + return -EINVAL; + v->config = TAKE_PTR(config); + } + + for (;;) { + _cleanup_free_ char *kv = NULL, *key = NULL, *value = NULL; + + r = extract_first_word(&p, &kv, ",", 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = split_pair(kv, "=", &key, &value); + if (r < 0) + return r; + if (isempty(key)) + return -EINVAL; + + r = bind_volume_apply_extra(v, key, value); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int machine_storage_name_split(const char *s, char **ret_provider, char **ret_volume) { + _cleanup_free_ char *p = NULL, *v = NULL; + int r; + + if (isempty(s)) + return -EINVAL; + + r = split_pair(s, ":", &p, &v); + if (r < 0) + return r; + + if (!storage_provider_name_is_valid(p) || !storage_volume_name_is_valid(v)) + return -EINVAL; + + if (ret_provider) + *ret_provider = TAKE_PTR(p); + if (ret_volume) + *ret_volume = TAKE_PTR(v); + return 0; +} diff --git a/src/shared/machine-util.h b/src/shared/machine-util.h index 3937ce170377e..a992e24448076 100644 --- a/src/shared/machine-util.h +++ b/src/shared/machine-util.h @@ -2,6 +2,7 @@ #pragma once #include "shared-forward.h" +#include "storage-util.h" typedef enum ImageFormat { IMAGE_FORMAT_RAW, @@ -30,3 +31,56 @@ int parse_disk_spec( ImageFormat *format, DiskType *disk_type, char **ret_path); + +typedef enum ReadOnlyMode { + READ_ONLY_NO, + READ_ONLY_YES, + READ_ONLY_AUTO, + _READ_ONLY_MAX, + _READ_ONLY_INVALID = -EINVAL, +} ReadOnlyMode; + +DECLARE_STRING_TABLE_LOOKUP(read_only_mode, ReadOnlyMode); + +/* Map ReadOnlyMode onto the Acquire() wire tristate (-1 unset/auto, 0 no, 1 yes). */ +static inline int read_only_mode_to_tristate(ReadOnlyMode m) { + switch (m) { + case READ_ONLY_NO: return 0; + case READ_ONLY_YES: return 1; + default: return -1; + } +} + +/* Parsed "PROVIDER:VOLUME[:CONFIG][:K=V,K=V,...]" used by --bind-volume, + * machinectl bind-volume, and (future) the BindVolume= unit setting. The 'config' + * field is opaque here and interpreted per-backend (vmspawn: a DiskType name; + * nspawn: a mount path). */ +typedef struct BindVolume { + char *provider; + char *volume; + char *config; + + /* Acquire() parameters parsed from the trailing key=value list. */ + char *template; + CreateMode create_mode; + ReadOnlyMode read_only; + uint64_t create_size_bytes; + VolumeType request_as; +} BindVolume; + +#define BIND_VOLUME_INIT \ + (BindVolume) { \ + .create_mode = _CREATE_MODE_INVALID, \ + .read_only = _READ_ONLY_INVALID, \ + .create_size_bytes = UINT64_MAX, \ + .request_as = _VOLUME_TYPE_INVALID, \ + } + +BindVolume* bind_volume_free(BindVolume *v); +DEFINE_TRIVIAL_CLEANUP_FUNC(BindVolume*, bind_volume_free); + +int bind_volume_parse(const char *arg, BindVolume **ret); + +/* Validate a ":" binding name as used by AddStorage/RemoveStorage. + * ret_provider/ret_volume may each be NULL when the caller only wants validation. */ +int machine_storage_name_split(const char *s, char **ret_provider, char **ret_volume); diff --git a/src/test/meson.build b/src/test/meson.build index 09c367d3074f3..f4288119f94ba 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -141,6 +141,7 @@ simple_tests += files( 'test-log.c', 'test-logarithm.c', 'test-login-util.c', + 'test-machine-util.c', 'test-macro.c', 'test-memfd-util.c', 'test-memory-util.c', diff --git a/src/test/test-machine-util.c b/src/test/test-machine-util.c new file mode 100644 index 0000000000000..8774be2189228 --- /dev/null +++ b/src/test/test-machine-util.c @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "machine-util.h" +#include "tests.h" + +TEST(bind_volume_parse_minimal) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sda", &v)); + ASSERT_STREQ(v->provider, "block"); + ASSERT_STREQ(v->volume, "/dev/sda"); + ASSERT_NULL(v->config); + ASSERT_NULL(v->template); + ASSERT_EQ(v->create_mode, _CREATE_MODE_INVALID); + ASSERT_EQ(v->request_as, _VOLUME_TYPE_INVALID); + ASSERT_EQ(v->read_only, _READ_ONLY_INVALID); + ASSERT_EQ(v->create_size_bytes, UINT64_MAX); +} + +TEST(bind_volume_parse_with_config) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sda:virtio-scsi", &v)); + ASSERT_STREQ(v->provider, "block"); + ASSERT_STREQ(v->volume, "/dev/sda"); + ASSERT_STREQ(v->config, "virtio-scsi"); +} + +TEST(bind_volume_parse_empty_config) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("fs:vol-1::create=new,size=64M,template=sparse-file", &v)); + ASSERT_STREQ(v->provider, "fs"); + ASSERT_STREQ(v->volume, "vol-1"); + ASSERT_NULL(v->config); + ASSERT_EQ(v->create_mode, CREATE_NEW); + ASSERT_STREQ(v->template, "sparse-file"); + ASSERT_EQ(v->create_size_bytes, UINT64_C(64) * 1024 * 1024); +} + +TEST(bind_volume_parse_full) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse( + "fs:vol-2:nvme:create=any,template=allocated-file,size=128M,ro=auto,request-as=blk", + &v)); + ASSERT_STREQ(v->provider, "fs"); + ASSERT_STREQ(v->volume, "vol-2"); + ASSERT_STREQ(v->config, "nvme"); + ASSERT_EQ(v->create_mode, CREATE_ANY); + ASSERT_STREQ(v->template, "allocated-file"); + ASSERT_EQ(v->request_as, VOLUME_BLK); + ASSERT_EQ(v->create_size_bytes, UINT64_C(128) * 1024 * 1024); + ASSERT_EQ(v->read_only, READ_ONLY_AUTO); +} + +TEST(bind_volume_parse_read_only) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sdb:scsi-cd:read-only=yes", &v)); + ASSERT_EQ(v->read_only, READ_ONLY_YES); + + v = bind_volume_free(v); + ASSERT_OK(bind_volume_parse("block:/dev/sdb:scsi-cd:ro=no", &v)); + ASSERT_EQ(v->read_only, READ_ONLY_NO); +} + +TEST(bind_volume_parse_invalid) { + BindVolume *v = NULL; + + /* Missing provider */ + ASSERT_ERROR(bind_volume_parse(":vol", &v), EINVAL); + ASSERT_NULL(v); + + /* Missing volume */ + ASSERT_ERROR(bind_volume_parse("block:", &v), EINVAL); + ASSERT_NULL(v); + + /* Provider with control char */ + ASSERT_ERROR(bind_volume_parse("bl\x01ock:vol", &v), EINVAL); + ASSERT_NULL(v); + + /* Config with control char */ + ASSERT_ERROR(bind_volume_parse("block:vol:nv\x01me", &v), EINVAL); + ASSERT_NULL(v); + + /* Unknown extras key */ + ASSERT_ERROR(bind_volume_parse("block:vol::bogus=foo", &v), EINVAL); + ASSERT_NULL(v); + + /* Bogus create mode */ + ASSERT_ERROR(bind_volume_parse("block:vol::create=bogus", &v), EINVAL); + ASSERT_NULL(v); + + /* Bogus request-as */ + ASSERT_ERROR(bind_volume_parse("block:vol::request-as=bogus", &v), EINVAL); + ASSERT_NULL(v); + + /* Extras entry without '=' */ + ASSERT_ERROR(bind_volume_parse("block:vol::nokey", &v), EINVAL); + ASSERT_NULL(v); + + /* Empty key (=value with no key) */ + ASSERT_ERROR(bind_volume_parse("block:vol::=value", &v), EINVAL); + ASSERT_NULL(v); + + /* Duplicate key */ + ASSERT_ERROR(bind_volume_parse("block:vol::create=new,create=any", &v), EINVAL); + ASSERT_NULL(v); + + /* Aliased duplicate (size / create-size) */ + ASSERT_ERROR(bind_volume_parse("block:vol::size=64M,create-size=128M", &v), EINVAL); + ASSERT_NULL(v); + + /* Zero-byte size */ + ASSERT_ERROR(bind_volume_parse("block:vol::size=0", &v), EINVAL); + ASSERT_NULL(v); + + /* Duplicate read-only with explicit yes/no values */ + ASSERT_ERROR(bind_volume_parse("block:vol::read-only=yes,read-only=no", &v), EINVAL); + ASSERT_NULL(v); + ASSERT_ERROR(bind_volume_parse("block:vol::read-only=yes,ro=auto", &v), EINVAL); + ASSERT_NULL(v); +} + +TEST(machine_storage_name_split) { + _cleanup_free_ char *p = NULL, *v = NULL; + + ASSERT_OK(machine_storage_name_split("block:/dev/sda", &p, &v)); + ASSERT_STREQ(p, "block"); + ASSERT_STREQ(v, "/dev/sda"); + + /* NULL outputs — validate-only mode */ + ASSERT_OK(machine_storage_name_split("fs:vol-1", NULL, NULL)); + + ASSERT_ERROR(machine_storage_name_split(NULL, NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("no-colon", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split(":vol", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("block:", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("bl\x01ock:vol", NULL, NULL), EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); From fad897b8bf5f940f0ba04c4f3742f16d2bb05390 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:31:21 +0200 Subject: [PATCH 1485/2155] shared: add storage_acquire_volume() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit storagectl's mount.storage helper bundles "open StorageProvider socket + Acquire() + dispatch reply + take fd" inline. Future consumers (systemd-vmspawn boot-time --bind-volume, machinectl bind-volume) need the same dance. Factor it into a single libshared helper that takes the Acquire() parameters by value and returns the fd plus the actual type/read-only flags. Library code, so no logging — varlink errors are surfaced via sd_varlink_error_to_errno() and the StorageProvider error_id is returned to the caller via reterr_error_id (caller decides how to format messages). Signed-off-by: Christian Brauner (Amutable) --- src/libsystemd/sd-json/json-util.h | 2 + src/shared/storage-util.c | 107 +++++++++++++++++++++++++++++ src/shared/storage-util.h | 29 ++++++++ 3 files changed, 138 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index cea2d368b43db..0db1e445e62ac 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -268,6 +268,8 @@ enum { SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_UNSIGNED(value)) #define JSON_BUILD_PAIR_CONDITION_BOOLEAN(condition, name, value) \ SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_BOOLEAN(value)) +#define JSON_BUILD_PAIR_CONDITION_STRING(condition, name, value) \ + SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_STRING(value)) #define JSON_BUILD_PAIR_CONDITION_STRV(condition, name, value) \ SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_STRV(value)) diff --git a/src/shared/storage-util.c b/src/shared/storage-util.c index 793946c03a63e..3a3c1e6c57d3f 100644 --- a/src/shared/storage-util.c +++ b/src/shared/storage-util.c @@ -1,6 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "fd-util.h" #include "json-util.h" +#include "machine-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "runtime-scope.h" #include "string-table.h" #include "storage-util.h" @@ -21,3 +29,102 @@ DEFINE_STRING_TABLE_LOOKUP(create_mode, CreateMode); JSON_DISPATCH_ENUM_DEFINE(json_dispatch_volume_type, VolumeType, volume_type_from_string); JSON_DISPATCH_ENUM_DEFINE(json_dispatch_create_mode, CreateMode, create_mode_from_string); + +void storage_acquire_reply_done(StorageAcquireReply *reply) { + if (!reply) + return; + + reply->fd = safe_close(reply->fd); +} + +int storage_acquire_volume( + RuntimeScope scope, + const BindVolume *bv, + bool allow_interactive_auth, + char **reterr_error_id, + StorageAcquireReply *ret) { + + int r; + + assert(bv); + assert(bv->provider); + assert(bv->volume); + assert(ret); + + /* Defense-in-depth: this is a libshared helper that may grow new callers; reject + * provider names that could escape the StorageProvider runtime directory. */ + if (!storage_provider_name_is_valid(bv->provider)) + return -EINVAL; + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return r; + + if (!path_extend(&socket_path, bv->provider)) + return -ENOMEM; + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, socket_path); + if (r < 0) + return r; + + r = sd_varlink_set_allow_fd_passing_input(link, true); + if (r < 0) + return r; + + sd_json_variant *mreply = NULL; + const char *merror_id = NULL; + r = sd_varlink_callbo( + link, + "io.systemd.StorageProvider.Acquire", + &mreply, + &merror_id, + SD_JSON_BUILD_PAIR_STRING("name", bv->volume), + JSON_BUILD_PAIR_CONDITION_STRING(bv->create_mode >= 0, "createMode", create_mode_to_string(bv->create_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("template", bv->template), + JSON_BUILD_PAIR_TRISTATE_NON_NULL("readOnly", read_only_mode_to_tristate(bv->read_only)), + JSON_BUILD_PAIR_CONDITION_STRING(bv->request_as >= 0, "requestAs", volume_type_to_string(bv->request_as)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("createSizeBytes", bv->create_size_bytes, UINT64_MAX), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", allow_interactive_auth)); + if (r < 0) + return r; + + if (merror_id) { + if (reterr_error_id) { + char *copy = strdup(merror_id); + if (!copy) + return -ENOMEM; + *reterr_error_id = copy; + } + + r = sd_varlink_error_to_errno(merror_id, mreply); + return r == -EBADR ? -EPROTO : r; + } + + /* tmp.fd holds the JSON fd index until sd_varlink_take_fd() swaps it for the real fd. */ + StorageAcquireReply tmp = STORAGE_ACQUIRE_REPLY_INIT; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(StorageAcquireReply, fd), SD_JSON_MANDATORY }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(StorageAcquireReply, read_only), 0 }, + { "type", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(StorageAcquireReply, type), SD_JSON_MANDATORY }, + { "baseUID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(StorageAcquireReply, base_uid), 0 }, + { "baseGID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(StorageAcquireReply, base_gid), 0 }, + {} + }; + + r = sd_json_dispatch(mreply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &tmp); + if (r < 0) + return r; + if (tmp.fd < 0) + return -EBADMSG; + + _cleanup_close_ int fd = sd_varlink_take_fd(link, tmp.fd); + if (fd < 0) + return fd; + + tmp.fd = TAKE_FD(fd); + *ret = tmp; + return 0; +} diff --git a/src/shared/storage-util.h b/src/shared/storage-util.h index f7a62aeec0835..b37515bedbd43 100644 --- a/src/shared/storage-util.h +++ b/src/shared/storage-util.h @@ -41,3 +41,32 @@ static inline bool storage_template_name_is_valid(const char *n) { static inline bool storage_provider_name_is_valid(const char *n) { return string_is_safe(n, STRING_FILENAME); } + +typedef struct StorageAcquireReply { + int fd; + VolumeType type; + int read_only; + uid_t base_uid; + gid_t base_gid; +} StorageAcquireReply; + +#define STORAGE_ACQUIRE_REPLY_INIT \ + (StorageAcquireReply) { \ + .fd = -EBADF, \ + .type = _VOLUME_TYPE_INVALID, \ + .read_only = -1, \ + .base_uid = UID_INVALID, \ + .base_gid = GID_INVALID, \ + } + +void storage_acquire_reply_done(StorageAcquireReply *reply); + +/* On varlink failure, reterr_error_id (if non-NULL) is set to the io.systemd.StorageProvider.* + * error name. The reply is untouched on any error. */ +typedef struct BindVolume BindVolume; +int storage_acquire_volume( + RuntimeScope scope, + const BindVolume *bv, + bool allow_interactive_auth, + char **reterr_error_id, + StorageAcquireReply *ret); From a0faa6a798ae4364726a5676dd5455decad2e7d1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:32:01 +0200 Subject: [PATCH 1486/2155] storagectl: refactor mount.storage helper to use storage_acquire_volume() Drop the inline socket-build + sd_varlink_callbo() + reply-dispatch + take_fd block from run_as_mount_helper() in favour of the shared helper. Preserves the type-fallback retry (TypeNotSupported / WrongType re-tries with requestAs="blk") and the per-error-id message mapping; the helper just reports the io.systemd.StorageProvider.* error name back to the caller. Net effect: ~50 lines of dedup, no functional change. Signed-off-by: Christian Brauner (Amutable) --- src/storage/storagectl.c | 224 +++++++++++++-------------------------- 1 file changed, 76 insertions(+), 148 deletions(-) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index 23d91d6ba12ee..bbe09e01b1d70 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -19,7 +19,7 @@ #include "format-table.h" #include "format-util.h" #include "help-util.h" -#include "json-util.h" +#include "machine-util.h" #include "main-func.h" #include "mount-util.h" #include "namespace-util.h" @@ -524,7 +524,7 @@ static int run_as_mount_helper(int argc, char *argv[]) { _cleanup_free_ char *filtered = NULL, *template = NULL; CreateMode create_mode = _CREATE_MODE_INVALID; uint64_t create_size = UINT64_MAX; - int read_only = -1; + ReadOnlyMode read_only = _READ_ONLY_INVALID; for (const char *p = options;;) { _cleanup_free_ char *word = NULL; @@ -555,9 +555,9 @@ static int run_as_mount_helper(int argc, char *argv[]) { } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown mount option '%s', refusing.", word); } else if (streq(word, "ro")) - read_only = true; + read_only = READ_ONLY_YES; else if (streq(word, "rw")) - read_only = false; + read_only = READ_ONLY_NO; else if (!strextend_with_separator(&filtered, ",", word)) return log_oom(); } @@ -565,141 +565,69 @@ static int run_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - _cleanup_free_ char *socket_path = NULL; - r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); - if (r < 0) - return log_error_errno(r, "Failed to determine socket directory: %m"); - - if (!path_extend(&socket_path, provider)) - return log_oom(); - - _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; - r = sd_varlink_connect_address(&link, socket_path); - if (r < 0) - return log_error_errno(r, "Failed to connect to '%s': %m", socket_path); - - r = sd_varlink_set_allow_fd_passing_input(link, true); - if (r < 0) - return log_error_errno(r, "Failed to enable file descriptor passing: %m"); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - sd_json_variant *mreply = NULL; - const char *merror_id = NULL, *vtype = fstype ? "reg" : "dir"; - r = sd_varlink_callbo( - link, - "io.systemd.StorageProvider.Acquire", - &mreply, - &merror_id, - SD_JSON_BUILD_PAIR_STRING("name", name), - SD_JSON_BUILD_PAIR_CONDITION(create_mode >= 0, "createMode", SD_JSON_BUILD_STRING(create_mode_to_string(create_mode))), - JSON_BUILD_PAIR_STRING_NON_EMPTY("template", template), - SD_JSON_BUILD_PAIR_CONDITION(read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(read_only)), - SD_JSON_BUILD_PAIR_STRING("requestAs", vtype), - SD_JSON_BUILD_PAIR_CONDITION(create_size != UINT64_MAX, "createSizeBytes", SD_JSON_BUILD_UNSIGNED(create_size)), - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = sd_json_variant_ref(mreply); - if (merror_id) { - /* Copy out the error ID, as the follow-up call will invalidate it */ - _cleanup_free_ char *error_id = strdup(merror_id); - if (!error_id) - return log_oom(); - - /* Hmm, the type might not have been right for the backend or the volume? then try - * again, and switch from "reg" to "blk", maybe it works then. (We keep the original - * reply referenced, since we prefer generating an error for the first error.) */ - if (streq(vtype, "reg") && STR_IN_SET(error_id, - "io.systemd.StorageProvider.TypeNotSupported", - "io.systemd.StorageProvider.WrongType")) { - - sd_json_variant *freply = NULL; - const char *ferror_id = NULL; - r = sd_varlink_callbo( - link, - "io.systemd.StorageProvider.Acquire", - &freply, - &ferror_id, - SD_JSON_BUILD_PAIR_STRING("name", name), - SD_JSON_BUILD_PAIR_CONDITION(create_mode >= 0, "createMode", SD_JSON_BUILD_STRING(create_mode_to_string(create_mode))), - JSON_BUILD_PAIR_STRING_NON_EMPTY("template", template), - SD_JSON_BUILD_PAIR_CONDITION(read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(read_only)), - SD_JSON_BUILD_PAIR_STRING("requestAs", "blk"), - SD_JSON_BUILD_PAIR_CONDITION(create_size != UINT64_MAX, "createSizeBytes", SD_JSON_BUILD_UNSIGNED(create_size)), - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); - if (!ferror_id) { - /* The 2nd call worked? then let's forget about the first failure */ - sd_json_variant_unref(reply); - reply = sd_json_variant_ref(freply); - error_id = mfree(error_id); - } - - /* NB: if both fail we show the Varlink error of the first call here, i.e. of the preferred type */ - } - - if (error_id) { - if (streq(error_id, "io.systemd.StorageProvider.NoSuchVolume")) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Volume '%s' not known.", name); - if (streq(error_id, "io.systemd.StorageProvider.NoSuchTemplate")) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Template '%s' not known.", template); - if (streq(error_id, "io.systemd.StorageProvider.VolumeExists")) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Volume '%s' exists already.", name); - if (streq(error_id, "io.systemd.StorageProvider.TypeNotSupported")) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support the specified volume type '%s'.", vtype); - if (streq(error_id, "io.systemd.StorageProvider.WrongType")) - return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Volume '%s' is not of type '%s'.", name, vtype); - if (streq(error_id, "io.systemd.StorageProvider.CreateNotSupported")) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support creating volumes."); - if (streq(error_id, "io.systemd.StorageProvider.CreateSizeRequired")) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Storage provider requires a create size to be provided when creating volumes on-the-fly. Use 'storage.create-size=' mount option."); - if (streq(error_id, "io.systemd.StorageProvider.ReadOnlyVolume")) - return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Volume '%s' is read-only.", name); - if (streq(error_id, "io.systemd.StorageProvider.BadTemplate")) - return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Template does not apply to this volume type."); - - r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ - if (r != -EBADR) - return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); - - return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %s", error_id); + VolumeType requested_type = fstype ? VOLUME_REG : VOLUME_DIR; + + BindVolume bv = BIND_VOLUME_INIT; + bv.provider = provider; + bv.volume = name; + bv.create_mode = create_mode; + bv.template = template; + bv.read_only = read_only; + bv.request_as = requested_type; + bv.create_size_bytes = create_size; + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, &bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0 && fstype && + STR_IN_SET(strna(acquire_error_id), + "io.systemd.StorageProvider.TypeNotSupported", + "io.systemd.StorageProvider.WrongType")) { + _cleanup_(storage_acquire_reply_done) StorageAcquireReply retry = STORAGE_ACQUIRE_REPLY_INIT; + assert(bv.request_as == VOLUME_REG); + bv.request_as = VOLUME_BLK; + int k = storage_acquire_volume(arg_runtime_scope, &bv, arg_ask_password, /* reterr_error_id= */ NULL, &retry); + if (k >= 0) { + storage_acquire_reply_done(&reply); + reply = retry; + retry = STORAGE_ACQUIRE_REPLY_INIT; + acquire_error_id = mfree(acquire_error_id); + requested_type = VOLUME_BLK; + r = 0; } } - struct { - unsigned fd_idx; - int read_only; - const char *type; - uid_t base_uid; - gid_t base_gid; - } p = { - .fd_idx = UINT_MAX, - .read_only = -1, - .base_uid = UID_INVALID, - .base_gid = GID_INVALID, - }; - - static const sd_json_dispatch_field dispatch_table[] = { - { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, fd_idx), SD_JSON_MANDATORY }, - { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, - { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, - { "baseUID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(p, base_uid), 0 }, - { "baseGID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(p, base_gid), 0 }, - {} - }; - - r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); - if (r < 0) - return log_error_errno(r, "Failed to decode Acquire() reply: %m"); - - _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_idx); - if (fd < 0) - return log_error_errno(fd, "Failed to acquire fd from Varlink connection: %m"); + if (r < 0) { + const char *eid = acquire_error_id; + + if (streq_ptr(eid, "io.systemd.StorageProvider.NoSuchVolume")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Volume '%s' not known.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.NoSuchTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Template '%s' not known.", template); + if (streq_ptr(eid, "io.systemd.StorageProvider.VolumeExists")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Volume '%s' exists already.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.TypeNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support the specified volume type '%s'.", volume_type_to_string(requested_type)); + if (streq_ptr(eid, "io.systemd.StorageProvider.WrongType")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Volume '%s' is not of type '%s'.", name, volume_type_to_string(requested_type)); + if (streq_ptr(eid, "io.systemd.StorageProvider.CreateNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support creating volumes."); + if (streq_ptr(eid, "io.systemd.StorageProvider.CreateSizeRequired")) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Storage provider requires a create size to be provided when creating volumes on-the-fly. Use 'storage.create-size=' mount option."); + if (streq_ptr(eid, "io.systemd.StorageProvider.ReadOnlyVolume")) + return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Volume '%s' is read-only.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.BadTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Template does not apply to this volume type."); + + if (eid) + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call (%s): %m", eid); + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); + } struct stat st; - if (fstat(fd, &st) < 0) + if (fstat(reply.fd, &st) < 0) return log_error_errno(errno, "Failed to stat returned file descriptor: %m"); _cleanup_strv_free_ char **cmdline = strv_new("mount", "-c"); @@ -707,7 +635,7 @@ static int run_as_mount_helper(int argc, char *argv[]) { return log_oom(); if (fstype) { - if (!STR_IN_SET(p.type, "reg", "blk")) + if (!IN_SET(reply.type, VOLUME_REG, VOLUME_BLK)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mounting as file system type '%s' requested, but volume is not a block device or regular file.", fstype); r = stat_verify_regular_or_block(&st); @@ -717,31 +645,31 @@ static int run_as_mount_helper(int argc, char *argv[]) { if (strv_extend_strv(&cmdline, STRV_MAKE("-t", fstype), /* filter_duplicates= */ false) < 0) return log_oom(); } else { - if (!streq(p.type, "dir")) + if (reply.type != VOLUME_DIR) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mount as directory requested, but volume is not a directory."); - if (!uid_is_valid(p.base_uid) || !gid_is_valid(p.base_gid)) + if (!uid_is_valid(reply.base_uid) || !gid_is_valid(reply.base_gid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provider did not report base UID/GID, cannot mount."); - if (p.base_uid > UINT32_MAX - USERNS_RANGE_SIZE || - p.base_gid > UINT32_MAX - USERNS_RANGE_SIZE) + if (reply.base_uid > UINT32_MAX - USERNS_RANGE_SIZE || + reply.base_gid > UINT32_MAX - USERNS_RANGE_SIZE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Returned base UID/GID out of range."); r = stat_verify_directory(&st); if (r < 0) return log_error_errno(r, "File descriptor for directory volume is not a directory inode: %m"); - if (st.st_uid < p.base_uid || st.st_uid >= p.base_uid + USERNS_RANGE_SIZE || - st.st_gid < p.base_gid || st.st_gid >= p.base_gid + USERNS_RANGE_SIZE) + if (st.st_uid < reply.base_uid || st.st_uid >= reply.base_uid + USERNS_RANGE_SIZE || + st.st_gid < reply.base_gid || st.st_gid >= reply.base_gid + USERNS_RANGE_SIZE) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File descriptor for directory volume is not owned by base UID/GID range, refusing."); /* Now move the mount into our own UID/GID range */ _cleanup_free_ char *uid_line = asprintf_safe( UID_FMT " " UID_FMT " " UID_FMT "\n", - p.base_uid, (uid_t) 0, USERNS_RANGE_SIZE); + reply.base_uid, (uid_t) 0, USERNS_RANGE_SIZE); _cleanup_free_ char *gid_line = asprintf_safe( GID_FMT " " GID_FMT " " GID_FMT "\n", - p.base_gid, (gid_t) 0, USERNS_RANGE_SIZE); + reply.base_gid, (gid_t) 0, USERNS_RANGE_SIZE); if (!uid_line || !gid_line) return log_oom(); @@ -750,7 +678,7 @@ static int run_as_mount_helper(int argc, char *argv[]) { return log_error_errno(userns_fd, "Failed to acquire new user namespace: %m"); _cleanup_close_ int remapped_fd = open_tree_attr_with_fallback( - fd, + reply.fd, /* path= */ NULL, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC, &(struct mount_attr) { @@ -760,25 +688,25 @@ static int run_as_mount_helper(int argc, char *argv[]) { if (remapped_fd < 0) return log_error_errno(remapped_fd, "Failed to set ID mapping on returned mount: %m"); - close_and_replace(fd, remapped_fd); + close_and_replace(reply.fd, remapped_fd); if (strv_extend(&cmdline, "--bind") < 0) return log_oom(); } - if (p.read_only > 0) - read_only = true; + if (reply.read_only > 0) + read_only = READ_ONLY_YES; - if (!strextend_with_separator(&filtered, ",", read_only > 0 ? "ro" : "rw")) + if (!strextend_with_separator(&filtered, ",", read_only == READ_ONLY_YES ? "ro" : "rw")) return log_oom(); if (strv_extend_strv(&cmdline, STRV_MAKE("-o", filtered), /* filter_duplicates= */ false) < 0) return log_oom(); - if (strv_extend_strv(&cmdline, STRV_MAKE(FORMAT_PROC_FD_PATH(fd), path), /* filter_duplicates= */ false) < 0) + if (strv_extend_strv(&cmdline, STRV_MAKE(FORMAT_PROC_FD_PATH(reply.fd), path), /* filter_duplicates= */ false) < 0) return log_oom(); - r = fd_cloexec(fd, false); + r = fd_cloexec(reply.fd, false); if (r < 0) return log_error_errno(r, "Failed to disable O_CLOEXEC for mount fd: %m"); From aa02ad284e6d98fdd594c0f4e4d1765f8b4574d4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:32:58 +0200 Subject: [PATCH 1487/2155] shared: add AddStorage / RemoveStorage to io.systemd.MachineInstance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define two new methods on the generic 'MachineInstance' Varlink interface that systemd-vmspawn (this series) and (future) systemd-nspawn implement on their per-machine control sockets: AddStorage(fileDescriptorIndex, name, config?) -> () Attach a storage volume — the caller passes an fd previously acquired from a StorageProvider, plus a unique name of the form ':' that identifies this binding for later removal, plus a backend-specific 'config' field (vmspawn: guest device type; future nspawn: mount path). RemoveStorage(name) -> () Detach a previously-added storage volume. Plus errors NoSuchStorage, StorageExists, StorageImmutable (the volume was attached at boot and cannot be removed), BadConfig, and ConfigNotSupported. Names follow the io.systemd.StorageProvider vocabulary (NoSuchVolume, BadTemplate, TypeNotSupported, etc.) so the two interfaces are visually consistent. Signed-off-by: Christian Brauner (Amutable) --- .../varlink-io.systemd.MachineInstance.c | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/shared/varlink-io.systemd.MachineInstance.c b/src/shared/varlink-io.systemd.MachineInstance.c index 365b6f5f9e1af..6630402709870 100644 --- a/src/shared/varlink-io.systemd.MachineInstance.c +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -25,8 +25,27 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Event-specific payload"), SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + AddStorage, + SD_VARLINK_FIELD_COMMENT("Index of the attached file descriptor for the storage volume"), + SD_VARLINK_DEFINE_INPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Unique storage name of the form ':' identifying this binding for later removal"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Backend-specific configuration"), + SD_VARLINK_DEFINE_INPUT(config, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + RemoveStorage, + SD_VARLINK_FIELD_COMMENT("Unique storage name ':' to detach"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); + static SD_VARLINK_DEFINE_ERROR(NotConnected); static SD_VARLINK_DEFINE_ERROR(NotSupported); +static SD_VARLINK_DEFINE_ERROR(NoSuchStorage); +static SD_VARLINK_DEFINE_ERROR(StorageExists); +static SD_VARLINK_DEFINE_ERROR(StorageImmutable); +static SD_VARLINK_DEFINE_ERROR(BadConfig); +static SD_VARLINK_DEFINE_ERROR(ConfigNotSupported); SD_VARLINK_DEFINE_INTERFACE( io_systemd_MachineInstance, @@ -45,7 +64,21 @@ SD_VARLINK_DEFINE_INTERFACE( &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("Attach a storage volume (passed via file descriptor) to the running machine"), + &vl_method_AddStorage, + SD_VARLINK_SYMBOL_COMMENT("Detach a previously-attached storage volume from the running machine"), + &vl_method_RemoveStorage, 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); + &vl_error_NotSupported, + SD_VARLINK_SYMBOL_COMMENT("The named storage binding does not exist"), + &vl_error_NoSuchStorage, + SD_VARLINK_SYMBOL_COMMENT("A storage binding with this name already exists"), + &vl_error_StorageExists, + SD_VARLINK_SYMBOL_COMMENT("The storage binding cannot be detached at runtime (e.g. attached at boot)"), + &vl_error_StorageImmutable, + SD_VARLINK_SYMBOL_COMMENT("The supplied 'config' value is not valid for this backend"), + &vl_error_BadConfig, + SD_VARLINK_SYMBOL_COMMENT("The supplied 'config' value is recognized but not supported by this backend"), + &vl_error_ConfigNotSupported); From 0e911e41649d46ff529522ba2ed036e779c13098 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:34:06 +0200 Subject: [PATCH 1488/2155] vmspawn: track removability as a QmpDriveFlags bit and expose add_block_device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drives attached at boot via the existing CLI options (--image, --extra-drive) must not be detachable at runtime via the upcoming RemoveStorage Varlink method, while drives added at runtime via AddStorage must be. Track this distinction with a new QMP_DRIVE_REMOVABLE property flag — placed alongside QMP_DRIVE_BLOCK_DEVICE, not in the transient BlockDeviceStateFlags state-machine, since "may be removed" is a permanent property of the drive. vmspawn_qmp_remove_block_device() now early-rejects unknown ids with io.systemd.MachineInstance.NoSuchStorage and immutable drives with io.systemd.MachineInstance.StorageImmutable. vmspawn_qmp_add_block_device() loses its 'static' qualifier and gets a declaration in the header, so the runtime hotplug path (vmspawn-bind-volume.c, next) can dispatch into it directly. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 9 +++++---- src/vmspawn/vmspawn-qmp.h | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index d09576213a163..52d9e11b9f89e 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -826,7 +826,7 @@ static int on_add_device_add_complete( return 0; if (d->link) { - (void) sd_varlink_replybo(d->link, SD_JSON_BUILD_PAIR_STRING("id", d->id)); + (void) sd_varlink_reply(d->link, NULL); d->link = sd_varlink_unref(d->link); } @@ -882,7 +882,7 @@ static int qmp_setup_scsi_controller(VmspawnQmpBridge *bridge, const char *pcie_ return 0; } -static int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { +int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { int r; assert(bridge); @@ -989,7 +989,6 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, DriveInfo *drive) { assert(bridge); assert(drive); assert(drive->fd >= 0); - assert(!drive->id); return vmspawn_qmp_add_block_device(bridge, drive); } @@ -1028,7 +1027,9 @@ int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, DriveInfo *drive = hashmap_get(bridge->block_devices, id); if (!drive) - return reply_qmp_error(link, "Unknown block device id", -ENOENT); + return sd_varlink_error(link, "io.systemd.MachineInstance.NoSuchStorage", NULL); + if (!FLAGS_SET(drive->flags, QMP_DRIVE_REMOVABLE)) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageImmutable", NULL); 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)) diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index d8403520c9afe..a2f929732086e 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -72,6 +72,7 @@ typedef enum QmpDriveFlags { QMP_DRIVE_BOOT = 1u << 4, QMP_DRIVE_IO_URING = 1u << 5, QMP_DRIVE_DISCARD_NO_UNREF = 1u << 6, /* qcow2 only */ + QMP_DRIVE_REMOVABLE = 1u << 7, /* may be detached at runtime via RemoveStorage */ } QmpDriveFlags; typedef enum BlockDeviceStateFlags { @@ -177,5 +178,6 @@ 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_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive); 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); From a34ad7f7b01ba39f2a7fd4a3b1ba6b1eff49c6f2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:34:54 +0200 Subject: [PATCH 1489/2155] vmspawn: add vmspawn-bind-volume glue This is vmspawn's per-backend code for the StorageProvider integration. Other backends (future systemd-nspawn, future service-manager BindVolume=) consume the same shared parser and Acquire helper but each provides its own attach/detach glue; this is vmspawn's. - disk_type_from_bind_volume_config() turns the opaque BindVolume 'config' field (e.g. "scsi-cd") into a DiskType. Empty defaults to virtio-blk to match the --bind-volume CLI grammar. - vmspawn_bind_volume_acquire() takes a parsed BindVolume, calls storage_acquire_volume() for the fd, and builds a DriveInfo ready for vmspawn_qmp_setup_drives() (boot) or vmspawn_qmp_add_block_device() (hotplug). Rejects directory-typed volumes (vmspawn block devices need a regular file or a host block device). - vmspawn_bind_volume_attach_fd() is the runtime path: takes a fd that was already pushed across by an AddStorage caller plus the name+config it specified, builds the DriveInfo with QMP_DRIVE_REMOVABLE set and a varlink link, and dispatches to vmspawn_qmp_add_block_device(). Reply is delivered asynchronously by the existing on_add_device_add_complete() callback. - vmspawn_bind_volume_prepare_boot() is a thin loop the boot-time path uses to populate DriveInfos. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/meson.build | 1 + src/vmspawn/vmspawn-bind-volume.c | 202 ++++++++++++++++++++++++++++++ src/vmspawn/vmspawn-bind-volume.h | 39 ++++++ 3 files changed, 242 insertions(+) create mode 100644 src/vmspawn/vmspawn-bind-volume.c create mode 100644 src/vmspawn/vmspawn-bind-volume.h diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 6d08755fedf8b..6bc31c77c692d 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -6,6 +6,7 @@ endif vmspawn_sources = files( 'vmspawn.c', + 'vmspawn-bind-volume.c', 'vmspawn-qemu-config.c', 'vmspawn-qmp.c', 'vmspawn-varlink.c', diff --git a/src/vmspawn/vmspawn-bind-volume.c b/src/vmspawn/vmspawn-bind-volume.c new file mode 100644 index 0000000000000..d67fc61de02e5 --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.c @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "runtime-scope.h" +#include "stat-util.h" +#include "storage-util.h" +#include "string-util.h" +#include "vmspawn-bind-volume.h" +#include "vmspawn-qmp.h" + +DiskType disk_type_from_bind_volume_config(const char *config) { + if (isempty(config)) + return DISK_TYPE_VIRTIO_BLK; + return disk_type_from_string(config); +} + +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id) { + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *err = NULL; + int r; + + assert(v); + assert(ret); + + DiskType dt = disk_type_from_bind_volume_config(v->config); + if (dt < 0) { + r = dt; + goto fail; + } + + r = storage_acquire_volume(scope, v, /* allow_interactive_auth= */ false, &err, &reply); + if (r < 0) + goto fail; + + if (reply.type == VOLUME_DIR) { + r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Directory volumes are not supported for vmspawn block devices."); + goto fail; + } + + struct stat st; + if (fstat(reply.fd, &st) < 0) { + r = -errno; + goto fail; + } + r = stat_verify_regular_or_block(&st); + if (r < 0) + goto fail; + + d = drive_info_new(); + if (!d) { + r = -ENOMEM; + goto fail; + } + + d->id = strjoin(v->provider, ":", v->volume); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(v->volume); + if (!d->id || !d->disk_driver || !d->format || !d->path) { + r = -ENOMEM; + goto fail; + } + + d->disk_type = dt; + d->fd = TAKE_FD(reply.fd); + + if (reply.type == VOLUME_BLK || S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (reply.read_only > 0 || dt == DISK_TYPE_VIRTIO_SCSI_CDROM) + d->flags |= QMP_DRIVE_READ_ONLY; + if (removable) + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + *ret = TAKE_PTR(d); + return 0; + +fail: + if (reterr_error_id) + *reterr_error_id = TAKE_PTR(err); + return r; +} + +/* Takes ownership of fd unconditionally — it is closed on every error path too. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config) { + + _cleanup_close_ int owned_fd = fd; + int r; + + assert(bridge); + assert(link); + assert(fd >= 0); + assert(name); + + DiskType dt = disk_type_from_bind_volume_config(config); + if (dt < 0) + return dt; + + struct stat st; + if (fstat(owned_fd, &st) < 0) + return -errno; + r = stat_verify_regular_or_block(&st); + if (r < 0) + return r; + + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); + if (!d) + return -ENOMEM; + + d->id = strdup(name); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(name); + if (!d->id || !d->disk_driver || !d->format || !d->path) + return -ENOMEM; + + int oflags = fcntl(owned_fd, F_GETFL); + if (oflags < 0) + return -errno; + + d->disk_type = dt; + d->fd = TAKE_FD(owned_fd); + if (S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (dt == DISK_TYPE_VIRTIO_SCSI_CDROM || (oflags & O_ACCMODE_STRICT) == O_RDONLY) + d->flags |= QMP_DRIVE_READ_ONLY; + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + return vmspawn_qmp_add_block_device(bridge, TAKE_PTR(d)); +} + +void bind_volumes_done(BindVolumes *bv) { + assert(bv); + FOREACH_ARRAY(v, bv->items, bv->n_items) + bind_volume_free(*v); + bv->items = mfree(bv->items); + bv->n_items = 0; +} + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives) { + + int r; + + assert(bv); + assert(drives); + + if (bv->n_items == 0) + return 0; + + if (!GREEDY_REALLOC(drives->drives, drives->n_drives + bv->n_items)) + return log_oom(); + + FOREACH_ARRAY(it, bv->items, bv->n_items) { + BindVolume *v = *it; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *error_id = NULL; + + r = vmspawn_bind_volume_acquire( + scope, v, + /* removable= */ false, + /* link= */ NULL, + &d, &error_id); + if (r < 0) { + if (error_id) + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s' (%s): %m", + v->provider, v->volume, error_id); + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s': %m", + v->provider, v->volume); + } + + drives->drives[drives->n_drives++] = TAKE_PTR(d); + } + + return 0; +} diff --git a/src/vmspawn/vmspawn-bind-volume.h b/src/vmspawn/vmspawn-bind-volume.h new file mode 100644 index 0000000000000..23b3ff52f3cb3 --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machine-util.h" +#include "shared-forward.h" +#include "vmspawn-qmp.h" + +/* Empty/NULL defaults to virtio-blk; otherwise delegates to disk_type_from_string(). */ +DiskType disk_type_from_bind_volume_config(const char *config); + +/* Acquires the volume and builds a DriveInfo with id=":" (the + * bridge-visible name; QMP-side names are still allocated by add_block_device). */ +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id); + +typedef struct BindVolumes { + BindVolume **items; + size_t n_items; +} BindVolumes; + +void bind_volumes_done(BindVolumes *bv); + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives); + +/* Takes ownership of fd unconditionally. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config); From 413fd62dd79c1983d4c2ec93570fb85f8167d242 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:35:44 +0200 Subject: [PATCH 1490/2155] vmspawn: add --bind-volume= command line option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit systemd-vmspawn --bind-volume=PROVIDER:VOLUME[:CONFIG][:K=V,...] For each --bind-volume passed at startup, vmspawn calls Acquire() on the named StorageProvider and attaches the resulting fd to the VM as an additional drive. The drive is identified by the user-visible name ':' on the bridge — that is also the handle used later when machinectl unbind-volume detaches drives at runtime (though boot-time drives like these are NOT removable; that is the StorageImmutable behaviour added earlier). The colon grammar is parsed by the shared bind_volume_parse() helper. The 3rd 'config' field selects the guest device type from the disk_type_table[] vocabulary (virtio-blk, virtio-scsi, nvme, scsi-cd); empty defaults to virtio-blk per the TASK grammar. Wiring lives next to the existing --extra-drive setup: parse_argv() appends a parsed BindVolume to arg_bind_volumes, and prepare_device_info() hands the array to vmspawn_bind_volume_prepare_boot() which Acquires each volume and pushes a DriveInfo onto the existing drives array. PCIe port assignment (assign_pcie_ports()) and the QMP setup loop pick them up automatically. Signed-off-by: Christian Brauner (Amutable) --- shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn.c | 33 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index efa0dae58de04..62fa5ab52065d 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 --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files' + [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 --bind-volume' [IMAGE_FORMAT]='--image-format' [IMAGE_DISK_TYPE]='--image-disk-type' ) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 81c035c250d62..ee8ae518f0330 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -88,6 +88,7 @@ #include "user-record.h" #include "user-util.h" #include "utf8.h" +#include "vmspawn-bind-volume.h" #include "vmspawn-mount.h" #include "vmspawn-qemu-config.h" #include "vmspawn-qmp.h" @@ -163,6 +164,7 @@ static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; static char **arg_kernel_cmdline_extra = NULL; static ExtraDriveContext arg_extra_drives = {}; +static BindVolumes arg_bind_volumes = {}; static char *arg_background = NULL; static bool arg_pass_ssh_key = true; static char *arg_ssh_key_type = NULL; @@ -200,6 +202,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done); STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, extra_drive_context_done); +STATIC_DESTRUCTOR_REGISTER(arg_bind_volumes, bind_volumes_done); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep); @@ -766,6 +769,30 @@ static int parse_argv(int argc, char *argv[]) { break; } + OPTION_LONG("bind-volume", "PROVIDER:VOLUME[:CONFIG][:KEY=VALUE,...]", + "Acquire a storage volume from a StorageProvider and attach it to the VM"): { + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + + r = bind_volume_parse(opts.arg, &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind-volume= argument '%s': %m", opts.arg); + + if (disk_type_from_bind_volume_config(bv->config) < 0) { + _cleanup_free_ char *valid = NULL; + for (DiskType t = 0; t < _DISK_TYPE_MAX; t++) + if (!strextend_with_separator(&valid, ", ", disk_type_to_string(t))) + return log_oom(); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown device type '%s' for --bind-volume=. Valid values: %s.", + bv->config, valid); + } + + if (!GREEDY_REALLOC(arg_bind_volumes.items, arg_bind_volumes.n_items + 1)) + return log_oom(); + arg_bind_volumes.items[arg_bind_volumes.n_items++] = TAKE_PTR(bv); + break; + } + OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"): if (!valid_user_group_name(opts.arg, /* flags= */ 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg); @@ -2475,7 +2502,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 + arg_bind_volumes.n_items); if (!drives->drives) return log_oom(); @@ -2487,6 +2514,10 @@ static int prepare_device_info(const char *runtime_dir, MachineConfig *c) { if (r < 0) return r; + r = vmspawn_bind_volume_prepare_boot(arg_runtime_scope, &arg_bind_volumes, drives); + if (r < 0) + return r; + return assign_pcie_ports(c); } From e7eac392ef87879451fdca337bb2ccd0113c1a86 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:36:21 +0200 Subject: [PATCH 1491/2155] vmspawn: implement io.systemd.MachineInstance.AddStorage / RemoveStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire up the runtime hotplug Varlink methods on the per-VM control socket: AddStorage → take fd from the link, look up the DiskType from the 'config' field, build a DriveInfo flagged QMP_DRIVE_REMOVABLE, dispatch to vmspawn_qmp_add_block_device(). Reply delivered async by on_add_device_add_complete() once the guest sees the device. RemoveStorage → forward the user-visible name to vmspawn_qmp_remove_block_device(); the existing device_del / DEVICE_DELETED / blockdev-del chain replies on the link. Add SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT to the server flags so clients can push storage fds across via sd_varlink_push_fd(). Maps -EEXIST → StorageExists and -EOPNOTSUPP/-EINVAL → ConfigNotSupported in the AddStorage handler so callers see the specific MachineInstance errors. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 82 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 9aa6afeae0385..a782a5c9c1a4f 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "errno-util.h" +#include "fd-util.h" #include "hashmap.h" #include "log.h" #include "path-util.h" @@ -11,6 +12,7 @@ #include "varlink-io.systemd.MachineInstance.h" #include "varlink-io.systemd.VirtualMachineInstance.h" #include "varlink-util.h" +#include "vmspawn-bind-volume.h" #include "vmspawn-qmp.h" #include "vmspawn-varlink.h" @@ -168,6 +170,77 @@ static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_ return qmp_execute_varlink_async(ctx, link, "query-status", /* arguments= */ NULL, on_qmp_describe_complete); } +static int vl_method_add_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + int fd_index; + const char *name; + const char *config; + } p = { + .fd_index = -1, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd_index), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "config", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, config), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (machine_storage_name_split(p.name, /* ret_provider= */ NULL, /* ret_volume= */ NULL) < 0) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (disk_type_from_bind_volume_config(p.config) < 0) + return sd_varlink_error(link, "io.systemd.MachineInstance.BadConfig", NULL); + + if (p.fd_index < 0) + return sd_varlink_error_invalid_parameter_name(link, "fileDescriptorIndex"); + + _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_index); + if (fd < 0) + return sd_varlink_error_errno(link, fd); + + r = vmspawn_bind_volume_attach_fd(ctx->bridge, link, TAKE_FD(fd), p.name, p.config); + if (r == -EEXIST) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageExists", NULL); + if (r == -EOPNOTSUPP) + return sd_varlink_error(link, "io.systemd.MachineInstance.ConfigNotSupported", NULL); + if (r < 0) + return sd_varlink_error_errno(link, r); + + /* Async reply via on_add_device_add_complete. */ + return 0; +} + +static int vl_method_remove_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + const char *name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (machine_storage_name_split(p.name, /* ret_provider= */ NULL, /* ret_volume= */ NULL) < 0) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + return vmspawn_qmp_remove_block_device(ctx->bridge, link, p.name); +} + 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; @@ -380,9 +453,10 @@ int vmspawn_varlink_setup( if (!ctx) return log_oom(); - /* Create varlink server for VM control */ + /* AddStorage receives an fd from the caller. */ r = varlink_server_new(&ctx->varlink_server, - SD_VARLINK_SERVER_INHERIT_USERDATA, + SD_VARLINK_SERVER_INHERIT_USERDATA | + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, ctx); if (r < 0) return log_error_errno(r, "Failed to create varlink server: %m"); @@ -402,7 +476,9 @@ 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.MachineInstance.SubscribeEvents", vl_method_subscribe_events, + "io.systemd.MachineInstance.AddStorage", vl_method_add_storage, + "io.systemd.MachineInstance.RemoveStorage", vl_method_remove_storage); if (r < 0) return log_error_errno(r, "Failed to bind varlink methods: %m"); From c9f461a8067996c6b0c3ac3bf6f9097aedbf4734 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:37:33 +0200 Subject: [PATCH 1492/2155] machinectl: add bind-volume / unbind-volume verbs machinectl bind-volume MACHINE PROVIDER:VOLUME[:CONFIG][:K=V,...] machinectl unbind-volume MACHINE PROVIDER:VOLUME For bind-volume, machinectl parses the SPEC with the shared bind_volume_parse(), Acquires the storage volume from the named provider on the machinectl side, locates the target machine's io.systemd.MachineInstance control socket via machine_get_control_address(), pushes the fd across, and calls io.systemd.MachineInstance.AddStorage with name=':' and the user-supplied config string. For unbind-volume, machinectl just forwards the name string to io.systemd.MachineInstance.RemoveStorage. Volumes attached at machine startup (e.g. via systemd-vmspawn's --bind-volume=) are rejected with StorageImmutable when the user attempts to unbind them at runtime. Signed-off-by: Christian Brauner (Amutable) --- shell-completion/bash/machinectl | 2 +- shell-completion/zsh/_machinectl | 8 +++ src/machine/machinectl.c | 120 +++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/shell-completion/bash/machinectl b/shell-completion/bash/machinectl index 50b46fb27925b..d6627e6ba7e63 100644 --- a/shell-completion/bash/machinectl +++ b/shell-completion/bash/machinectl @@ -48,7 +48,7 @@ _machinectl() { [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' + [MACHINE_ONLY]='clone rename set-limit bind-volume unbind-volume' [READONLY]='read-only' [FILE]='import-tar import-raw' [MACHINES_AND_FILES]='copy-to copy-from bind' diff --git a/shell-completion/zsh/_machinectl b/shell-completion/zsh/_machinectl index 31ddf4fca571d..d61a62ee27681 100644 --- a/shell-completion/zsh/_machinectl +++ b/shell-completion/zsh/_machinectl @@ -45,6 +45,8 @@ "copy-to:Copy files from the host to a container" "copy-from:Copy files from a container to the host" "bind:Bind mount a path from the host into a container" + "bind-volume:Attach a storage volume to a running machine" + "unbind-volume:Detach a storage volume from a running machine" "list-images:Show available container and VM images" "image-status:Show image details" @@ -115,6 +117,12 @@ else stop=1 fi ;; + bind-volume|unbind-volume) + if (( CURRENT == 2 )); then _sd_machines + elif (( CURRENT == 3 )); then _message "volume spec" + else stop=1 + fi ;; + read-only) if (( CURRENT == 2 )); then _machinectl_images elif (( CURRENT == 3 )); then _values 'read-only flag' 'true' 'false' diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index ae5473aa99d50..3d7f782a8c775 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -29,16 +29,19 @@ #include "cgroup-util.h" #include "edit-util.h" #include "env-util.h" +#include "fd-util.h" #include "format-ifname.h" #include "format-table.h" #include "format-util.h" #include "hostname-util.h" #include "import-util.h" #include "in-addr-util.h" +#include "json-util.h" #include "label-util.h" #include "log.h" #include "logs-show.h" #include "machine-dbus.h" +#include "machine-util.h" #include "main-func.h" #include "nulstr-util.h" #include "osc-context.h" @@ -55,6 +58,7 @@ #include "ptyfwd.h" #include "runtime-scope.h" #include "stdio-util.h" +#include "storage-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -1338,6 +1342,120 @@ static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userda return 0; } +static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bind-volume is only supported on the local transport."); + + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + r = bind_volume_parse(argv[2], &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + /* Locate and connect to the target machine before acquiring storage, so a missing + * machine doesn't trigger 'create=new' side effects on the StorageProvider. */ + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m"); + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0) { + if (acquire_error_id) + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s", + bv->provider, bv->volume, acquire_error_id); + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m", + bv->provider, bv->volume); + } + + int fd_index = sd_varlink_push_fd(vl, reply.fd); + if (fd_index < 0) + return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m"); + TAKE_FD(reply.fd); + + _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume); + if (!name) + return log_oom(); + + sd_json_variant *vl_reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.AddStorage", + &vl_reply, &error_id, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index), + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config)); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply), + "AddStorage failed for '%s': %s", name, error_id); + + return 0; +} + +static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "unbind-volume is only supported on the local transport."); + + r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid unbind-volume name '%s', expected ':'.", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.RemoveStorage", + &reply, &error_id, + SD_JSON_BUILD_PAIR_STRING("name", argv[2])); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "RemoveStorage failed for '%s': %s", argv[2], error_id); + + return 0; +} + 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); @@ -2606,6 +2724,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "login", VERB_ANY, 2, 0, verb_login_machine }, { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine }, { "bind", 3, 4, 0, verb_bind_mount }, + { "bind-volume", 3, 3, 0, verb_bind_volume }, + { "unbind-volume", 3, 3, 0, verb_unbind_volume }, { "edit", 2, VERB_ANY, 0, verb_edit_settings }, { "cat", 2, VERB_ANY, 0, verb_cat_settings }, { "copy-to", 3, 4, 0, verb_copy_files }, From 8a4451d2f0d994562ec01f88283b23986565126e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 1 May 2026 13:38:12 +0200 Subject: [PATCH 1493/2155] docs,test: --bind-volume / bind-volume / unbind-volume - Document the new --bind-volume= option in systemd-vmspawn(1) and the new bind-volume / unbind-volume verbs in machinectl(1). - Add an integration test (TEST-87-AUX-UTILS-VM.bind-volume.sh) covering boot-time attach via --bind-volume, runtime attach via 'machinectl bind-volume', runtime detach via 'machinectl unbind-volume', the StorageImmutable rejection of attempts to detach boot-time volumes, and the NoSuchStorage rejection of detach on unknown names. - Strike "hook-up in systemd-vmspawn" from TODO.md; the nspawn and service-manager hookups remain. Signed-off-by: Christian Brauner (Amutable) --- TODO.md | 1 - man/machinectl.xml | 32 ++++ man/systemd-vmspawn.xml | 33 ++++ .../units/TEST-87-AUX-UTILS-VM.bind-volume.sh | 166 ++++++++++++++++++ 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh diff --git a/TODO.md b/TODO.md index 61114235f3290..f3d1070c6c746 100644 --- a/TODO.md +++ b/TODO.md @@ -138,7 +138,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - StorageProvider interface + storagectl - hook-up in systemd-nspawn - - hook-up in systemd-vmspawn - hook-up in service manager (BindVolume=) - introduce a locking concept: right now all access to volumes is fully shared. Let's add a basic locking concept: supporting backends can take an diff --git a/man/machinectl.xml b/man/machinectl.xml index b4fb15b4f93a3..10ab225074dfc 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -357,6 +357,38 @@ + + bind-volume NAME SPEC + + Acquire a storage volume from a + storagectl1 + provider and attach it to the running machine. SPEC is a string of the form + PROVIDER:VOLUME[:CONFIG][:K=V,…], + identical in grammar to the argument of + systemd-vmspawn1. + + The attached volume is identified by the name PROVIDER:VOLUME + and may be detached at runtime via unbind-volume. Currently only supported for + systemd-vmspawn machines that expose an + io.systemd.MachineInstance control socket. + + + + + + unbind-volume NAME STORAGE-NAME + + Detach a storage volume from the running machine. STORAGE-NAME + is the PROVIDER:VOLUME + identifier that was specified at bind-volume time. Volumes that were attached at machine + startup (e.g. via on + systemd-vmspawn1) + cannot be detached and will fail with + io.systemd.MachineInstance.StorageImmutable. + + + + copy-to NAME PATH [PATH] diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 5c5ec4ccbcd55..b23c66514221d 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -566,6 +566,39 @@ + + + + Acquire a storage volume from a + storagectl1 + provider and attach it to the virtual machine. PROVIDER is the + provider name (typically block or fs). VOLUME + is the volume name passed to the provider's Acquire() method. + CONFIG selects the guest device type and takes one of + virtio-blk, virtio-scsi, nvme, or + scsi-cd. If empty or omitted, defaults to virtio-blk. + + The trailing comma-separated K=V list passes parameters to + io.systemd.StorageProvider.Acquire(): template=, + create= (one of any, new, open), + read-only= (or ro=; takes a boolean or auto), + size= / create-size= (size for created volumes), + request-as= (one of blk, reg, + dir; dir is rejected by vmspawn). + + Each attached volume is identified by the name PROVIDER:VOLUME. + Volumes attached at startup via this option cannot be detached at runtime via + machinectl unbind-volume; only volumes added at runtime via + machinectl bind-volume are removable. + + The provider is looked up under + /run/systemd/io.systemd.StorageProvider/ for system mode (or + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/ for user mode), matching + the runtime scope chosen via / . + + + + diff --git a/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh b/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh new file mode 100755 index 0000000000000..6339e390936bb --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test --bind-volume / machinectl bind-volume / unbind-volume integration with the +# StorageProvider Varlink interface. +# +# Exercises: +# - --bind-volume parser + runtime_directory_generic + Acquire round-trip +# - boot-time attach via DriveInfo (non-removable) +# - runtime hotplug via io.systemd.MachineInstance.AddStorage (removable) +# - runtime hot-remove via io.systemd.MachineInstance.RemoveStorage +# - StorageImmutable rejection for boot-time attached volumes +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 ! command -v storagectl >/dev/null 2>&1; then + echo "storagectl 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 + +# Storage providers are socket-activated; skip if the fs provider socket isn't present. +if ! test -S /run/systemd/io.systemd.StorageProvider/fs; then + echo "StorageProvider fs socket 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 + +WORKDIR="$(mktemp -d /tmp/test-bind-volume.XXXXXXXXXX)" + +at_exit() { + set +e + if [[ -n "${MACHINE:-}" ]]; then + if machinectl status "$MACHINE" &>/dev/null; then + machinectl terminate "$MACHINE" 2>/dev/null + timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + fi + [[ -n "${VMSPAWN_PID:-}" ]] && { kill "$VMSPAWN_PID" 2>/dev/null; wait "$VMSPAWN_PID" 2>/dev/null; } + rm -rf "$WORKDIR" + rm -f /var/lib/storage/test-bind-volume-*.volume +} +trap at_exit EXIT + +# Build a minimal root for direct boot — guest just sleeps. +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" + +BOOT_VOL="test-bind-volume-boot-$$" +RUNTIME_VOL="test-bind-volume-runtime-$$" + +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 77 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +# --- Boot the VM with one boot-time bind-volume --- +MACHINE="test-bind-volume-$$" +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --bind-volume="fs:${BOOT_VOL}::create=new,size=64M,template=sparse-file" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered" + +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' \ + | jq -e '.running == true' >/dev/null +echo "VM running with boot-time bind-volume attached" + +# --- Hot-add a second volume via machinectl bind-volume (must succeed) --- +machinectl bind-volume "$MACHINE" \ + "fs:${RUNTIME_VOL}:virtio-scsi:create=new,size=32M,template=sparse-file" +echo "Hot-added runtime bind-volume succeeded" + +# --- Hot-remove the runtime-added volume (must succeed) --- +machinectl unbind-volume "$MACHINE" "fs:${RUNTIME_VOL}" +echo "Hot-removed runtime bind-volume succeeded" + +# --- Removing the boot-time volume must fail with StorageImmutable --- +if machinectl unbind-volume "$MACHINE" "fs:${BOOT_VOL}" 2>"$WORKDIR/unbind.err"; then + echo "ERROR: unbind-volume of boot-time volume should have failed" + cat "$WORKDIR/unbind.err" + exit 1 +fi +grep StorageImmutable "$WORKDIR/unbind.err" >/dev/null +echo "Boot-time bind-volume correctly rejected with StorageImmutable" + +# --- Removing a non-existent name must fail with NoSuchStorage --- +if machinectl unbind-volume "$MACHINE" "fs:no-such-volume-$$" 2>"$WORKDIR/unbind-noexist.err"; then + echo "ERROR: unbind-volume of non-existent name should have failed" + cat "$WORKDIR/unbind-noexist.err" + exit 1 +fi +grep NoSuchStorage "$WORKDIR/unbind-noexist.err" >/dev/null +echo "Non-existent unbind-volume correctly rejected with NoSuchStorage" + +machinectl terminate "$MACHINE" +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +echo "All bind-volume tests passed" From 5a41d43e1b33746fb59114b5dabd3ad1d5409af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 12:54:12 +0200 Subject: [PATCH 1494/2155] portablectl: reorder verb functions The order in --help is changed to move have 'list', 'inspect' (query operations), 'attach'/'detach'/'reattach' (main ops), and then the other more specialized verbs. The functions are then reordered to match this. --- src/portable/portablectl.c | 254 ++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 2c555ee359877..0c0f03ff166ac 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -251,6 +251,80 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } +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; + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); + + table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); + if (!table) + return log_oom(); + + r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *name, *type, *state; + uint64_t crtime, mtime, usage; + int ro_int; + + r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_many(table, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_BOOLEAN, ro_int, + TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime, + TABLE_SIZE, usage, + TABLE_STRING, state, + TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!table_isempty(table)) { + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); + + table_set_header(table, arg_legend); + + r = table_print_or_warn(table); + if (r < 0) + return r; + } + + if (arg_legend) { + if (table_isempty(table)) + printf("No images.\n"); + else + printf("\n%zu images listed.\n", table_get_rows(table) - 1); + } + + return 0; +} + static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -960,10 +1034,6 @@ static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *user return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } -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[], 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; @@ -1018,77 +1088,81 @@ static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } -static int verb_list_images(int argc, char *argv[], uintptr_t _data, 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_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_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *image = NULL; + const char *state, *method; int r; - r = acquire_bus(&bus); + r = determine_image(argv[1], true, &image); if (r < 0) return r; - r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); + return r; - table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); - if (!table) - return log_oom(); + method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions"; - r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); + r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); if (r < 0) - return bus_log_parse_error(r); + return bus_log_create_error(r); - for (;;) { - const char *name, *type, *state; - uint64_t crtime, mtime, usage; - int ro_int; + r = sd_bus_message_append(m, "s", image); + if (r < 0) + return bus_log_create_error(r); - r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; + r = attach_extensions_to_message(m, method, arg_extension_images); + if (r < 0) + return r; - r = table_add_many(table, - TABLE_STRING, name, - TABLE_STRING, type, - TABLE_BOOLEAN, ro_int, - TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, - TABLE_TIMESTAMP, crtime, - TABLE_TIMESTAMP, mtime, - TABLE_SIZE, usage, - TABLE_STRING, state, - TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); + if (!strv_isempty(arg_extension_images)) { + r = sd_bus_message_append(m, "t", UINT64_C(0)); if (r < 0) - return table_log_add_error(r); + return bus_log_create_error(r); } - r = sd_bus_message_exit_container(reply); + r = sd_bus_call(bus, m, 0, &error, &reply); if (r < 0) - return bus_log_parse_error(r); + return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r)); - if (!table_isempty(table)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); + r = sd_bus_message_read(reply, "s", &state); + if (r < 0) + return r; - table_set_header(table, arg_legend); + if (!arg_quiet) + puts(state); - r = table_print_or_warn(table); - if (r < 0) - return r; - } + return streq(state, "detached"); +} - if (arg_legend) { - if (table_isempty(table)) - printf("No images.\n"); - else - printf("\n%zu images listed.\n", table_get_rows(table) - 1); +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; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); } + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); + if (r < 0) + return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); + return 0; } @@ -1123,30 +1197,6 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } -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; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); - } - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); - if (r < 0) - return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - - return 0; -} - 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; @@ -1180,56 +1230,6 @@ static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } -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; - _cleanup_free_ char *image = NULL; - const char *state, *method; - int r; - - r = determine_image(argv[1], true, &image); - if (r < 0) - return r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions"; - - r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", image); - if (r < 0) - return bus_log_create_error(r); - - r = attach_extensions_to_message(m, method, arg_extension_images); - if (r < 0) - return r; - - if (!strv_isempty(arg_extension_images)) { - r = sd_bus_message_append(m, "t", UINT64_C(0)); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &state); - if (r < 0) - return r; - - if (!arg_quiet) - puts(state); - - return streq(state, "detached"); -} - static int dump_profiles(void) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1269,14 +1269,14 @@ static int help(void) { "%sAttach or detach portable services from the local system.%s\n" "\nCommands:\n" " list List available portable service images\n" + " inspect NAME|PATH [PREFIX...]\n" + " Show details of specified portable service image\n" " attach NAME|PATH [PREFIX...]\n" " Attach the specified portable service image\n" " detach NAME|PATH [PREFIX...]\n" " Detach the specified portable service image\n" " reattach NAME|PATH [PREFIX...]\n" " Reattach the specified portable service image\n" - " inspect NAME|PATH [PREFIX...]\n" - " Show details of specified portable service image\n" " is-attached NAME|PATH Query if portable service image is attached\n" " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n" " remove NAME|PATH... Remove a portable service image\n" From 0731b6c4d947d5eb6cb6f1cfd7c4890965ee00c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 14:36:12 +0200 Subject: [PATCH 1495/2155] portablectl: convert to OPTION and VERB macros s|attach/detach from the local system|in the local system|, because "attach from" doesn't work. The synopses for 'list' and 'set-limit' are fixed. Co-developed-by: Claude Opus 4.7 --- src/portable/portablectl.c | 273 ++++++++++++++----------------------- 1 file changed, 106 insertions(+), 167 deletions(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 0c0f03ff166ac..575ab4149aa63 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -15,8 +14,11 @@ #include "env-file.h" #include "format-table.h" #include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" #include "install.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -24,7 +26,6 @@ #include "path-util.h" #include "polkit-agent.h" #include "portable.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "verbs.h" @@ -155,8 +156,9 @@ static int extract_prefix(const char *path, char **ret) { if (!name) return -ENOMEM; - /* 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. */ + /* 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, ALPHANUMERICAL "-.")) return -EINVAL; @@ -171,9 +173,9 @@ static int determine_matches(const char *image, char **l, bool allow_any, char * _cleanup_strv_free_ char **k = NULL; int r; - /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list - * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly - * permitted. */ + /* Determine the matches to apply. If the list is empty we derive the match from the image name. If + * the list contains exactly the "-" we return a wildcard list (which is the empty list), but only if + * this is expressly permitted. */ if (strv_isempty(l)) { char *prefix; @@ -251,6 +253,8 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } +VERB(verb_list_images, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "List available portable service images (default)"); 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; @@ -366,6 +370,8 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd return 0; } +VERB(verb_inspect_image, "inspect", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Show details of specified portable service image"); 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; @@ -1030,10 +1036,14 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return 0; } +VERB(verb_attach_image, "attach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Attach the specified portable service image"); 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"); } +VERB(verb_detach_image, "detach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Detach the specified portable service image"); 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; @@ -1088,10 +1098,14 @@ static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_reattach_image, "reattach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Reattach the specified portable service image"); 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"); } +VERB(verb_is_image_attached, "is-attached", "NAME|PATH", 2, 2, 0, + "Query if portable service image is attached"); 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; @@ -1142,6 +1156,8 @@ static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void return streq(state, "detached"); } +VERB(verb_read_only_image, "read-only", "NAME|PATH [BOOL]", 2, 3, 0, + "Mark or unmark portable service image read-only"); 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; @@ -1166,6 +1182,8 @@ static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_remove_image, "remove", "NAME|PATH…", 2, VERB_ANY, 0, + "Remove a portable service image"); 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; @@ -1197,6 +1215,8 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 3, 3, 0, + "Set image or pool size limit (disk quota)"); 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; @@ -1256,176 +1276,104 @@ static int dump_profiles(void) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("portablectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sAttach or detach portable services from the local system.%s\n" - "\nCommands:\n" - " list List available portable service images\n" - " inspect NAME|PATH [PREFIX...]\n" - " Show details of specified portable service image\n" - " attach NAME|PATH [PREFIX...]\n" - " Attach the specified portable service image\n" - " detach NAME|PATH [PREFIX...]\n" - " Detach the specified portable service image\n" - " reattach NAME|PATH [PREFIX...]\n" - " Reattach the specified portable service image\n" - " is-attached NAME|PATH Query if portable service image is attached\n" - " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n" - " remove NAME|PATH... Remove a portable service image\n" - " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n" - "\nOptions:\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" - " -q --quiet Suppress informational messages\n" - " -p --profile=PROFILE Pick security profile for portable service\n" - " --copy=copy|auto|symlink|mixed\n" - " Pick copying or symlinking of resources\n" - " --runtime Attach portable service until next reboot only\n" - " --no-reload Don't reload the system and service manager\n" - " --cat When inspecting include unit and os-release file\n" - " contents\n" - " --enable Immediately enable/disable the portable service\n" - " after attach/detach\n" - " --now Immediately start/stop the portable service after\n" - " attach/before detach\n" - " --no-block Don't block waiting for attach --now to complete\n" - " --extension=PATH Extend the image with an overlay\n" - " --force Skip 'already active' check when attaching or\n" - " detaching an image (with extensions)\n" - " --clean When detaching, also remove configuration, state,\n" - " cache, logs or runtime data of the portable\n" - " service(s)\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + (void) table_sync_column_widths(0, verbs, options); - return 0; -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Attach or detach portable services in the local system."); + + help_section("Commands"); + 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(); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("portablectl", "1"); + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_COPY, - ARG_RUNTIME, - ARG_NO_RELOAD, - ARG_CAT, - ARG_ENABLE, - ARG_NOW, - ARG_NO_BLOCK, - ARG_EXTENSION, - ARG_FORCE, - ARG_CLEAN, - 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 }, - { "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' }, - { "quiet", no_argument, NULL, 'q' }, - { "profile", required_argument, NULL, 'p' }, - { "copy", required_argument, NULL, ARG_COPY }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "cat", no_argument, NULL, ARG_CAT }, - { "enable", no_argument, NULL, ARG_ENABLE }, - { "now", no_argument, NULL, ARG_NOW }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "extension", required_argument, NULL, ARG_EXTENSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - - int r, c; +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, "hH:M:qp:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - case 'p': - if (streq(optarg, "help")) + OPTION('p', "profile", "PROFILE", + "Pick security profile for portable service"): + if (streq(opts.arg, "help")) return dump_profiles(); - if (!filename_is_valid(optarg)) + if (!filename_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unit profile name not valid: %s", optarg); + "Unit profile name not valid: %s", opts.arg); - arg_profile = optarg; + arg_profile = opts.arg; break; - case ARG_COPY: - if (streq(optarg, "auto")) + OPTION_LONG("copy", "MODE", + "Pick copying or symlinking of resources " + "(copy, auto, symlink, mixed)"): + if (streq(opts.arg, "auto")) arg_copy_mode = NULL; - else if (STR_IN_SET(optarg, "copy", "symlink", "mixed")) - arg_copy_mode = optarg; - else if (streq(optarg, "help")) { + else if (STR_IN_SET(opts.arg, "copy", "symlink", "mixed")) + arg_copy_mode = opts.arg; + else if (streq(opts.arg, "help")) { puts("auto\n" "copy\n" "symlink\n" @@ -1433,90 +1381,81 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --copy= argument: %s", optarg); - + "Failed to parse --copy= argument: %s", opts.arg); break; - case ARG_RUNTIME: + OPTION_LONG("runtime", NULL, + "Attach portable service until next reboot only"): arg_runtime = true; break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, + "Don't reload the system and service manager"): arg_reload = false; break; - case ARG_CAT: + OPTION_LONG("cat", NULL, + "When inspecting include unit and os-release file contents"): arg_cat = true; break; - case ARG_ENABLE: + OPTION_LONG("enable", NULL, + "Immediately enable/disable the portable service after attach/detach"): arg_enable = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Immediately start/stop the portable service after attach/before detach"): arg_now = true; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, + "Don't block waiting for attach --now to complete"): arg_no_block = true; break; - case ARG_EXTENSION: - r = strv_extend(&arg_extension_images, optarg); + OPTION_LONG("extension", "PATH", + "Extend the image with an overlay"): + r = strv_extend(&arg_extension_images, opts.arg); if (r < 0) return log_oom(); break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Skip 'already active' check when attaching or detaching an image (with extensions)"): arg_force = true; break; - case ARG_CLEAN: + OPTION_LONG("clean", NULL, + "When detaching, also remove configuration, state, " + "cache, logs or runtime data of the portable service(s)"): arg_clean = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "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 }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + 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 e6ee724ac82042f92eace0d6c015dd9681122bf5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 6 May 2026 10:06:48 +0100 Subject: [PATCH 1496/2155] test: bump timeout in TEST-07-PID1.socket-on-failure The timeout is too short so under sanitizers the test sometimes fails: [ 3502.317855] TEST-07-PID1.sh[16212]: + mkdir -p /tmp/TEST-07-PID1-socket-29942 [ 3502.317855] TEST-07-PID1.sh[16212]: + cat [ 3502.317855] TEST-07-PID1.sh[16212]: + cat [ 3502.317855] TEST-07-PID1.sh[16212]: + cat [ 3502.317855] TEST-07-PID1.sh[16212]: + systemctl start TEST-07-PID1-socket-29942.socket [ 3502.317855] TEST-07-PID1.sh[16212]: + systemctl is-active TEST-07-PID1-socket-29942.socket [ 3502.319398] TEST-07-PID1.sh[16221]: active [ 3502.319758] TEST-07-PID1.sh[16212]: + [[ -S /tmp/TEST-07-PID1-socket-29942/test ]] [ 3502.319758] TEST-07-PID1.sh[16212]: + systemctl stop TEST-07-PID1-socket-29942.socket [ 3502.319758] TEST-07-PID1.sh[16212]: + rm /tmp/TEST-07-PID1-socket-29942/test [ 3502.319758] TEST-07-PID1.sh[16212]: + chattr +i /tmp/TEST-07-PID1-socket-29942 [ 3502.320499] TEST-07-PID1.sh[16227]: + systemctl start TEST-07-PID1-socket-29942.socket [ 3502.320914] TEST-07-PID1.sh[16228]: Job failed. See "journalctl -xe" for details. [ 3502.321560] TEST-07-PID1.sh[16212]: + systemctl is-failed TEST-07-PID1-socket-29942.socket [ 3502.321856] TEST-07-PID1.sh[16230]: failed [ 3502.322315] TEST-07-PID1.sh[16233]: ++ systemctl show TEST-07-PID1-socket-29942.socket -P SubState [ 3502.322722] TEST-07-PID1.sh[16212]: + assert_eq failed failed [ 3502.323111] TEST-07-PID1.sh[16235]: + set +ex [ 3502.323512] TEST-07-PID1.sh[16212]: + [[ ! -e /tmp/TEST-07-PID1-socket-29942/test ]] [ 3502.323512] TEST-07-PID1.sh[16212]: + timeout 10 bash -c 'until systemctl is-failed TEST-07-PID1-socket-OnFailure.service; do sleep .5; done' [ 3502.323949] TEST-07-PID1.sh[16238]: activating [ 3502.324376] TEST-07-PID1.sh[16241]: activating [ 3502.324754] TEST-07-PID1.sh[16244]: activating [ 3502.325164] TEST-07-PID1.sh[16247]: activating [ 3502.325587] TEST-07-PID1.sh[16250]: activating [ 3502.325938] TEST-07-PID1.sh[16253]: activating [ 3502.326393] TEST-07-PID1.sh[16256]: activating [ 3502.326791] TEST-07-PID1.sh[16259]: activating [ 3502.327180] TEST-07-PID1.sh[16262]: activating [ 3502.327558] TEST-07-PID1.sh[16265]: activating [ 3502.327971] TEST-07-PID1.sh[16268]: activating [ 3502.328409] TEST-07-PID1.sh[16212]: + at_exit [ 3502.328409] TEST-07-PID1.sh[16212]: + systemctl stop TEST-07-PID1-socket-29942.socket [ 3502.328409] TEST-07-PID1.sh[16212]: + rm -f /run/systemd/system/TEST-07-PID1-socket-29942.socket /run/systemd/system/TEST-07-PID1-socket-29942.service /run/systemd/system/TEST-07-PID1-socket-OnFailure.service [ 3502.328945] TEST-07-PID1.sh[147]: + echo 'Subtest /usr/lib/systemd/tests/testdata/units/TEST-07-PID1.socket-on-failure.sh failed' Bump from 10s to 60s to try and make it less flaky --- test/units/TEST-07-PID1.socket-on-failure.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-07-PID1.socket-on-failure.sh b/test/units/TEST-07-PID1.socket-on-failure.sh index affcec44ff265..44b8a43634faa 100755 --- a/test/units/TEST-07-PID1.socket-on-failure.sh +++ b/test/units/TEST-07-PID1.socket-on-failure.sh @@ -55,7 +55,7 @@ systemctl is-failed "$UNIT_NAME.socket" assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "failed" [[ ! -e "/tmp/$UNIT_NAME/test" ]] -timeout 10 bash -c "until systemctl is-failed TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" +timeout --foreground 60 bash -c "until systemctl is-failed TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" chattr -i "/tmp/$UNIT_NAME" @@ -65,7 +65,7 @@ mkdir "/tmp/$UNIT_NAME/test" systemctl is-failed "$UNIT_NAME.socket" assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "failed" -timeout 10 bash -c "while [[ -d '/tmp/$UNIT_NAME/test' ]]; do sleep .5; done" +timeout --foreground 60 bash -c "while [[ -d '/tmp/$UNIT_NAME/test' ]]; do sleep .5; done" [[ ! -e "/tmp/$UNIT_NAME/test" ]] systemctl is-active TEST-07-PID1-socket-OnFailure.service From 4394046ee6f56092179631a3ba117e2a582490e9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 23:17:11 +0100 Subject: [PATCH 1497/2155] core: when skipping state deserializing units, also skip job subsections If a unit has active jobs, when it gets serialized there are job subsections, each with their own empty line marker. The skipping function ignores this and skips until the marker, but then leaves the job in place, breaking deserialization. Consume jobs subsections too. This shows up now that there's TEST-07-PID1.alias-corruption, which occasionally fails when the aliased unit happens to still have a job when the reexec happens. [ 967.551630] TEST-07-PID1.sh[179]: + echo 'Testing with: systemctl daemon-reexec' [ 967.551630] TEST-07-PID1.sh[179]: Testing with: systemctl daemon-reexec [ 968.405274] TEST-07-PID1.sh[179]: + echo '--- Attempt 1/3 ---' [ 968.405274] TEST-07-PID1.sh[179]: --- Attempt 1/3 --- [ 968.698641] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...' [ 968.698641] TEST-07-PID1.sh[179]: Running daemon-reexec... [ 969.130261] TEST-07-PID1.sh[179]: + echo 'legit.service PID remains 1282. Attempt 1 passed.' [ 969.130261] TEST-07-PID1.sh[179]: legit.service PID remains 1282. Attempt 1 passed. [ 970.870456] TEST-07-PID1.sh[179]: + echo '--- Attempt 2/3 ---' [ 970.870456] TEST-07-PID1.sh[179]: --- Attempt 2/3 --- [ 971.267205] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...' [ 971.267205] TEST-07-PID1.sh[179]: Running daemon-reexec... [ 971.715743] TEST-07-PID1.sh[179]: + echo 'legit.service PID changed from 1282 to 1643!' [ 971.715743] TEST-07-PID1.sh[179]: legit.service PID changed from 1282 to 1643! Follow-up for a77c7a8224447890a304bd857f412c8103f217f1 Follow-up for 0742986650b36b604613f9aaa1f6bd45b51c0e67 --- src/core/unit-serialize.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/unit-serialize.c b/src/core/unit-serialize.c index d2aee125ddda6..867472a61923e 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -402,6 +402,23 @@ int unit_deserialize_state_skip(FILE *f) { /* End marker */ if (isempty(line)) return 1; + + /* A unit's serialized state may embed one or more "job" subsections (for u->job and + * u->nop_job), each itself terminated by an empty line. We must consume those nested + * sections fully, otherwise we'd stop at the job's end marker and treat the rest of the + * unit's fields as a new top-level entry. */ + if (streq(line, "job")) + for (;;) { + _cleanup_free_ char *job_line = NULL; + + r = read_stripped_line(f, LONG_LINE_MAX, &job_line); + if (r < 0) + return log_error_errno(r, "Failed to read serialization line: %m"); + if (r == 0) + return 0; + if (isempty(job_line)) + break; + } } } From a1aed3eae2c4e23dc7e3e42aefeb6133491218d0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 May 2026 23:20:09 +0100 Subject: [PATCH 1498/2155] test: add reproducer for alias-corruption skip-desync regression The existing alias-corruption subtest only intermittently caught the regression where unit_deserialize_state_skip() stopped at the first empty line and thus failed to consume embedded "job" subsections (written by job_serialize() when u->job or u->nop_job is non-NULL). This same skip routine is also used by the pre-scan that builds the set of serialized unit names (added in a77c7a8224 to detect stale alias state). When skip terminates early at a job's end marker, the scan desyncs and every subsequent unit name is dropped from the set, silently bypassing the alias-corruption protection for those units. Whether the bug fired depended on: 1. Whether any unit happened to carry a pending job at the moment of reload/reexec (mostly chance, depends on timers, transient units, load, etc.). 2. Hashmap iteration order during serialization (per-boot randomized siphash seed) determining if a job-bearing unit was written before a sus-NN.service alias. To try and make the regression deterministic, add a "with_pending_jobs" mode that creates 50 Type=oneshot services and starts them with systemctl --no-block. Each remains in "activating" state with u->job set forever, guaranteeing the serialized stream contains many embedded job subsections regardless of hashmap order, and that at least one of them precedes the sus units. Without the fix, the desync drops the sus-NN entries from the set, the alias-corruption check set_contains(serialized_units, u->id) returns false for every alias, and legit.service's MainPID is overwritten on every run. Co-developed-by: Claude Opus 4.7 --- test/units/TEST-07-PID1.alias-corruption.sh | 99 +++++++++++++++++---- 1 file changed, 82 insertions(+), 17 deletions(-) diff --git a/test/units/TEST-07-PID1.alias-corruption.sh b/test/units/TEST-07-PID1.alias-corruption.sh index e2fc00d410f85..009ee484a0250 100755 --- a/test/units/TEST-07-PID1.alias-corruption.sh +++ b/test/units/TEST-07-PID1.alias-corruption.sh @@ -58,11 +58,20 @@ reap_abandoned_pids() { run_test() { local reload_cmd="${1:?}" + # If "with_pending_jobs", also create many Type=oneshot units that hang in + # "activating" state with a pending job, to ensure that the serialized state + # contains embedded "job" subsections to fully exercise the deserialization + local pending_jobs="${2:-}" + local n_sus=20 local current_pid journal_warnings new_pid orig_pid pid reload_start unit warning_count + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + n_sus=100 + fi + echo "" echo "=========================================" - echo "Testing with: systemctl $reload_cmd" + echo "Testing with: systemctl $reload_cmd${pending_jobs:+ ($pending_jobs)}" echo "=========================================" cat >/run/systemd/system/legit.service <<'EOF' @@ -71,15 +80,27 @@ 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 + # Create 100 sus units. They must be running so systemd CANNOT garbage + # collect them. If they are dead/stopped, systemd can remove them from + # memory before serialization. + # + # In with_pending_jobs mode they additionally get a pending restart job + # queued via 'systemctl --no-block restart' AFTER they are running, so the + # serialized stream contains 'job\n...\n\n' subsections AND the units have + # a real MainPID. The skip-desync regression in unit_deserialize_state_skip() + # stops at the job subsection's empty line marker, leaving the rest of the + # serialized stream to be consumed as garbage. If legit.service is dropped + # from the collected names set as a result, the alias-protection branch in + # manager_deserialize_one_unit() is bypassed and a sus unit's MainPID + # overwrites legit.service's MainPID. + echo "Creating $n_sus sus units..." + for i in $(seq -f "%03g" 1 "$n_sus"); do cat >/run/systemd/system/sus-"${i}".service <<'EOF' [Service] Type=simple ExecStart=/bin/sleep infinity +ExecReload=/bin/sleep infinity +TimeoutStartSec=infinity EOF done @@ -89,11 +110,32 @@ EOF systemctl start legit.service echo "Starting sus units..." - for i in $(seq -f "%02g" 1 20); do + for i in $(seq -f "%03g" 1 "$n_sus"); do systemctl start sus-"${i}".service done - echo "Setup complete: 1 running legit unit, 20 running sus units" + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + # Queue a hanging reload job on each running sus unit. ExecReload runs + # 'sleep infinity', so the reload job stays in the queue forever; the + # unit stays active with its real MainPID, and the serialized stream + # contains both 'main-pid=...' and 'job\n...\n\n' subsections. + for i in $(seq -f "%03g" 1 "$n_sus"); do + systemctl --no-block reload sus-"${i}".service + done + # Make sure at least one reload job is actually queued, otherwise the + # serialized stream might not contain any job subsections yet. + for i in {1..100}; do + [[ -n "$(systemctl list-jobs --no-legend | grep -E '^[[:space:]]*[0-9]+ sus-' || true)" ]] && break + if (( i == 100 )); then + echo "ERROR: no sus-*.service reload jobs are pending" + systemctl list-jobs || true + return 1 + fi + sleep 0.1 + done + fi + + echo "Setup complete: 1 running legit unit, $n_sus ${pending_jobs:+job-bearing }sus units" orig_pid=$(systemctl show -P MainPID legit.service) echo "Original legit PID: $orig_pid" @@ -103,26 +145,28 @@ EOF 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. + # Since ordering is not deterministic we should loop several times to + # reduce false negative rate (ordering luck). The skip-desync regression + # also depends on iteration order: legit.service must happen to be + # serialized right after a job-bearing unit for its name to be dropped from + # the collected set (which is what bypasses the alias-protection check), + # so multiple attempts are needed in both modes. 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 + for i in $(seq -f "%03g" 1 "$n_sus"); 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 + for i in $(seq -f "%03g" 1 "$n_sus"); do rm -f /run/systemd/system/sus-"${i}".service ln -sf /run/systemd/system/legit.service /run/systemd/system/sus-"${i}".service done @@ -185,12 +229,14 @@ EOF echo "Resetting sus units..." # We must fully reset to get independent running units again - for i in $(seq -f "%02g" 1 20); do + for i in $(seq -f "%03g" 1 "$n_sus"); do rm -f /run/systemd/system/sus-"${i}".service cat >/run/systemd/system/sus-"${i}".service <<'EOF' [Service] Type=simple ExecStart=/bin/sleep infinity +ExecReload=/bin/sleep infinity +TimeoutStartSec=infinity EOF done @@ -198,10 +244,25 @@ EOF # Ensure they are running again (they might have been # abandoned/killed during the transition) - for i in $(seq -f "%02g" 1 20); do + for i in $(seq -f "%03g" 1 "$n_sus"); do systemctl start sus-"${i}".service done + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + for i in $(seq -f "%03g" 1 "$n_sus"); do + systemctl --no-block reload sus-"${i}".service + done + for i in {1..100}; do + [[ -n "$(systemctl list-jobs --no-legend | grep -E '^[[:space:]]*[0-9]+ sus-' || true)" ]] && break + if (( i == 100 )); then + echo "ERROR: no sus-*.service reload jobs are pending after reset" + systemctl list-jobs || true + return 1 + fi + sleep 0.1 + done + fi + echo "Reset complete." fi done @@ -214,7 +275,7 @@ EOF cleanup_test_units() { reap_abandoned_pids || true systemctl stop legit.service 2>/dev/null || true - for i in $(seq -f "%02g" 1 20); do + for i in $(seq -f "%03g" 1 100); do systemctl stop sus-"${i}".service 2>/dev/null || true rm -f /run/systemd/system/sus-"${i}".service done @@ -227,3 +288,7 @@ trap cleanup_test_units EXIT run_test daemon-reload cleanup_test_units run_test daemon-reexec +cleanup_test_units +run_test daemon-reload with_pending_jobs +cleanup_test_units +run_test daemon-reexec with_pending_jobs From cbadda5e60bae8858f38379aae3556ed3b03721f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 6 May 2026 12:40:26 +0200 Subject: [PATCH 1499/2155] vmspawn: reject --bind-volume= duplicates at parse time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bind_volume_parse() does not look at peers, so passing the same PROVIDER:VOLUME twice on the command line silently produces two parsed entries in arg_bind_volumes. vmspawn_bind_volume_acquire() then builds two DriveInfo with identical d->id (":"). At boot, bridge_register_drive() puts d->id into the b->block_devices hashmap; the second insert returns -EEXIST and the user sees a bare "File exists" with no context for which volume is responsible. Reject the collision at the parse site with a linear scan over the existing array — n_items is small (one entry per --bind-volume on the command line), and a clear error message naming the offending volume is much more useful than the late EEXIST from the QMP setup loop. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index ee8ae518f0330..57b7697079ee4 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -787,6 +787,12 @@ static int parse_argv(int argc, char *argv[]) { bv->config, valid); } + FOREACH_ARRAY(it, arg_bind_volumes.items, arg_bind_volumes.n_items) + if (streq((*it)->provider, bv->provider) && streq((*it)->volume, bv->volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Volume '%s:%s' specified more than once for --bind-volume=.", + bv->provider, bv->volume); + if (!GREEDY_REALLOC(arg_bind_volumes.items, arg_bind_volumes.n_items + 1)) return log_oom(); arg_bind_volumes.items[arg_bind_volumes.n_items++] = TAKE_PTR(bv); From a5004653acd25d166ab8c10d987173f3bae0420a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 6 May 2026 14:46:19 +0200 Subject: [PATCH 1500/2155] update TODO --- TODO.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO.md b/TODO.md index f3d1070c6c746..466eb223426dc 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- bootctl link + sysupdate integration + - make sysupdate call out to a special varlink dir on completion + - bind bootctl link socket in there, which when invoked goes to new dir in + /var/ where downloaded kernels+confext+sysext are dropped in (place in + .v/) and then does "bootctl link" on them. + - a tool that can prep credentials, put them in the ESP, for provisioning systems for SBC. Should be doing what sysinstall does with the credentials, and maybe even *be* sysinstall. From a7e276a82f7e2bf080685afca83d30215b1307a5 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 6 May 2026 14:03:29 +0200 Subject: [PATCH 1501/2155] dbus: limit the number of env variables to something reasonable, vol. 3 Let's limit the number of environment variables when creating a transient unit via StartTransientUnit as well, since validating the environment variable names/assignments is expensive. Follow-up for 49c1e1bcf2b482b6de35a4212a06ed1d8c382745. --- src/core/dbus-execute.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index aea9fbb304e22..2329762f16b68 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -3456,6 +3456,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments."); if (!strv_env_is_valid(l)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); @@ -3490,6 +3493,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names or assignments."); if (!strv_env_name_or_assignment_is_valid(l)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UnsetEnvironment= list."); @@ -3642,6 +3648,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names."); if (!strv_env_name_is_valid(l)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment= block."); From f0a171d1d8d511bbdc8fd1a5cb9ec2b2b156adbc Mon Sep 17 00:00:00 2001 From: Nita Vesa Date: Wed, 6 May 2026 19:37:14 +0300 Subject: [PATCH 1502/2155] hwdb: add Fn-key mappings for MSI GE76 Raider 10UG --- 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 99006885f076f..8c1605930da97 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1613,6 +1613,14 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE60*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE70*:* KEYBOARD_KEY_c2=ejectcd +# MSI GE76 Raider 10UG uses Fn+F3 for touchpad and Fn+F6 and Fn+F7 +# for crosshair-mode and gaming-mode respectively but the latter +# two were previously not generating any keycodes under Linux +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pnGE76Raider10UG:* + KEYBOARD_KEY_76=touchpad_toggle + KEYBOARD_KEY_f3=prog2 + KEYBOARD_KEY_8a=prog3 + # some MSI models generate ACPI/input events on the LNXVIDEO input devices, # plus some extra synthesized ones on atkbd as an echo of actually changing the # brightness; so ignore those atkbd ones, to avoid loops From f91e0730560acd314e03aa4d38370345b5040017 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 6 May 2026 17:36:26 +0200 Subject: [PATCH 1503/2155] ci: install pefile with pip as well Otherwise, ukify gets disabled, so its build wasn't being tested by this job. --- .github/workflows/build-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 506479a55845d..d33229d42936d 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -156,8 +156,8 @@ if [ -n "$bpftool_dir" ]; then fi if [[ -n "$CUSTOM_PYTHON" ]]; then - # If CUSTOM_PYTHON is set we need to pull jinja2 from pip, as a local interpreter is used - pip3 install --user --break-system-packages jinja2 + # If CUSTOM_PYTHON is set we need to pull dependencies from pip, as a local interpreter is used + pip3 install --user --break-system-packages jinja2 pefile fi $CC --version From 106bb47d2d193e546821da593d1c446ea53414c3 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 6 May 2026 15:31:59 +0200 Subject: [PATCH 1504/2155] meson: improve precision of pefile checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, if boot was enabled but ukify wasn't, the check for the pefile Python module would be omitted. The check in want_ukify was incorrect — with Meson it's only possible to check for Python dependencies available for the build Python. When the build Python is not the same as the Python that will be used to run the installed ukify, this would incorrectly require ukify to be available to the build Python. The most common, but not the only case where this would happen would be when cross compiling. If bootloader and tests are disabled, there's no need for the build Python to have pefile even when installing ukify. Therefore, check in the more specific case of ukify's tests being enabled, in which case pefile is required at build time. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d6fbd7c2b7ea6..59bfde7f0cb46 100644 --- a/meson.build +++ b/meson.build @@ -1686,7 +1686,7 @@ if have and efi_arch == 'x64' and cc.links(''' efi_cpu_family_alt = 'x86' endif -want_ukify = pymod.find_installation('python3', required: get_option('ukify'), modules : ['pefile']).found() +want_ukify = get_option('ukify').allowed() conf.set10('ENABLE_UKIFY', want_ukify) ##################################################################### From 67e23e0e050fca0279fc6045de20ed05b0b97bee Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Wed, 6 May 2026 17:35:48 +0200 Subject: [PATCH 1505/2155] vmspawn: search XDG_DATA_DIRS for QEMU firmware get_firmware_search_dirs() previously hardcoded /usr/share/qemu/firmware as the only system-wide search path. That assumption breaks on distributions that deliberately do not populate /usr/share, making vmspawn fail: "Failed to find OVMF config: No such file or directory". NixOS exposes those firmware locations through XDG_DATA_DIRS. Extend the search list with XDG_DATA_HOME/XDG_DATA_DIRS. This is the standard XDG mechanism and is already what QEMU itself uses for the same descriptors, so behavior matches user expectations across tooling. To avoid regressing setups where user has set XDG_DATA_DIRS to a custom value that omits /usr/share, keep /usr/share/qemu/firmware as an unconditional fallback. Precedence is unchanged: XDG_CONFIG_HOME/qemu/firmware still wins over /etc/qemu/firmware, which still wins over any shared-data dir. Co-developed-by: Claude Opus 4.7 Signed-off-by: Paul Meyer --- src/vmspawn/vmspawn-util.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 72187b6731a0a..c5ab40a0d2055 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -254,7 +254,9 @@ static int get_firmware_search_dirs(char ***ret) { /* Search in: * - $XDG_CONFIG_HOME/qemu/firmware * - /etc/qemu/firmware - * - /usr/share/qemu/firmware + * - $XDG_DATA_HOME/qemu/firmware (default: ~/.local/share/qemu/firmware) + * - each entry in $XDG_DATA_DIRS suffixed with /qemu/firmware + * (default: /usr/local/share/qemu/firmware, /usr/share/qemu/firmware) * * Prioritising entries in "more specific" directories */ @@ -264,10 +266,27 @@ static int get_firmware_search_dirs(char ***ret) { return r; _cleanup_strv_free_ char **l = NULL; - l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"); + l = strv_new(user_firmware_dir, "/etc/qemu/firmware"); if (!l) return log_oom_debug(); + _cleanup_strv_free_ char **data_dirs = NULL; + r = sd_path_lookup_strv(SD_PATH_SEARCH_SHARED, "/qemu/firmware", &data_dirs); + if (r < 0) + return r; + + r = strv_extend_strv(&l, data_dirs, /* filter_duplicates = */ true); + if (r < 0) + return log_oom_debug(); + + /* Always include /usr/share/qemu/firmware as a final fallback, + * even if a custom $XDG_DATA_DIRS replaced it. */ + r = strv_extend(&l, "/usr/share/qemu/firmware"); + if (r < 0) + return log_oom_debug(); + + strv_uniq(l); + *ret = TAKE_PTR(l); return 0; } @@ -424,13 +443,8 @@ int find_ovmf_config( if (r < 0) return r; - /* Search in: - * - $XDG_CONFIG_HOME/qemu/firmware - * - /etc/qemu/firmware - * - /usr/share/qemu/firmware - * - * Prioritising entries in "more specific" directories - */ + /* Search paths are constructed by get_firmware_search_dirs(), + * prioritising entries in "more specific" directories. */ r = list_ovmf_config(&conf_files); if (r < 0) From de29e618434ad53fee7e60cfc2e3d186968b21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:07:52 +0200 Subject: [PATCH 1506/2155] portablectl: actually allow set-limit with one arg In the man page and in the actual code, the first arg is optional. But the arg limit in the verbs table did not allow only one arg to be specified. Fixes: 61d0578b07b97cbffebfd350bac481274e310d39 --- src/portable/portablectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 575ab4149aa63..c70d50634676a 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1215,7 +1215,7 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } -VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 3, 3, 0, +VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 2, 3, 0, "Set image or pool size limit (disk quota)"); 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; From a42f1ebb94062a9c11c072b6ca8ff84c509f9445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 18:23:55 +0200 Subject: [PATCH 1507/2155] report-cgroup-server: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/report/report-cgroup-server.c | 60 +++++++++++-------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/report/report-cgroup-server.c b/src/report/report-cgroup-server.c index eef2ec05fcbfd..9f5ac9370694a 100644 --- a/src/report/report-cgroup-server.c +++ b/src/report/report-cgroup-server.c @@ -1,15 +1,14 @@ /* 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 "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "report-cgroup.h" #include "varlink-io.systemd.Metrics.h" #include "varlink-util.h" @@ -46,63 +45,44 @@ static int vl_server(void) { } static int help(void) { - _cleanup_free_ char *url = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-report-cgroup", "8", &url); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - 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); + help_cmdline("[OPTIONS...]"); + help_abstract("Report cgroup metrics."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-report-cgroup", "8"); 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; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); From b49a4daa1b118cdb55bf0389dea09375b2a4e577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 13:39:25 +0200 Subject: [PATCH 1508/2155] coredumpctl: convert to OPTION and VERB macros The order of options is changed (to what was present in parse_argv). I don't the order in --help was mostly random, as is the new one, so I didn't try to preserve the old order. Some help strings are reworded/adjusted. Co-developed-by: Claude Opus 4.7 --- src/coredump/coredumpctl.c | 230 ++++++++++++++----------------------- 1 file changed, 85 insertions(+), 145 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index b6ca0f8b7dd23..d89da78755f35 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -14,6 +13,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -29,6 +29,7 @@ #include "format-util.h" #include "fs-util.h" #include "glob-util.h" +#include "help-util.h" #include "image-policy.h" #include "io-util.h" #include "journal-internal.h" @@ -39,12 +40,12 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "string-util.h" @@ -177,224 +178,166 @@ static int acquire_journal(sd_journal **ret, char **matches) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("coredumpctl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("List or retrieve coredumps from the journal."); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sList or retrieve coredumps from the journal.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [MATCHES...] List available coredumps (default)\n" - " info [MATCHES...] Show detailed information about one or more coredumps\n" - " dump [MATCHES...] Print first matching coredump to stdout\n" - " debug [MATCHES...] Start a debugger for the first matching coredump\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version string\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers\n" - " --json=pretty|short|off Generate JSON output\n" - " --debugger=DEBUGGER Use the given debugger\n" - " -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n" - " -n INT Show maximum number of rows\n" - " -1 Show information about most recent entry only\n" - " -S --since=DATE Only print coredumps since the date\n" - " -U --until=DATE Only print coredumps until the date\n" - " -r --reverse Show the newest entries first\n" - " -F --field=FIELD List all values a certain field takes\n" - " -o --output=FILE Write output to FILE\n" - " --file=PATH Use journal file\n" - " -D --directory=DIR Use journal files from directory\n\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --all Look at all journal files instead of local ones\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" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("coredumpctl", "1"); 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_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_DEBUGGER, - ARG_FILE, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_ALL, - }; - - int c, 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 }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - { "output", required_argument, NULL, 'o' }, - { "field", required_argument, NULL, 'F' }, - { "file", required_argument, NULL, ARG_FILE }, - { "directory", required_argument, NULL, 'D' }, - { "reverse", no_argument, NULL, 'r' }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "quiet", no_argument, NULL, 'q' }, - { "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 }, - { "all", no_argument, NULL, ARG_ALL }, - {} - }; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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_DEBUGGER: - arg_debugger = optarg; + OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"): + arg_debugger = opts.arg; break; - case 'A': { + OPTION('A', "debugger-arguments", "…", "Pass the given arguments to the debugger"): { _cleanup_strv_free_ char **l = NULL; - r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE); + r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE); if (r < 0) - return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg); + return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg); strv_free_and_replace(arg_debugger_args, l); break; } - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Use journal file"): + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - case 'o': + OPTION('o', "output", "FILE", "Write output to FILE"): if (arg_output) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot set output more than once."); - arg_output = optarg; + arg_output = opts.arg; break; - case 'S': - r = parse_timestamp(optarg, &arg_since); + OPTION('S', "since", "DATE", "Only print coredumps since the date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'U': - r = parse_timestamp(optarg, &arg_until); + OPTION('U', "until", "DATE", "Only print coredumps until the date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'F': + OPTION('F', "field", "FIELD", "List all values a certain field takes"): if (arg_field) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --field/-F more than once."); - arg_field = optarg; + arg_field = opts.arg; break; - case '1': + OPTION_SHORT('1', NULL, "Show information about most recent entry only"): arg_rows_max = 1; arg_reverse = true; break; - case 'n': { + OPTION_SHORT('n', "INT", "Show at most this many rows"): { unsigned n; - r = safe_atou(optarg, &n); + r = safe_atou(opts.arg, &n); if (r < 0 || n < 1) return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), - "Invalid numeric parameter to -n: %s", optarg); + "Invalid numeric parameter to -n: %s", opts.arg); arg_rows_max = n; break; } - case 'D': - arg_directory = optarg; + OPTION('D', "directory", "DIR", "Use journal files from directory"): + arg_directory = opts.arg; break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, false, &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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case 'r': + OPTION('r', "reverse", NULL, "Show the newest entries first"): arg_reverse = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): arg_quiet = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_ALL: + OPTION_LONG("all", NULL, "Look at all journal files instead of local ones"): arg_all = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY && @@ -405,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) { if ((!!arg_directory + !!arg_image + !!arg_root) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root=, --image= or -D/--directory=, the combination of these options is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -902,6 +846,10 @@ static int print_entry( return print_info(stdout, j, n_found > 0); } +VERB(verb_dump_list, "list", "[MATCHES…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "List available coredumps"); +VERB(verb_dump_list, "info", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Show detailed information about one or more coredumps"); 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; @@ -1146,6 +1094,8 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) return r; } +VERB(verb_dump_core, "dump", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Print first matching coredump to stdout"); 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; @@ -1182,6 +1132,10 @@ static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB(verb_run_debug, "debug", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Start a debugger for the first matching coredump"); +VERB(verb_run_debug, "gdb", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + /* help= */ NULL); 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, @@ -1372,30 +1326,16 @@ static int check_units_active(void) { return c; } -static int coredumpctl_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = NULL; int r, units_active; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1425,7 +1365,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - r = coredumpctl_main(argc, argv); + r = dispatch_verb_with_args(args, NULL); if (units_active > 0) printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n", From 561f3a65da050dedaf96854c460bbe97258e3d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 13:59:44 +0200 Subject: [PATCH 1509/2155] mount: convert to OPTION macros Previously, we'd show a partial synopsis for systemd-mount in --help for systemd-umount. I don't think it makes sense to do that. So now the --help for systemd-umount is separate, with just its syntax and a new blurb. "transiently" is dropped from the description. Mount points generally are transient, so no need to say that. (E.g. the man page for mount just says "attach" and "detach".) Co-developed-by: Claude Opus 4.7 --- src/mount/mount-tool.c | 300 +++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 194 deletions(-) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index f1c4c90d76883..80846c1c2a929 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "sd-device.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-error.h" @@ -20,15 +19,16 @@ #include "format-table.h" #include "format-util.h" #include "fstab-util.h" +#include "help-util.h" #include "libmount-util.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "process-util.h" #include "runtime-scope.h" #include "stat-util.h" @@ -108,296 +108,206 @@ static int parse_where(const char *input, char **ret_where) { return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; +static int help(char *argv[]) { + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-mount", "1", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + if (invoked_as(argv, "systemd-umount")) { + help_cmdline("[OPTIONS…] WHAT|WHERE…"); + help_abstract("Unmount one or more mount points."); + } else { + help_cmdline("[OPTIONS…] WHAT [WHERE]"); + help_cmdline("[OPTIONS…] --tmpfs [NAME] WHERE"); + help_cmdline("[OPTIONS…] --list"); + help_cmdline("[OPTIONS…] --umount WHAT|WHERE…"); + help_abstract("Establish a mount or auto-mount point."); + } - printf("systemd-mount [OPTIONS...] WHAT [WHERE]\n" - "systemd-mount [OPTIONS...] --tmpfs [NAME] WHERE\n" - "systemd-mount [OPTIONS...] --list\n" - "%1$s [OPTIONS...] %7$sWHAT|WHERE...\n" - "\n%5$sEstablish a mount or auto-mount point transiently.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-block Do not wait until operation finished\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers\n" - " -l --full Do not ellipsize output\n" - " --no-ask-password Do not prompt for password\n" - " -q --quiet Suppress information messages during runtime\n" - " --json=pretty|short|off Generate JSON output\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --discover Discover mount device metadata\n" - " -t --type=TYPE File system type\n" - " -o --options=OPTIONS Mount options\n" - " --owner=USER Add uid= and gid= options for USER\n" - " --fsck=no Don't run file system check before mount\n" - " --description=TEXT Description for unit\n" - " -p --property=NAME=VALUE Set mount unit property\n" - " --automount=BOOL Create an automount point\n" - " -A Same as --automount=yes\n" - " --timeout-idle-sec=SEC Specify automount idle timeout\n" - " --automount-property=NAME=VALUE\n" - " Set automount unit property\n" - " --bind-device Bind automount unit to device\n" - " --list List mountable block devices\n" - " -u --umount Unmount mount points\n" - " -G --collect Unload unit after it stopped, even when failed\n" - " -T --tmpfs Create a new tmpfs on the mount point\n" - " --canonicalize=BOOL Controls whether to canonicalize path before\n" - " operation\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount "); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-mount", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_BLOCK, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - ARG_DISCOVER, - ARG_MOUNT_TYPE, - ARG_MOUNT_OPTIONS, - ARG_OWNER, - ARG_FSCK, - ARG_DESCRIPTION, - ARG_TIMEOUT_IDLE, - ARG_AUTOMOUNT, - ARG_AUTOMOUNT_PROPERTY, - ARG_BIND_DEVICE, - ARG_LIST, - ARG_JSON, - ARG_CANONICALIZE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "full", no_argument, NULL, 'l' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "type", required_argument, NULL, 't' }, - { "options", required_argument, NULL, 'o' }, - { "owner", required_argument, NULL, ARG_OWNER }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "property", required_argument, NULL, 'p' }, - { "automount", required_argument, NULL, ARG_AUTOMOUNT }, - { "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE }, - { "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY }, - { "bind-device", no_argument, NULL, ARG_BIND_DEVICE }, - { "list", no_argument, NULL, ARG_LIST }, - { "umount", no_argument, NULL, 'u' }, - { "unmount", no_argument, NULL, 'u' }, /* Compat spelling */ - { "collect", no_argument, NULL, 'G' }, - { "tmpfs", no_argument, NULL, 'T' }, - { "json", required_argument, NULL, ARG_JSON }, - { "canonicalize", required_argument, NULL, ARG_CANONICALIZE }, - {}, - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); if (invoked_as(argv, "systemd-umount")) arg_action = ACTION_UMOUNT; - while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:AuGlT", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); + OPTION_COMMON_HELP: + return help(argv); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = 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 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Run as user unit"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_DISCOVER: + OPTION_LONG("discover", NULL, "Discover mount device metadata"): arg_discover = true; break; - case 't': - r = free_and_strdup_warn(&arg_mount_type, optarg); + OPTION('t', "type", "TYPE", "File system type"): + r = free_and_strdup_warn(&arg_mount_type, opts.arg); if (r < 0) return r; break; - case 'o': - r = free_and_strdup_warn(&arg_mount_options, optarg); + OPTION('o', "options", "OPTIONS", "Mount options"): + r = free_and_strdup_warn(&arg_mount_options, opts.arg); if (r < 0) return r; break; - case ARG_OWNER: { - const char *user = optarg; + OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): { + const char *user = opts.arg; r = get_user_creds(&user, &arg_uid, &arg_gid, NULL, NULL, 0); if (r < 0) return log_error_errno(r, r == -EBADMSG ? "UID or GID of user %s are invalid." : "Cannot use \"%s\" as owner: %m", - optarg); + opts.arg); break; } - case ARG_FSCK: - r = parse_boolean_argument("--fsck=", optarg, &arg_fsck); + OPTION_LONG("fsck", "BOOL", "Run a file system check before mount"): + r = parse_boolean_argument("--fsck=", opts.arg, &arg_fsck); if (r < 0) return r; break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) + OPTION('p', "property", "NAME=VALUE", "Set mount unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case 'A': + OPTION_SHORT('A', NULL, "Same as --automount=yes"): arg_action = ACTION_AUTOMOUNT; break; - case ARG_AUTOMOUNT: - r = parse_boolean_argument("--automount=", optarg, NULL); + OPTION_LONG("automount", "BOOL", "Create an automount point"): + r = parse_boolean_argument("--automount=", opts.arg, NULL); if (r < 0) return r; arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT; break; - case ARG_TIMEOUT_IDLE: - r = parse_sec(optarg, &arg_timeout_idle); + OPTION_LONG("timeout-idle-sec", "SEC", "Specify automount idle timeout"): + r = parse_sec(opts.arg, &arg_timeout_idle); if (r < 0) - return log_error_errno(r, "Failed to parse timeout: %s", optarg); + return log_error_errno(r, "Failed to parse timeout: %s", opts.arg); arg_timeout_idle_set = true; break; - case ARG_AUTOMOUNT_PROPERTY: - if (strv_extend(&arg_automount_property, optarg) < 0) + OPTION_LONG("automount-property", "NAME=VALUE", "Set automount unit property"): + if (strv_extend(&arg_automount_property, opts.arg) < 0) return log_oom(); - break; - case ARG_BIND_DEVICE: + OPTION_LONG("bind-device", NULL, "Bind automount unit to device"): arg_bind_device = true; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List mountable block devices"): arg_action = ACTION_LIST; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount mount points"): {} + OPTION_LONG("unmount", NULL, /* help= */ NULL): /* compat spelling */ arg_action = ACTION_UMOUNT; break; - case 'G': + OPTION('G', "collect", NULL, "Unload unit after it stopped, even when failed"): arg_aggressive_gc = true; break; - case 'T': + OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"): arg_tmpfs = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_CANONICALIZE: - r = parse_boolean_argument("--canonicalize=", optarg, &arg_canonicalize); + OPTION_LONG("canonicalize", "BOOL", + "Controls whether to canonicalize path before operation"): + r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + if (arg_runtime_scope == RUNTIME_SCOPE_USER) { arg_ask_password = false; @@ -407,7 +317,7 @@ static int parse_argv(int argc, char *argv[]) { } if (arg_action == ACTION_LIST) { - if (optind < argc) + if (n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); @@ -415,22 +325,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Listing devices only supported locally."); } else if (arg_action == ACTION_UMOUNT) { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) - for (int i = optind; i < argc; i++) - if (!path_is_absolute(argv[i])) + STRV_FOREACH(a, args) + if (!path_is_absolute(*a)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path must be absolute when operating remotely or when canonicalization is turned off: %s", - argv[i]); + *a); } else { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); - if (argc > optind+2) + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "More than two arguments are not allowed."); @@ -439,16 +349,16 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--discover cannot be used in conjunction with --tmpfs."); - if (argc <= optind+1) { + if (n_args == 1) { arg_mount_what = strdup("tmpfs"); if (!arg_mount_what) return log_oom(); - r = parse_where(argv[optind], &arg_mount_where); + r = parse_where(args[0], &arg_mount_where); if (r < 0) return r; } else { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } @@ -463,12 +373,12 @@ static int parse_argv(int argc, char *argv[]) { arg_mount_type); } else { if (arg_mount_type && !fstype_is_blockdev_backed(arg_mount_type)) { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } else { _cleanup_free_ char *u = NULL; - const char *p = argv[optind]; + const char *p = args[0]; if (arg_canonicalize) { u = fstab_node_to_udev_node(p); @@ -494,8 +404,8 @@ static int parse_argv(int argc, char *argv[]) { } } - if (argc > optind+1) { - r = parse_where(argv[optind+1], &arg_mount_where); + if (n_args >= 2) { + r = parse_where(args[1], &arg_mount_where); if (r < 0) return r; } else if (!arg_tmpfs) @@ -524,6 +434,7 @@ static int parse_argv(int argc, char *argv[]) { } } + *remaining_args = args; return 1; } @@ -1077,18 +988,18 @@ static int umount_loop(sd_bus *bus, const char *backing_file) { return umount_by_device(bus, dev); } -static int action_umount(sd_bus *bus, int argc, char **argv) { +static int action_umount(sd_bus *bus, char **args) { int r, ret = 0; assert(bus); - assert(argv); - assert(argc > optind); + assert(args); + assert(!strv_isempty(args)); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *p = NULL; - r = path_simplify_alloc(argv[i], &p); + r = path_simplify_alloc(*arg, &p); if (r < 0) return r; @@ -1097,10 +1008,10 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { return ret; } - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *u = NULL, *p = NULL; - u = fstab_node_to_udev_node(argv[i]); + u = fstab_node_to_udev_node(*arg); if (!u) return log_oom(); @@ -1113,7 +1024,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { struct stat st; if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, argv[i]); + return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, *arg); r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */ @@ -1135,7 +1046,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { else r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown file type for unmounting: %s (from %s)", - p, argv[i]); + p, *arg); RET_GATHER(ret, r); } } @@ -1552,11 +1463,12 @@ static int list_devices(void) { static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1570,7 +1482,7 @@ static int run(int argc, char* argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); if (arg_action == ACTION_UMOUNT) - return action_umount(bus, argc, argv); + return action_umount(bus, args); if ((!arg_mount_type || fstype_is_blockdev_backed(arg_mount_type)) && !path_is_normalized(arg_mount_what)) From 355f2bec79c40e276a8dc3c089875399a9d3a123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 14:24:37 +0200 Subject: [PATCH 1510/2155] mount: stop showing mount options for systemd-unmount This only serves to confuse the user. --- src/mount/mount-tool.c | 80 +++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 80846c1c2a929..636930d277176 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -109,10 +109,10 @@ static int parse_where(const char *input, char **ret_where) { } static int help(char *argv[]) { - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *options_common = NULL, *options_mount = NULL; int r; - r = option_parser_get_help_table(&options); + r = option_parser_get_help_table(&options_common); if (r < 0) return r; @@ -125,13 +125,25 @@ static int help(char *argv[]) { help_cmdline("[OPTIONS…] --list"); help_cmdline("[OPTIONS…] --umount WHAT|WHERE…"); help_abstract("Establish a mount or auto-mount point."); + + r = option_parser_get_help_table_group("Mount options", &options_mount); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options_common, options_mount); } help_section("Options"); - r = table_print_or_warn(options); + r = table_print_or_warn(options_common); if (r < 0) return r; + if (options_mount) { + r = table_print_or_warn(options_mount); + if (r < 0) + return r; + } + help_man_page_reference("systemd-mount", "1"); return 0; } @@ -157,6 +169,32 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_COMMON_VERSION: return version(); + OPTION_LONG("user", NULL, "Run as user unit"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_LONG("system", NULL, /* help= */ NULL): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; + break; + + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + + OPTION_LONG("canonicalize", "BOOL", + "Whether to canonicalize path before operation"): + r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); + if (r < 0) + return r; + break; + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; @@ -177,29 +215,18 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_ask_password = false; break; - OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): + OPTION('q', "quiet", NULL, "Suppress informational messages during runtime"): arg_quiet = true; break; - OPTION_LONG("user", NULL, "Run as user unit"): - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - OPTION_LONG("system", NULL, /* help= */ NULL): - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - OPTION_COMMON_HOST: - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = opts.arg; - break; - - OPTION_COMMON_MACHINE: - r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); - if (r < 0) + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) return r; break; + OPTION_GROUP("Mount options"): {} + OPTION_LONG("discover", NULL, "Discover mount device metadata"): arg_discover = true; break; @@ -290,19 +317,6 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"): arg_tmpfs = true; break; - - OPTION_COMMON_JSON: - r = parse_json_argument(opts.arg, &arg_json_format_flags); - if (r <= 0) - return r; - break; - - OPTION_LONG("canonicalize", "BOOL", - "Controls whether to canonicalize path before operation"): - r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); - if (r < 0) - return r; - break; } char **args = option_parser_get_args(&opts); From d4bc62713e09df09281f26f4bf385801a3ee2897 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 6 May 2026 18:04:51 +0100 Subject: [PATCH 1511/2155] test: fix flaky testcase_15_wait_online_dns in TEST-75-RESOLVED The test used `timeout 30 bash -c "journalctl -b -u $unit -f | grep -m1 ..."` to wait for systemd-networkd-wait-online to log that no DNS server is accessible. The expected message is actually emitted ~1s after the unit starts, but `grep -m1` exiting doesn't tear down `journalctl -f`: journalctl only notices the closed pipe on its next write, which may never happen for an otherwise idle unit. The pipeline therefore hangs until the 30s timeout fires, eventually causing the test to fail. Replace the follow+pipe with a polling `journalctl --grep` loop, which exits cleanly as soon as the message lands in the journal. Logs from the failing run: [ 2650.871441] systemd-networkd-wait-online[2190]: dns0: No DNS configuration yet [ 2651.723180] systemd-networkd-wait-online[2190]: dns0: No DNS server is accessible. [ 2680.909048] systemd-networkd-wait-online[2190]: json-stream: Got POLLHUP from socket. [ 2680.909092] systemd-networkd-wait-online[2190]: DNS configuration monitor disconnected, reconnecting... [ 2680.914368] systemd-networkd-wait-online[2190]: Failed to connect to io.systemd.Resolve.Monitor: Connection refused [ 2681.966674] systemd-networkd-wait-online[2190]: dns0: No DNS server is accessible. [ 2681.969527] systemd-networkd-wait-online[2190]: Failed to connect to io.systemd.Resolve.Monitor: Connection refused [ 2682.077032] systemd[1]: Stopping wait-online-dns-0f9e4f6d-8b34-4cff-b2da-03612ca731e8.service - [systemd-run] /usr/lib/systemd/systemd-networkd-wait-online --timeout=0 --dns --interface=dns0... Co-developed-by: Claude Opus 4.7 --- test/units/TEST-75-RESOLVED.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index bb1cf9576c292..7b5670a4bc726 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1421,7 +1421,7 @@ testcase_15_wait_online_dns() { /usr/lib/systemd/systemd-networkd-wait-online --timeout=0 --dns --interface=dns0 # Wait until it blocks waiting for updated DNS config - timeout 30 bash -c "journalctl -b -u $unit -f | grep -m1 'dns0: No.*DNS server is accessible'" >/dev/null + timeout 30 bash -c "until journalctl -b -u $unit --grep 'dns0: No.*DNS server is accessible' >/dev/null 2>&1; do sleep 0.5; done" # Update the global configuration. Restart rather than reload systemd-resolved so that # systemd-networkd-wait-online has to re-connect to the varlink service. From 16c3c69be3c4c5d0e86c051b66153eff3b2ac98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 14:41:08 +0200 Subject: [PATCH 1512/2155] resolvectl: split out parse_protocol --- src/resolve/resolvectl.c | 91 +++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 1452131b59e7d..7b7cae68d437f 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3139,16 +3139,38 @@ static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\n" - "llmnr\n" - "llmnr-ipv4\n" - "llmnr-ipv6\n" - "mdns\n" - "mdns-ipv4\n" - "mdns-ipv6"); +static int parse_protocol(const char *arg) { + if (streq(arg, "help")) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\n" + "llmnr\n" + "llmnr-ipv4\n" + "llmnr-ipv6\n" + "mdns\n" + "mdns-ipv4\n" + "mdns-ipv6"); + return 0; + } + + if (streq(arg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(arg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(arg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(arg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else if (streq(arg, "mdns")) + arg_flags |= SD_RESOLVED_MDNS; + else if (streq(arg, "mdns-ipv4")) + arg_flags |= SD_RESOLVED_MDNS_IPV4; + else if (streq(arg, "mdns-ipv6")) + arg_flags |= SD_RESOLVED_MDNS_IPV6; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown protocol specifier: %s", arg); + return 1; } static void help_dns_types(void) { @@ -3434,27 +3456,9 @@ static int compat_parse_argv(int argc, char *argv[]) { break; case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", optarg); - + r = parse_protocol(optarg); + if (r <= 0) + return r; break; case ARG_SERVICE: @@ -3736,28 +3740,9 @@ static int native_parse_argv(int argc, char *argv[]) { break; case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", - optarg); - + r = parse_protocol(optarg); + if (r <= 0) + return r; break; case ARG_RAW: From df24074bb51a72ee5beb8c16c5b6660058892e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:30:37 +0200 Subject: [PATCH 1513/2155] resolvectl: move things around in --help Move the "display options" to the end of both --help strings and then reorder the implementation to match. --- src/resolve/resolvectl.c | 187 ++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 93 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 7b7cae68d437f..fdce91344774b 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3206,7 +3206,6 @@ static int compat_help(void) { "%2$sResolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%3$s\n\n" " -h --help Show this help\n" " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" " -i --interface=INTERFACE Look on interface\n" @@ -3221,8 +3220,6 @@ static int compat_help(void) { " --cname=BOOL Follow CNAME redirects (default: yes)\n" " --search=BOOL Use search domains for single-label names\n" " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" " --statistics Show resolver statistics\n" " --reset-statistics Reset resolver statistics\n" " --status Show link and server status\n" @@ -3237,6 +3234,9 @@ static int compat_help(void) { " --set-dnssec=MODE Set per-interface DNSSEC mode\n" " --set-nta=DOMAIN Set per-interface DNSSEC NTA\n" " --revert Revert per-interface configuration\n" + " --raw[=payload|packet] Dump the answer as binary data\n" + " --no-pager Do not pipe output into a pager\n" + " --legend=BOOL Print headers and additional info (default: yes)\n" "\nSee the %4$s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -3286,8 +3286,6 @@ static int native_help(void) { "\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-ask-password Do not prompt for password\n" " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" " -i --interface=INTERFACE Look on interface\n" @@ -3310,6 +3308,8 @@ static int native_help(void) { " --search=BOOL Use search domains for single-label names (default:\n" " yes)\n" " --raw[=payload|packet] Dump the answer as binary data\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" " --legend=BOOL Print headers and additional info (default: yes)\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short\n" @@ -3418,6 +3418,12 @@ static int compat_parse_argv(int argc, char *argv[]) { return r; break; + case 'p': + r = parse_protocol(optarg); + if (r <= 0) + return r; + break; + case 't': if (streq(optarg, "help")) { help_dns_types(); @@ -3449,20 +3455,22 @@ static int compat_parse_argv(int argc, char *argv[]) { break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); - if (r < 0) - return r; + case ARG_SERVICE: + arg_mode = MODE_RESOLVE_SERVICE; break; - case 'p': - r = parse_protocol(optarg); - if (r <= 0) + case ARG_SERVICE_ADDRESS: + r = parse_boolean_argument("--service-address=", optarg, NULL); + if (r < 0) return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; + case ARG_SERVICE_TXT: + r = parse_boolean_argument("--service-txt=", optarg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; case ARG_OPENPGP: @@ -3478,23 +3486,6 @@ static int compat_parse_argv(int argc, char *argv[]) { "Unknown service family \"%s\".", optarg); break; - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; - break; - case ARG_CNAME: r = parse_boolean_argument("--cname=", optarg, NULL); if (r < 0) @@ -3502,19 +3493,6 @@ static int compat_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; case ARG_SEARCH: r = parse_boolean_argument("--search=", optarg, NULL); @@ -3531,6 +3509,10 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESET_STATISTICS; break; + case ARG_STATUS: + arg_mode = MODE_STATUS; + break; + case ARG_FLUSH_CACHES: arg_mode = MODE_FLUSH_CACHES; break; @@ -3539,14 +3521,6 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESET_SERVER_FEATURES; break; - case ARG_STATUS: - arg_mode = MODE_STATUS; - break; - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - case ARG_SET_DNS: r = strv_extend(&arg_set_dns, optarg); if (r < 0) @@ -3595,6 +3569,33 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_REVERT_LINK; break; + case ARG_RAW: + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + optarg); + + arg_legend = false; + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_LEGEND: + r = parse_boolean_argument("--legend=", optarg, &arg_legend); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -3703,6 +3704,12 @@ static int native_parse_argv(int argc, char *argv[]) { return r; break; + case 'p': + r = parse_protocol(optarg); + if (r <= 0) + return r; + break; + case 't': if (streq(optarg, "help")) { help_dns_types(); @@ -3733,33 +3740,18 @@ static int native_parse_argv(int argc, char *argv[]) { break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + case ARG_SERVICE_ADDRESS: + r = parse_boolean_argument("--service-address=", optarg, NULL); if (r < 0) return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case 'p': - r = parse_protocol(optarg); - if (r <= 0) + case ARG_SERVICE_TXT: + r = parse_boolean_argument("--service-txt=", optarg, NULL); + if (r < 0) return r; - break; - - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; case ARG_CNAME: @@ -3797,6 +3789,13 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_STALE, r == 0); break; + case ARG_RELAX_SINGLE_LABEL: + r = parse_boolean_argument("--relax-single-label=", optarg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + break; + case ARG_ZONE: r = parse_boolean_argument("--zone=", optarg, NULL); if (r < 0) @@ -3818,20 +3817,6 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - case ARG_SEARCH: r = parse_boolean_argument("--search=", optarg, NULL); if (r < 0) @@ -3839,11 +3824,21 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_RELAX_SINGLE_LABEL: - r = parse_boolean_argument("--relax-single-label=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + case ARG_RAW: + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + optarg); + + arg_legend = false; break; case ARG_NO_PAGER: @@ -3854,6 +3849,12 @@ static int native_parse_argv(int argc, char *argv[]) { arg_ask_password = false; break; + case ARG_LEGEND: + r = parse_boolean_argument("--legend=", optarg, &arg_legend); + if (r < 0) + return r; + break; + case ARG_JSON: r = parse_json_argument(optarg, &arg_json_format_flags); if (r <= 0) From d865863929bd010392330a4358b005c9eb902880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:31:28 +0200 Subject: [PATCH 1514/2155] resolvectl: move verb implementations to match order in --help --- src/resolve/resolvectl.c | 2533 +++++++++++++++++++------------------- 1 file changed, 1266 insertions(+), 1267 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index fdce91344774b..4088c39b1da60 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -122,6 +122,18 @@ static const char* const status_mode_json_field_table[_STATUS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(status_mode_json_field, StatusMode); +static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { + int r; + + if (value) { + r = parse_boolean(value); + if (r >= 0) + return strv_extendf(strv, "%s%s", plus_minus(r), name); + } + + return strv_extendf(strv, "%s=%s", name, value ?: "???"); +} + static int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; @@ -1163,430 +1175,114 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } -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; +static int varlink_dump_dns_configuration(sd_json_variant **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *v; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(ret); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); if (r < 0) return r; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - - struct statistics { - sd_json_variant *transactions; - sd_json_variant *cache; - sd_json_variant *dnssec; - } statistics; - - static const sd_json_dispatch_field statistics_dispatch_table[] = { - { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, - { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); - if (r < 0) - return r; + v = sd_json_variant_by_key(reply, "configuration"); - struct transactions { - uint64_t n_current_transactions; - uint64_t n_transactions_total; - uint64_t n_timeouts_total; - uint64_t n_timeouts_served_stale_total; - uint64_t n_failure_responses_total; - uint64_t n_failure_responses_served_stale_total; - } transactions; + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - static const sd_json_dispatch_field transactions_dispatch_table[] = { - { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, - { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, - { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, - { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, - { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, - { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, - {}, - }; + TAKE_PTR(reply); + *ret = sd_json_variant_ref(v); + return 0; +} - r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); - if (r < 0) - return r; +static int status_json_filter_links(sd_json_variant **configuration, char **links) { + _cleanup_set_free_ Set *links_by_index = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + int r; - struct cache { - uint64_t cache_size; - uint64_t n_cache_hit; - uint64_t n_cache_miss; - } cache; + assert(configuration); - static const sd_json_dispatch_field cache_dispatch_table[] = { - { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, - { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, - { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, - {}, - }; + if (links) + STRV_FOREACH(ifname, links) { + int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); + if (ifindex < 0) + return ifindex; - r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); - if (r < 0) - return r; + r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); + if (r < 0) + return r; + } - struct dnsssec { - uint64_t n_dnssec_secure; - uint64_t n_dnssec_insecure; - uint64_t n_dnssec_bogus; - uint64_t n_dnssec_indeterminate; - } dnsssec; + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); - static const sd_json_dispatch_field dnssec_dispatch_table[] = { - { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, - { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, - { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, - { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, - {}, - }; + if (links_by_index) { + if (ifindex <= 0) + /* Possibly invalid, but most likely unset because this is global + * or delegate configuration. */ + continue; - r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); - if (r < 0) - return r; + if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) + continue; - table = table_new_vertical(); - if (!table) - return log_oom(); + } else if (ifindex == LOOPBACK_IFINDEX) + /* By default, exclude the loopback interface. */ + continue; - r = table_add_many(table, - TABLE_STRING, "Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Transactions", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_current_transactions, - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_FIELD, "Total Transactions", - TABLE_UINT64, transactions.n_transactions_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Cache", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Cache Size", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, cache.cache_size, - TABLE_FIELD, "Cache Hits", - TABLE_UINT64, cache.n_cache_hit, - TABLE_FIELD, "Cache Misses", - TABLE_UINT64, cache.n_cache_miss, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Failure Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Total Timeouts", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_timeouts_total, - TABLE_FIELD, "Total Timeouts (Stale Data Served)", - TABLE_UINT64, transactions.n_timeouts_served_stale_total, - TABLE_FIELD, "Total Failure Responses", - TABLE_UINT64, transactions.n_failure_responses_total, - TABLE_FIELD, "Total Failure Responses (Stale Data Served)", - TABLE_UINT64, transactions.n_failure_responses_served_stale_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "DNSSEC Verdicts", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Secure", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, dnsssec.n_dnssec_secure, - TABLE_FIELD, "Insecure", - TABLE_UINT64, dnsssec.n_dnssec_insecure, - TABLE_FIELD, "Bogus", - TABLE_UINT64, dnsssec.n_dnssec_bogus, - TABLE_FIELD, "Indeterminate", - TABLE_UINT64, dnsssec.n_dnssec_indeterminate - ); - if (r < 0) - return table_log_add_error(r); + r = sd_json_variant_append_array(&v, w); + if (r < 0) + return r; + } - return table_print_or_warn(table); + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); + return 0; } -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; +static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + const char *field; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + assert(configuration); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.ResetStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return r; + field = status_mode_json_field_to_string(mode); + if (!field) + /* Nothing to filter for this mode. */ + return 0; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + /* Always include identifier fields like ifname or delegate, and include the requested + * field even if it is empty in the configuration. */ + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), + SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + if (r < 0) + return r; + } + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); return 0; } -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; +static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { + bool global; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); + assert(s); + assert(ret); - return 0; -} - -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - - return 0; -} - -static int status_print_strv(DNSConfiguration *c, char **p) { - const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ - int pos1, pos2; - - assert(c); - - if (c->ifname) - printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); - else if (c->delegate) - printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); - else - printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); - - size_t cols = columns(), position = pos2 - pos1 + 2; - - STRV_FOREACH(i, p) { - size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). - * If that happens, we'll just print one item per line. */ - - if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { - printf(" %s", *i); - position = size_add(size_add(position, 1), our_len); - } else { - printf("\n%*s%s", (int) indent, "", *i); - position = size_add(our_len, indent); - } - } - - printf("\n"); - - return 0; -} - -static void status_print_string(DNSConfiguration *c, const char *p) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s: %s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal(), - p); - else if (c->delegate) - printf("%sDelegate %s%s: %s\n", - ansi_highlight(), - c->delegate, - ansi_normal(), - p); - else - printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); -} - -static void status_print_header(DNSConfiguration *c) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal()); - else if (c->delegate) - printf("%sDelegate %s%s\n", - ansi_highlight(), - c->delegate, - ansi_normal()); - else - printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); -} - -static int dump_list(Table *table, const char *field, char * const *l) { - int r; - - if (strv_isempty(l)) - return 0; - - r = table_add_many(table, - TABLE_FIELD, field, - TABLE_STRV_WRAPPED, l); - if (r < 0) - return table_log_add_error(r); - - return 0; -} - -static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { - int r; - - if (value) { - r = parse_boolean(value); - if (r >= 0) - return strv_extendf(strv, "%s%s", plus_minus(r), name); - } - - return strv_extendf(strv, "%s=%s", name, value ?: "???"); -} - -static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - const char *field; - int r; - - assert(configuration); - - field = status_mode_json_field_to_string(mode); - if (!field) - /* Nothing to filter for this mode. */ - return 0; - - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - /* Always include identifier fields like ifname or delegate, and include the requested - * field even if it is empty in the configuration. */ - r = sd_json_variant_append_arraybo( - &v, - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), - SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); - if (r < 0) - return r; - } - - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} - -static int status_json_filter_links(sd_json_variant **configuration, char **links) { - _cleanup_set_free_ Set *links_by_index = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - int r; - - assert(configuration); - - if (links) - STRV_FOREACH(ifname, links) { - int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); - if (ifindex < 0) - return ifindex; - - r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); - if (r < 0) - return r; - } - - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); - - if (links_by_index) { - if (ifindex <= 0) - /* Possibly invalid, but most likely unset because this is global - * or delegate configuration. */ - continue; - - if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) - continue; - - } else if (ifindex == LOOPBACK_IFINDEX) - /* By default, exclude the loopback interface. */ - continue; - - r = sd_json_variant_append_array(&v, w); - if (r < 0) - return r; - } - - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} - -static int varlink_dump_dns_configuration(sd_json_variant **ret) { - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; - sd_json_variant *v; - int r; - - assert(ret); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); - if (r < 0) - return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - - r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); - if (r < 0) - return r; - - v = sd_json_variant_by_key(reply, "configuration"); - - if (!sd_json_variant_is_array(v)) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - - TAKE_PTR(reply); - *ret = sd_json_variant_ref(v); - return 0; -} - -static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { - bool global; - int r; - - assert(s); - assert(ret); - - global = !(configuration->ifindex > 0 || configuration->delegate); + global = !(configuration->ifindex > 0 || configuration->delegate); if (global && s->ifindex > 0 && s->ifindex != LOOPBACK_IFINDEX) { /* This one has an (non-loopback) ifindex set, and we were told to suppress those. Hence do so. */ @@ -1735,27 +1431,113 @@ static int format_scopes_string(DNSConfiguration *configuration, char **ret) { return 0; } -static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { - _cleanup_(table_unrefp) Table *table = NULL; - int r; - - assert(configuration); - - pager_open(arg_pager_flags); +static void status_print_header(DNSConfiguration *c) { + assert(c); - bool global = !(configuration->ifindex > 0 || configuration->delegate); - if (mode == STATUS_DNS) { - _cleanup_strv_free_ char **l = NULL; - r = format_dns_servers(configuration, configuration->dns_servers, &l); - if (r < 0) - return r; + if (c->ifname) + printf("%sLink %i (%s)%s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal()); + else if (c->delegate) + printf("%sDelegate %s%s\n", + ansi_highlight(), + c->delegate, + ansi_normal()); + else + printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); +} - return status_print_strv(configuration, l); +static void status_print_string(DNSConfiguration *c, const char *p) { + assert(c); - } else if (mode == STATUS_DOMAIN) { - _cleanup_strv_free_ char **l = NULL; - r = format_search_domains(configuration, configuration->search_domains, &l); - if (r < 0) + if (c->ifname) + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal(), + p); + else if (c->delegate) + printf("%sDelegate %s%s: %s\n", + ansi_highlight(), + c->delegate, + ansi_normal(), + p); + else + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); +} + +static int status_print_strv(DNSConfiguration *c, char **p) { + const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ + int pos1, pos2; + + assert(c); + + if (c->ifname) + printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); + else if (c->delegate) + printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); + else + printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); + + size_t cols = columns(), position = pos2 - pos1 + 2; + + STRV_FOREACH(i, p) { + size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). + * If that happens, we'll just print one item per line. */ + + if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { + printf(" %s", *i); + position = size_add(size_add(position, 1), our_len); + } else { + printf("\n%*s%s", (int) indent, "", *i); + position = size_add(our_len, indent); + } + } + + printf("\n"); + + return 0; +} + +static int dump_list(Table *table, const char *field, char * const *l) { + int r; + + if (strv_isempty(l)) + return 0; + + r = table_add_many(table, + TABLE_FIELD, field, + TABLE_STRV_WRAPPED, l); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + assert(configuration); + + pager_open(arg_pager_flags); + + bool global = !(configuration->ifindex > 0 || configuration->delegate); + if (mode == STATUS_DNS) { + _cleanup_strv_free_ char **l = NULL; + r = format_dns_servers(configuration, configuration->dns_servers, &l); + if (r < 0) + return r; + + return status_print_strv(configuration, l); + + } else if (mode == STATUS_DOMAIN) { + _cleanup_strv_free_ char **l = NULL; + r = format_search_domains(configuration, configuration->search_domains, &l); + if (r < 0) return r; return status_print_strv(configuration, l); @@ -1985,165 +1767,193 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return status_full(STATUS_ALL, strv_skip(argv, 1)); } -static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; +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; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); - - /* If only argument is the empty string, then call SetLinkDNS() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(dns, STRV_MAKE(""))) - STRV_FOREACH(p, dns) { - _cleanup_free_ char *name = NULL; - struct in_addr_data data; - uint16_t port; - int ifindex; - - r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS server address: %s", *p); - - if (ifindex != 0 && ifindex != arg_ifindex) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); - - r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", data.family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); - if (r < 0) - return bus_log_create_error(r); + return r; - if (extended) { - r = sd_bus_message_append(req, "q", port); - if (r < 0) - return bus_log_create_error(r); + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - r = sd_bus_message_append(req, "s", name); - if (r < 0) - return bus_log_create_error(r); - } + struct statistics { + sd_json_variant *transactions; + sd_json_variant *cache; + sd_json_variant *dnssec; + } statistics; - r = sd_bus_message_close_container(req); - if (r < 0) - return bus_log_create_error(r); - } + static const sd_json_dispatch_field statistics_dispatch_table[] = { + { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, + { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, + {}, + }; - r = sd_bus_message_close_container(req); + r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_call(bus, req, 0, error, NULL); - if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - sd_bus_error_free(error); - return call_dns(bus, dns, locator, error, false); - } - return r; -} + struct transactions { + uint64_t n_current_transactions; + uint64_t n_transactions_total; + uint64_t n_timeouts_total; + uint64_t n_timeouts_served_stale_total; + uint64_t n_failure_responses_total; + uint64_t n_failure_responses_served_stale_total; + } transactions; -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; + static const sd_json_dispatch_field transactions_dispatch_table[] = { + { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, + { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, + { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, + { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, + { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, + { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, + {}, + }; - r = acquire_bus(&bus); + r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DNS); + struct cache { + uint64_t cache_size; + uint64_t n_cache_hit; + uint64_t n_cache_miss; + } cache; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS); + static const sd_json_dispatch_field cache_dispatch_table[] = { + { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, + { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, + { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, + {}, + }; - char **args = strv_skip(argv, 2); - r = call_dns(bus, args, bus_resolve_mgr, &error, true); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); + if (r < 0) + return r; - r = call_dns(bus, args, bus_network_mgr, &error, true); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + struct dnsssec { + uint64_t n_dnssec_secure; + uint64_t n_dnssec_insecure; + uint64_t n_dnssec_bogus; + uint64_t n_dnssec_indeterminate; + } dnsssec; - return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); - } + static const sd_json_dispatch_field dnssec_dispatch_table[] = { + { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, + { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, + { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, + { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, + {}, + }; - return 0; -} + r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); + if (r < 0) + return r; -static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; - int r; + table = table_new_vertical(); + if (!table) + return log_oom(); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(req, 'a', "(sb)"); + r = table_add_many(table, + TABLE_STRING, "Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Transactions", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_current_transactions, + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Total Transactions", + TABLE_UINT64, transactions.n_transactions_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Cache", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Cache Size", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, cache.cache_size, + TABLE_FIELD, "Cache Hits", + TABLE_UINT64, cache.n_cache_hit, + TABLE_FIELD, "Cache Misses", + TABLE_UINT64, cache.n_cache_miss, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Failure Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Total Timeouts", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_timeouts_total, + TABLE_FIELD, "Total Timeouts (Stale Data Served)", + TABLE_UINT64, transactions.n_timeouts_served_stale_total, + TABLE_FIELD, "Total Failure Responses", + TABLE_UINT64, transactions.n_failure_responses_total, + TABLE_FIELD, "Total Failure Responses (Stale Data Served)", + TABLE_UINT64, transactions.n_failure_responses_served_stale_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "DNSSEC Verdicts", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Secure", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, dnsssec.n_dnssec_secure, + TABLE_FIELD, "Insecure", + TABLE_UINT64, dnsssec.n_dnssec_insecure, + TABLE_FIELD, "Bogus", + TABLE_UINT64, dnsssec.n_dnssec_bogus, + TABLE_FIELD, "Indeterminate", + TABLE_UINT64, dnsssec.n_dnssec_indeterminate + ); if (r < 0) - return bus_log_create_error(r); + return table_log_add_error(r); - /* If only argument is the empty string, then call SetLinkDomains() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(domain, STRV_MAKE(""))) - STRV_FOREACH(p, domain) { - const char *n; + return table_print_or_warn(table); +} - n = **p == '~' ? *p + 1 : *p; +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; - r = dns_name_is_valid(n); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", n); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Domain not valid: %s", - n); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_bus_message_append(req, "(sb)", n, **p == '~'); - if (r < 0) - return bus_log_create_error(r); - } + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = sd_bus_message_close_container(req); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.ResetStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); + return r; - return sd_bus_call(bus, req, 0, error, NULL); + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + return 0; } -static int verb_domain(int argc, char *argv[], uintptr_t _data, 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; @@ -2152,295 +1962,268 @@ static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DOMAIN); - - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DOMAIN); - - char **args = strv_skip(argv, 2); - r = call_domain(bus, args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); - - r = call_domain(bus, args, bus_network_mgr, &error); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - - return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); - } + r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); return 0; } -static int verb_default_route(int argc, char *argv[], uintptr_t _data, 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, b; + int r; r = acquire_bus(&bus); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DEFAULT_ROUTE); + r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); + return 0; +} - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); +static int print_question(char prefix, const char *color, sd_json_variant *question) { + sd_json_variant *q = NULL; + int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(color); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + JSON_VARIANT_ARRAY_FOREACH(q, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + char buf[DNS_RESOURCE_KEY_STRING_MAX]; - r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = dns_resource_key_from_json(q, &key); + if (r < 0) { + log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); + continue; + } - return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + printf("%s%s %c%s: %s\n", + color, + glyph(GLYPH_ARROW_RIGHT), + prefix, + ansi_normal(), + dns_resource_key_to_string(key, buf, sizeof(buf))); } return 0; } -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; - ResolveSupport global_llmnr_support, llmnr_support; +static int print_answer(sd_json_variant *answer) { + sd_json_variant *a; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_LLMNR); - - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_LLMNR); + JSON_VARIANT_ARRAY_FOREACH(a, answer) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *d = NULL; + sd_json_variant *jraw; + const char *s; + size_t l; - llmnr_support = resolve_support_from_string(argv[2]); - if (llmnr_support < 0) - return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); + jraw = sd_json_variant_by_key(a, "raw"); + if (!jraw) { + log_warning("Received monitor answer lacking valid raw data, ignoring."); + continue; + } - r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); + r = sd_json_variant_unbase64(jraw, &d, &l); + if (r < 0) { + log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); + continue; + } - global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); - if (global_llmnr_support < 0) - return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); + r = dns_resource_record_new_from_raw(&rr, d, l); + if (r < 0) { + log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); + continue; + } - if (global_llmnr_support < llmnr_support) - log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_llmnr_support_str); + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + printf("%s%s A%s: %s\n", + ansi_highlight_yellow(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + s); + } - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + return 0; +} - r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; +typedef struct MonitorQueryParams { + sd_json_variant *question; + sd_json_variant *answer; + sd_json_variant *collected_questions; + int rcode; + int error; + int ede_code; + const char *state; + const char *result; + const char *ede_msg; +} MonitorQueryParams; - return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); - } +static void monitor_query_params_done(MonitorQueryParams *p) { + assert(p); - return 0; + sd_json_variant_unref(p->question); + sd_json_variant_unref(p->answer); + sd_json_variant_unref(p->collected_questions); } -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; - ResolveSupport global_mdns_support, mdns_support; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_MDNS); +static void monitor_query_dump(sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, + { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, + { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, + { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, + { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, + {} + }; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_MDNS); + _cleanup_(monitor_query_params_done) MonitorQueryParams p = { + .rcode = -1, + .ede_code = -1, + }; - mdns_support = resolve_support_from_string(argv[2]); - if (mdns_support < 0) - return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); + assert(v); - r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); + if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) + return; - global_mdns_support = resolve_support_from_string(global_mdns_support_str); - if (global_mdns_support < 0) - return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); + /* First show the current question */ + print_question('Q', ansi_highlight_cyan(), p.question); - if (global_mdns_support < mdns_support) - log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_mdns_support_str); + /* And then show the questions that led to this one in case this was a CNAME chain */ + print_question('C', ansi_highlight_grey(), p.collected_questions); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + printf("%s%s S%s: %s", + streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : + streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : + strna(p.state)); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + if (!isempty(p.result)) + printf(": %s", p.result); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkMulticastDNS", - &error, - NULL, - "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + if (p.ede_code >= 0) + printf(" (%s%s%s)", + FORMAT_DNS_EDE_RCODE(p.ede_code), + !isempty(p.ede_msg) ? ": " : "", + strempty(p.ede_msg)); - return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); - } + puts(""); - return 0; + print_answer(p.answer); } -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DNS_OVER_TLS); +static int monitor_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); + assert(link); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + if (error_id) { + bool disconnect; - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); + if (disconnect) + log_info("Disconnected."); + else + log_error("Varlink error: %s", error_id); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkDNSOverTLS", - &error, - NULL, - "is", arg_ifindex, argv[2]); + (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); + return 0; } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); + if (sd_json_variant_by_key(parameters, "ready")) { + /* The first message coming in will just indicate that we are now subscribed. We let our + * caller know if they asked for it. Once the caller sees this they should know that we are + * not going to miss any queries anymore. */ + (void) sd_notify(/* unset_environment=false */ false, "READY=1"); + return 0; } + if (!sd_json_format_enabled(arg_json_format_flags)) { + monitor_query_dump(parameters); + printf("\n"); + } else + sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + + fflush(stdout); + return 0; } -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; +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; - r = acquire_bus(&bus); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_event_default(&event); if (r < 0) - return r; + return log_error_errno(r, "Failed to get event loop: %m"); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); - if (arg_ifindex <= 0) - return status_all(STATUS_DNSSEC); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNSSEC); + r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ + if (r < 0) + return log_error_errno(r, "Failed to set varlink timeout: %m"); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_varlink_bind_reply(vl, monitor_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); - r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = sd_varlink_observebo( + vl, + "io.systemd.Resolve.Monitor.SubscribeQueryResults", + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); - return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); - } + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); - return 0; + r = sd_event_get_exit_code(event, &c); + if (r < 0) + return log_error_errno(r, "Failed to get exit code: %m"); + + return c; } -static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { +static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); + r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); if (r < 0) return bus_log_create_error(r); @@ -2448,695 +2231,911 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(req, nta); + r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); if (r < 0) return bus_log_create_error(r); - return sd_bus_call(bus, req, 0, error, NULL); -} - -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; - bool clear; - int r; + /* If only argument is the empty string, then call SetLinkDNS() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(dns, STRV_MAKE(""))) + STRV_FOREACH(p, dns) { + _cleanup_free_ char *name = NULL; + struct in_addr_data data; + uint16_t port; + int ifindex; - r = acquire_bus(&bus); - if (r < 0) - return r; + r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS server address: %s", *p); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + if (ifindex != 0 && ifindex != arg_ifindex) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); - if (arg_ifindex <= 0) - return status_all(STATUS_NTA); + r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); + if (r < 0) + return bus_log_create_error(r); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_NTA); + r = sd_bus_message_append(req, "i", data.family); + if (r < 0) + return bus_log_create_error(r); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); + if (r < 0) + return bus_log_create_error(r); - /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() - * with an empty list, which will clear the list of domains for an interface. */ - args = strv_skip(argv, 2); - clear = strv_equal(args, STRV_MAKE("")); + if (extended) { + r = sd_bus_message_append(req, "q", port); + if (r < 0) + return bus_log_create_error(r); - if (!clear) - STRV_FOREACH(p, args) { - r = dns_name_is_valid(*p); + r = sd_bus_message_append(req, "s", name); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Domain not valid: %s", - *p); + return bus_log_create_error(r); } - r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); - r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); + r = sd_bus_call(bus, req, 0, error, NULL); + if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(error); + return call_dns(bus, dns, locator, error, false); } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + return r; +} - return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); - } +static int dump_cache_item(sd_json_variant *item) { - return 0; -} + struct item_info { + sd_json_variant *key; + sd_json_variant *rrs; + const char *type; + uint64_t until; + } item_info = {}; -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; + static const sd_json_dispatch_field dispatch_table[] = { + { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, + { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, + {}, + }; - r = acquire_bus(&bus); + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + int r, c = 0; + + r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + r = dns_resource_key_from_json(item_info.key, &k); + if (r < 0) + return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); - if (arg_ifindex <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); + if (item_info.type) + printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); + else { + sd_json_variant *i; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *data = NULL; + sd_json_variant *raw; + size_t size; - r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + raw = sd_json_variant_by_key(i, "raw"); + if (!raw) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); - r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = sd_json_variant_unbase64(raw, &data, &size); + if (r < 0) + return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); - return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + r = dns_resource_record_new_from_raw(&rr, data, size); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS data: %m"); + + printf("%s\n", dns_resource_record_to_string(rr)); + c++; + } } - return 0; + return c; } -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; +static int dump_cache_scope(sd_json_variant *scope) { + struct scope_info { + const char *protocol; + int family; + int ifindex; + const char *ifname; + sd_json_variant *cache; + const char *dnssec_mode; + const char *dns_over_tls_mode; + } scope_info = { + .family = AF_UNSPEC, + }; + sd_json_variant *i; + int r, c = 0; - r = acquire_bus(&bus); + static const sd_json_dispatch_field dispatch_table[] = { + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, + { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, + {}, + }; + + r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); if (r < 0) return r; - assert(IN_SET(argc, 1, 2)); + printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); - return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); -} + if (scope_info.family != AF_UNSPEC) + printf(" family=%s", af_to_name(scope_info.family)); -static int print_question(char prefix, const char *color, sd_json_variant *question) { - sd_json_variant *q = NULL; - int r; + if (scope_info.ifindex > 0) + printf(" ifindex=%i", scope_info.ifindex); + if (scope_info.ifname) + printf(" ifname=%s", scope_info.ifname); - assert(color); + if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { + if (scope_info.dnssec_mode) + printf(" DNSSEC=%s", scope_info.dnssec_mode); + if (scope_info.dns_over_tls_mode) + printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); + } - JSON_VARIANT_ARRAY_FOREACH(q, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - char buf[DNS_RESOURCE_KEY_STRING_MAX]; + printf("%s\n", ansi_normal()); - r = dns_resource_key_from_json(q, &key); - if (r < 0) { - log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); - continue; - } + JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { + r = dump_cache_item(i); + if (r < 0) + return r; - printf("%s%s %c%s: %s\n", - color, - glyph(GLYPH_ARROW_RIGHT), - prefix, - ansi_normal(), - dns_resource_key_to_string(key, buf, sizeof(buf))); + c += r; } + if (c == 0) + printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); + else + printf("\n"); + return 0; } -static int print_answer(sd_json_variant *answer) { - sd_json_variant *a; +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; - JSON_VARIANT_ARRAY_FOREACH(a, answer) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *d = NULL; - sd_json_variant *jraw; - const char *s; - size_t l; - - jraw = sd_json_variant_by_key(a, "raw"); - if (!jraw) { - log_warning("Received monitor answer lacking valid raw data, ignoring."); - continue; - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_json_variant_unbase64(jraw, &d, &l); - if (r < 0) { - log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); - continue; - } + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) { - log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); - continue; - } + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpCache", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); - printf("%s%s A%s: %s\n", - ansi_highlight_yellow(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - s); + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_cache_scope(i); + if (r < 0) + return r; + } + + return 0; } - return 0; + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } -typedef struct MonitorQueryParams { - sd_json_variant *question; - sd_json_variant *answer; - sd_json_variant *collected_questions; - int rcode; - int error; - int ede_code; - const char *state; - const char *result; - const char *ede_msg; -} MonitorQueryParams; +static int dump_server_state(sd_json_variant *server) { + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; -static void monitor_query_params_done(MonitorQueryParams *p) { - assert(p); + struct server_state { + const char *server_name; + const char *type; + const char *ifname; + int ifindex; + const char *verified_feature_level; + const char *possible_feature_level; + const char *dnssec_mode; + bool dnssec_supported; + size_t received_udp_fragment_max; + uint64_t n_failed_udp; + uint64_t n_failed_tcp; + bool packet_truncated; + bool packet_bad_opt; + bool packet_rrsig_missing; + bool packet_invalid; + bool packet_do_off; + } server_state = { + .ifindex = -1, + }; - sd_json_variant_unref(p->question); - sd_json_variant_unref(p->answer); - sd_json_variant_unref(p->collected_questions); -} + int r; -static void monitor_query_dump(sd_json_variant *v) { static const sd_json_dispatch_field dispatch_table[] = { - { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, - { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, - { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, - { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, - { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, - { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, - { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, - { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, - { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, - {} + { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, + { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, + { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, + { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, + { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, + { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, + { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, + { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, + { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, + { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, + { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, + { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, + { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, + { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, + { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, + { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, + {}, }; - _cleanup_(monitor_query_params_done) MonitorQueryParams p = { - .rcode = -1, - .ede_code = -1, - }; + r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + if (r < 0) + return r; - assert(v); + table = table_new_vertical(); + if (!table) + return log_oom(); - if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) - return; + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 0); - /* First show the current question */ - print_question('Q', ansi_highlight_cyan(), p.question); + r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); + if (r < 0) + return table_log_add_error(r); - /* And then show the questions that led to this one in case this was a CNAME chain */ - print_question('C', ansi_highlight_grey(), p.collected_questions); + r = table_add_many(table, + TABLE_EMPTY, + TABLE_FIELD, "Type", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_STRING, server_state.type); + if (r < 0) + return table_log_add_error(r); - printf("%s%s S%s: %s", - streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : - streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : - strna(p.state)); + if (server_state.ifname) { + r = table_add_many(table, + TABLE_FIELD, "Interface", + TABLE_STRING, server_state.ifname); + if (r < 0) + return table_log_add_error(r); + } - if (!isempty(p.result)) - printf(": %s", p.result); + if (server_state.ifindex >= 0) { + r = table_add_many(table, + TABLE_FIELD, "Interface Index", + TABLE_INT, server_state.ifindex); + if (r < 0) + return table_log_add_error(r); + } - if (p.ede_code >= 0) - printf(" (%s%s%s)", - FORMAT_DNS_EDE_RCODE(p.ede_code), - !isempty(p.ede_msg) ? ": " : "", - strempty(p.ede_msg)); + if (server_state.verified_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Verified feature level", + TABLE_STRING, server_state.verified_feature_level); + if (r < 0) + return table_log_add_error(r); + } - puts(""); + if (server_state.possible_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Possible feature level", + TABLE_STRING, server_state.possible_feature_level); + if (r < 0) + return table_log_add_error(r); + } - print_answer(p.answer); + r = table_add_many(table, + TABLE_FIELD, "DNSSEC Mode", + TABLE_STRING, server_state.dnssec_mode, + TABLE_FIELD, "DNSSEC Supported", + TABLE_STRING, yes_no(server_state.dnssec_supported), + TABLE_FIELD, "Maximum UDP fragment size received", + TABLE_UINT64, server_state.received_udp_fragment_max, + TABLE_FIELD, "Failed UDP attempts", + TABLE_UINT64, server_state.n_failed_udp, + TABLE_FIELD, "Failed TCP attempts", + TABLE_UINT64, server_state.n_failed_tcp, + TABLE_FIELD, "Seen truncated packet", + TABLE_STRING, yes_no(server_state.packet_truncated), + TABLE_FIELD, "Seen OPT RR getting lost", + TABLE_STRING, yes_no(server_state.packet_bad_opt), + TABLE_FIELD, "Seen RRSIG RR missing", + TABLE_STRING, yes_no(server_state.packet_rrsig_missing), + TABLE_FIELD, "Seen invalid packet", + TABLE_STRING, yes_no(server_state.packet_invalid), + TABLE_FIELD, "Server dropped DO flag", + TABLE_STRING, yes_no(server_state.packet_do_off), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, TABLE_EMPTY); + + if (r < 0) + return table_log_add_error(r); + + return table_print_or_warn(table); } -static int monitor_reply( - sd_varlink *link, - sd_json_variant *parameters, - const char *error_id, - sd_varlink_reply_flags_t flags, - 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; - assert(link); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - if (error_id) { - bool disconnect; + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); - if (disconnect) - log_info("Disconnected."); - else - log_error("Varlink error: %s", error_id); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpServerState", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_server_state(i); + if (r < 0) + return r; + } - (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); return 0; } - if (sd_json_variant_by_key(parameters, "ready")) { - /* The first message coming in will just indicate that we are now subscribed. We let our - * caller know if they asked for it. Once the caller sees this they should know that we are - * not going to miss any queries anymore. */ - (void) sd_notify(/* unset_environment=false */ false, "READY=1"); - return 0; + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); +} + +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; } - if (!sd_json_format_enabled(arg_json_format_flags)) { - monitor_query_dump(parameters); - printf("\n"); - } else - sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + if (arg_ifindex <= 0) + return status_all(STATUS_DNS); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNS); + + char **args = strv_skip(argv, 2); + r = call_dns(bus, args, bus_resolve_mgr, &error, true); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = call_dns(bus, args, bus_network_mgr, &error, true); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(req, 'a', "(sb)"); + if (r < 0) + return bus_log_create_error(r); + + /* If only argument is the empty string, then call SetLinkDomains() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(domain, STRV_MAKE(""))) + STRV_FOREACH(p, domain) { + const char *n; + + n = **p == '~' ? *p + 1 : *p; + + r = dns_name_is_valid(n); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain %s: %m", n); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Domain not valid: %s", + n); + + r = sd_bus_message_append(req, "(sb)", n, **p == '~'); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + + return sd_bus_call(bus, req, 0, error, NULL); +} + +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_DOMAIN); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DOMAIN); + + char **args = strv_skip(argv, 2); + r = call_domain(bus, args, bus_resolve_mgr, &error); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = call_domain(bus, args, bus_network_mgr, &error); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_DEFAULT_ROUTE); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); + + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +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; + ResolveSupport global_llmnr_support, llmnr_support; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_LLMNR); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_LLMNR); + + llmnr_support = resolve_support_from_string(argv[2]); + if (llmnr_support < 0) + return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); + + r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); + if (r < 0) + return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); + + global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); + if (global_llmnr_support < 0) + return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); + + if (global_llmnr_support < llmnr_support) + log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_llmnr_support_str); + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - fflush(stdout); + return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); + } return 0; } -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; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); +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; + ResolveSupport global_mdns_support, mdns_support; + int r; - r = sd_event_default(&event); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + return r; - r = sd_event_set_signal_exit(event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + if (arg_ifindex <= 0) + return status_all(STATUS_MDNS); - r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ - if (r < 0) - return log_error_errno(r, "Failed to set varlink timeout: %m"); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_MDNS); - r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + mdns_support = resolve_support_from_string(argv[2]); + if (mdns_support < 0) + return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); - r = sd_varlink_bind_reply(vl, monitor_reply); + r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); if (r < 0) - return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); - r = sd_varlink_observebo( - vl, - "io.systemd.Resolve.Monitor.SubscribeQueryResults", - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + global_mdns_support = resolve_support_from_string(global_mdns_support_str); + if (global_mdns_support < 0) + return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + if (global_mdns_support < mdns_support) + log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_mdns_support_str); - r = sd_event_get_exit_code(event, &c); - if (r < 0) - return log_error_errno(r, "Failed to get exit code: %m"); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return c; -} + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); -static int dump_cache_item(sd_json_variant *item) { + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkMulticastDNS", + &error, + NULL, + "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - struct item_info { - sd_json_variant *key; - sd_json_variant *rrs; - const char *type; - uint64_t until; - } item_info = {}; + return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); + } - static const sd_json_dispatch_field dispatch_table[] = { - { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, - { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, - { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, - { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, - {}, - }; + return 0; +} - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - int r, c = 0; +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; - r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); + r = acquire_bus(&bus); if (r < 0) return r; - r = dns_resource_key_from_json(item_info.key, &k); - if (r < 0) - return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (item_info.type) - printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); - else { - sd_json_variant *i; + if (arg_ifindex <= 0) + return status_all(STATUS_DNS_OVER_TLS); - JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *data = NULL; - sd_json_variant *raw; - size_t size; + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); - raw = sd_json_variant_by_key(i, "raw"); - if (!raw) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_json_variant_unbase64(raw, &data, &size); - if (r < 0) - return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = dns_resource_record_new_from_raw(&rr, data, size); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS data: %m"); + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkDNSOverTLS", + &error, + NULL, + "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - printf("%s\n", dns_resource_record_to_string(rr)); - c++; - } + return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); } - return c; + return 0; } -static int dump_cache_scope(sd_json_variant *scope) { - - struct scope_info { - const char *protocol; - int family; - int ifindex; - const char *ifname; - sd_json_variant *cache; - const char *dnssec_mode; - const char *dns_over_tls_mode; - } scope_info = { - .family = AF_UNSPEC, - }; - sd_json_variant *i; - int r, c = 0; - - static const sd_json_dispatch_field dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, - { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, - { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, - {}, - }; +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; - r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); + r = acquire_bus(&bus); if (r < 0) return r; - printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (scope_info.family != AF_UNSPEC) - printf(" family=%s", af_to_name(scope_info.family)); + if (arg_ifindex <= 0) + return status_all(STATUS_DNSSEC); - if (scope_info.ifindex > 0) - printf(" ifindex=%i", scope_info.ifindex); - if (scope_info.ifname) - printf(" ifname=%s", scope_info.ifname); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNSSEC); - if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { - if (scope_info.dnssec_mode) - printf(" DNSSEC=%s", scope_info.dnssec_mode); - if (scope_info.dns_over_tls_mode) - printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - printf("%s\n", ansi_normal()); + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { - r = dump_cache_item(i); - if (r < 0) - return r; + r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - c += r; + return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); } - if (c == 0) - printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); - else - printf("\n"); + return 0; +} + +static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(req, nta); + if (r < 0) + return bus_log_create_error(r); - return 0; + return sd_bus_call(bus, req, 0, error, NULL); } -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; +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; + bool clear; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpCache", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); + if (arg_ifindex <= 0) + return status_all(STATUS_NTA); - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_NTA); - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_cache_scope(i); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() + * with an empty list, which will clear the list of domains for an interface. */ + args = strv_skip(argv, 2); + clear = strv_equal(args, STRV_MAKE("")); + + if (!clear) + STRV_FOREACH(p, args) { + r = dns_name_is_valid(*p); if (r < 0) - return r; + return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Domain not valid: %s", + *p); } - return 0; - } + r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); -} + r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; -static int dump_server_state(sd_json_variant *server) { - _cleanup_(table_unrefp) Table *table = NULL; - TableCell *cell; + return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); + } - struct server_state { - const char *server_name; - const char *type; - const char *ifname; - int ifindex; - const char *verified_feature_level; - const char *possible_feature_level; - const char *dnssec_mode; - bool dnssec_supported; - size_t received_udp_fragment_max; - uint64_t n_failed_udp; - uint64_t n_failed_tcp; - bool packet_truncated; - bool packet_bad_opt; - bool packet_rrsig_missing; - bool packet_invalid; - bool packet_do_off; - } server_state = { - .ifindex = -1, - }; + return 0; +} +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; - static const sd_json_dispatch_field dispatch_table[] = { - { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, - { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, - { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, - { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, - { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, - { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, - { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, - { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, - { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, - { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, - { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, - { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, - { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, - { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, - { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, - { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + r = acquire_bus(&bus); if (r < 0) return r; - table = table_new_vertical(); - if (!table) - return log_oom(); - - assert_se(cell = table_get_cell(table, 0, 0)); - (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 0); - - r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); - if (r < 0) - return table_log_add_error(r); - - r = table_add_many(table, - TABLE_EMPTY, - TABLE_FIELD, "Type", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_STRING, server_state.type); - if (r < 0) - return table_log_add_error(r); - - if (server_state.ifname) { - r = table_add_many(table, - TABLE_FIELD, "Interface", - TABLE_STRING, server_state.ifname); + if (argc >= 2) { + r = ifname_mangle(argv[1]); if (r < 0) - return table_log_add_error(r); + return r; } - if (server_state.ifindex >= 0) { - r = table_add_many(table, - TABLE_FIELD, "Interface Index", - TABLE_INT, server_state.ifindex); - if (r < 0) - return table_log_add_error(r); - } + if (arg_ifindex <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); - if (server_state.verified_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Verified feature level", - TABLE_STRING, server_state.verified_feature_level); - if (r < 0) - return table_log_add_error(r); - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - if (server_state.possible_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Possible feature level", - TABLE_STRING, server_state.possible_feature_level); - if (r < 0) - return table_log_add_error(r); - } + r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = table_add_many(table, - TABLE_FIELD, "DNSSEC Mode", - TABLE_STRING, server_state.dnssec_mode, - TABLE_FIELD, "DNSSEC Supported", - TABLE_STRING, yes_no(server_state.dnssec_supported), - TABLE_FIELD, "Maximum UDP fragment size received", - TABLE_UINT64, server_state.received_udp_fragment_max, - TABLE_FIELD, "Failed UDP attempts", - TABLE_UINT64, server_state.n_failed_udp, - TABLE_FIELD, "Failed TCP attempts", - TABLE_UINT64, server_state.n_failed_tcp, - TABLE_FIELD, "Seen truncated packet", - TABLE_STRING, yes_no(server_state.packet_truncated), - TABLE_FIELD, "Seen OPT RR getting lost", - TABLE_STRING, yes_no(server_state.packet_bad_opt), - TABLE_FIELD, "Seen RRSIG RR missing", - TABLE_STRING, yes_no(server_state.packet_rrsig_missing), - TABLE_FIELD, "Seen invalid packet", - TABLE_STRING, yes_no(server_state.packet_invalid), - TABLE_FIELD, "Server dropped DO flag", - TABLE_STRING, yes_no(server_state.packet_do_off), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, TABLE_EMPTY); + r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - if (r < 0) - return table_log_add_error(r); + return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + } - return table_print_or_warn(table); + return 0; } -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; +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; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpServerState", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); - - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; - - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_server_state(i); - if (r < 0) - return r; - } - - return 0; - } + assert(IN_SET(argc, 1, 2)); - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); + return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); } static int parse_protocol(const char *arg) { From 4e20d61c65c232a25dd30ce0aad4235dec3f634c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:38:04 +0200 Subject: [PATCH 1515/2155] shared/verbs: when showing default verb, put [] around the args too The verb cannot be omitted but the args kept, so: resolvectl [status] [link] is wrong, we need: resolvectl [status [link]] Fixes f94da4b4c564f8cff4b5b739456c985e036a4201. --- src/shared/verbs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 276c6fd5be916..274945e5c5977 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -200,9 +200,9 @@ int _verbs_get_help_table( r = table_add_cell_stringf(table, NULL, " %s%s%s%s%s", is_default ? "[" : "", verb->verb, - is_default ? "]" : "", verb->argspec ? " " : "", - strempty(verb->argspec)); + strempty(verb->argspec), + is_default ? "]" : ""); if (r < 0) return table_log_add_error(r); From 7cea71e4c112177e74140b39898e9836f334e588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:57:43 +0200 Subject: [PATCH 1516/2155] resolvectl: convert to OPTION and VERB macros Use OPTION_NAMESPACE() to keep the resolvectl and systemd-resolve option sets separate. The resolvconf-compat path (resolvconf invocation) keeps its own getopt-based parsing. --help output has the expected changes to formatting. Synopis for [status] is now shows that the verb is optional. Co-developed-by: Claude Opus 4.7 --- src/resolve/resolvectl.c | 668 +++++++++++++++------------------------ 1 file changed, 249 insertions(+), 419 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 4088c39b1da60..015345fdba7d9 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,6 +12,7 @@ #include "af-list.h" #include "alloc-util.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-common-errors.h" @@ -30,17 +30,19 @@ #include "escape.h" #include "format-ifname.h" #include "format-table.h" +#include "glyph-util.h" +#include "help-util.h" #include "hostname-util.h" #include "json-util.h" #include "main-func.h" #include "missing-network.h" #include "netlink-util.h" +#include "options.h" #include "ordered-set.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "resolvconf-compat.h" #include "resolve-util.h" #include "resolvectl.h" @@ -799,6 +801,8 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { "Invalid DNS URI: %s", name); } +VERB(verb_query, "query", "HOSTNAME|ADDRESS…", 2, VERB_ANY, 0, + "Resolve domain names, IPv4 and IPv6 addresses"); 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; @@ -1009,6 +1013,8 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } +VERB(verb_service, "service", "[[NAME] TYPE] DOMAIN", 2, 4, 0, + "Resolve service (SRV)"); 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; @@ -1091,6 +1097,8 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } #endif +VERB(verb_openpgp, "openpgp", "EMAIL@DOMAIN…", 2, VERB_ANY, 0, + "Query OpenPGP public key"); 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; @@ -1148,6 +1156,8 @@ static bool service_family_is_valid(const char *s) { return STR_IN_SET(s, "tcp", "udp", "sctp"); } +VERB(verb_tlsa, "tlsa", "DOMAIN[:PORT]…", 2, VERB_ANY, 0, + "Query TLS public key"); 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"; @@ -1763,10 +1773,14 @@ static int status_ifindex(int ifindex, StatusMode mode) { return status_full(mode, STRV_MAKE(ifname)); } +VERB(verb_status, "status", "[LINK…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "Show link and server status"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return status_full(STATUS_ALL, strv_skip(argv, 1)); } +VERB(verb_show_statistics, "statistics", NULL, VERB_ANY, 1, 0, + "Show resolver statistics"); 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; @@ -1928,6 +1942,8 @@ static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *u return table_print_or_warn(table); } +VERB(verb_reset_statistics, "reset-statistics", NULL, VERB_ANY, 1, 0, + "Reset resolver statistics"); 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; @@ -1953,6 +1969,8 @@ static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void * return 0; } +VERB(verb_flush_caches, "flush-caches", NULL, VERB_ANY, 1, 0, + "Flush all local DNS caches"); 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; @@ -1969,6 +1987,8 @@ static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_reset_server_features, "reset-server-features", NULL, VERB_ANY, 1, 0, + "Forget learnt DNS server feature levels"); 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; @@ -2168,6 +2188,8 @@ static int monitor_reply( return 0; } +VERB(verb_monitor, "monitor", NULL, VERB_ANY, 1, 0, + "Monitor DNS queries"); 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; @@ -2414,6 +2436,8 @@ static int dump_cache_scope(sd_json_variant *scope) { return 0; } +VERB(verb_show_cache, "show-cache", NULL, VERB_ANY, 1, 0, + "Show cache contents"); 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; @@ -2590,6 +2614,8 @@ static int dump_server_state(sd_json_variant *server) { return table_print_or_warn(table); } +VERB(verb_show_server_state, "show-server-state", NULL, VERB_ANY, 1, 0, + "Show servers state"); 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; @@ -2633,6 +2659,8 @@ static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } +VERB(verb_dns, "dns", "[LINK [SERVER…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNS server address"); 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; @@ -2718,6 +2746,8 @@ static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd return sd_bus_call(bus, req, 0, error, NULL); } +VERB(verb_domain, "domain", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface search domain"); 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; @@ -2757,6 +2787,8 @@ static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB(verb_default_route, "default-route", "[LINK [BOOL]]", VERB_ANY, 3, 0, + "Get/set per-interface default route flag"); 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; @@ -2801,6 +2833,8 @@ static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB(verb_llmnr, "llmnr", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface LLMNR mode"); 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; @@ -2859,6 +2893,8 @@ static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_mdns, "mdns", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface MulticastDNS mode"); 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; @@ -2923,6 +2959,8 @@ static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_dns_over_tls, "dnsovertls", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNS-over-TLS mode"); 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; @@ -2969,6 +3007,8 @@ static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_dnssec, "dnssec", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNSSEC mode"); 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; @@ -3030,6 +3070,8 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ return sd_bus_call(bus, req, 0, error, NULL); } +VERB(verb_nta, "nta", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNSSEC NTA"); 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; @@ -3088,6 +3130,8 @@ static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_revert_link, "revert", "LINK", VERB_ANY, 2, 0, + "Revert per-interface configuration"); 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; @@ -3125,6 +3169,8 @@ static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_log_level, "log-level", "[LEVEL]", VERB_ANY, 2, 0, + "Get/set logging threshold for systemd-resolved"); 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; @@ -3187,251 +3233,116 @@ static void help_dns_classes(void) { } static int compat_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("systemd-resolve", &options); if (r < 0) - return log_oom(); + return r; pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\n" - "\n" - "%2$sResolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - " --status Show link and server status\n" - " --flush-caches Flush all local DNS caches\n" - " --reset-server-features\n" - " Forget learnt DNS server feature levels\n" - " --set-dns=SERVER Set per-interface DNS server address\n" - " --set-domain=DOMAIN Set per-interface search domain\n" - " --set-llmnr=MODE Set per-interface LLMNR mode\n" - " --set-mdns=MODE Set per-interface MulticastDNS mode\n" - " --set-dnsovertls=MODE Set per-interface DNS-over-TLS mode\n" - " --set-dnssec=MODE Set per-interface DNSSEC mode\n" - " --set-nta=DOMAIN Set per-interface DNSSEC NTA\n" - " --revert Revert per-interface configuration\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --no-pager Do not pipe output into a pager\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - "\nSee the %4$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("[OPTIONS…] HOSTNAME|ADDRESS…"); + help_cmdline("[OPTIONS…] --service [[NAME] TYPE] DOMAIN"); + help_cmdline("[OPTIONS…] --openpgp EMAIL@DOMAIN…"); + help_cmdline("[OPTIONS…] --statistics"); + help_cmdline("[OPTIONS…] --reset-statistics"); + help_abstract("Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("resolvectl", "1"); return 0; } static int native_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table_ns("resolvectl", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n" - "%5$sSend control commands to the network name resolution manager, or%6$s\n" - "%5$sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%6$s\n" - "\n%3$sCommands:%4$s\n" - " query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n" - " service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n" - " openpgp EMAIL@DOMAIN... Query OpenPGP public key\n" - " tlsa DOMAIN[:PORT]... Query TLS public key\n" - " status [LINK...] Show link and server status\n" - " statistics Show resolver statistics\n" - " reset-statistics Reset resolver statistics\n" - " flush-caches Flush all local DNS caches\n" - " reset-server-features Forget learnt DNS server feature levels\n" - " monitor Monitor DNS queries\n" - " show-cache Show cache contents\n" - " show-server-state Show servers state\n" - " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" - " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" - " default-route [LINK [BOOL]] Get/set per-interface default route flag\n" - " llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n" - " mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n" - " dnsovertls [LINK [MODE]] Get/set per-interface DNS-over-TLS mode\n" - " dnssec [LINK [MODE]] Get/set per-interface DNSSEC mode\n" - " nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n" - " revert LINK Revert per-interface configuration\n" - " log-level [LEVEL] Get/set logging threshold for systemd-resolved\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --validate=BOOL Allow DNSSEC validation (default: yes)\n" - " --synthesize=BOOL Allow synthetic response (default: yes)\n" - " --cache=BOOL Allow response from cache (default: yes)\n" - " --stale-data=BOOL Allow response from cache with stale data (default: yes)\n" - " --relax-single-label=BOOL Allow single label lookups to go upstream (default: no)\n" - " --zone=BOOL Allow response from locally registered mDNS/LLMNR\n" - " records (default: yes)\n" - " --trust-anchor=BOOL Allow response from local trust anchor (default:\n" - " yes)\n" - " --network=BOOL Allow response from network (default: yes)\n" - " --search=BOOL Use search domains for single-label names (default:\n" - " yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short\n" - " otherwise\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Send control commands to the network name resolution manager, or\n" + "resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("resolvectl", "1"); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return native_help(); -} - -static int compat_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_OPENPGP, - ARG_TLSA, - ARG_RAW, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - ARG_STATUS, - ARG_FLUSH_CACHES, - ARG_RESET_SERVER_FEATURES, - ARG_NO_PAGER, - ARG_SET_DNS, - ARG_SET_DOMAIN, - ARG_SET_LLMNR, - ARG_SET_MDNS, - ARG_SET_DNS_OVER_TLS, - ARG_SET_DNSSEC, - ARG_SET_NTA, - ARG_REVERT_LINK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - { "status", no_argument, NULL, ARG_STATUS }, - { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES }, - { "reset-server-features", no_argument, NULL, ARG_RESET_SERVER_FEATURES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "set-dns", required_argument, NULL, ARG_SET_DNS }, - { "set-domain", required_argument, NULL, ARG_SET_DOMAIN }, - { "set-llmnr", required_argument, NULL, ARG_SET_LLMNR }, - { "set-mdns", required_argument, NULL, ARG_SET_MDNS }, - { "set-dnsovertls", required_argument, NULL, ARG_SET_DNS_OVER_TLS }, - { "set-dnssec", required_argument, NULL, ARG_SET_DNSSEC }, - { "set-nta", required_argument, NULL, ARG_SET_NTA }, - { "revert", no_argument, NULL, ARG_REVERT_LINK }, - {} - }; +VERB_COMMON_HELP_HIDDEN(native_help); - int c, r; +static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "systemd-resolve" }; - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemd-resolve"): {} + + OPTION_COMMON_HELP: return compat_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 'p': - r = parse_protocol(optarg); + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); if (r <= 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); @@ -3439,167 +3350,162 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESOLVE_RECORD; break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_SERVICE: + OPTION_LONG("service", NULL, "Resolve service (SRV)"): arg_mode = MODE_RESOLVE_SERVICE; break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_OPENPGP: + OPTION_LONG("openpgp", NULL, "Query OpenPGP public key"): arg_mode = MODE_RESOLVE_OPENPGP; break; - case ARG_TLSA: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "tlsa", "FAMILY", "Query TLS public key"): arg_mode = MODE_RESOLVE_TLSA; - if (!optarg || service_family_is_valid(optarg)) - arg_service_family = optarg; + if (!opts.arg || service_family_is_valid(opts.arg)) + arg_service_family = opts.arg; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown service family \"%s\".", optarg); + "Unknown service family \"%s\".", opts.arg); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_STATISTICS: + OPTION_LONG("statistics", NULL, "Show resolver statistics"): arg_mode = MODE_STATISTICS; break; - case ARG_RESET_STATISTICS: + OPTION_LONG("reset-statistics", NULL, "Reset resolver statistics"): arg_mode = MODE_RESET_STATISTICS; break; - case ARG_STATUS: + OPTION_LONG("status", NULL, "Show link and server status"): arg_mode = MODE_STATUS; break; - case ARG_FLUSH_CACHES: + OPTION_LONG("flush-caches", NULL, "Flush all local DNS caches"): arg_mode = MODE_FLUSH_CACHES; break; - case ARG_RESET_SERVER_FEATURES: + OPTION_LONG("reset-server-features", NULL, + "Forget learnt DNS server feature levels"): arg_mode = MODE_RESET_SERVER_FEATURES; break; - case ARG_SET_DNS: - r = strv_extend(&arg_set_dns, optarg); + OPTION_LONG("set-dns", "SERVER", "Set per-interface DNS server address"): + r = strv_extend(&arg_set_dns, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_DOMAIN: - r = strv_extend(&arg_set_domain, optarg); + OPTION_LONG("set-domain", "DOMAIN", "Set per-interface search domain"): + r = strv_extend(&arg_set_domain, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_LLMNR: - arg_set_llmnr = optarg; + OPTION_LONG("set-llmnr", "MODE", "Set per-interface LLMNR mode"): + arg_set_llmnr = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_MDNS: - arg_set_mdns = optarg; + OPTION_LONG("set-mdns", "MODE", "Set per-interface MulticastDNS mode"): + arg_set_mdns = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNS_OVER_TLS: - arg_set_dns_over_tls = optarg; + OPTION_LONG("set-dnsovertls", "MODE", "Set per-interface DNS-over-TLS mode"): + arg_set_dns_over_tls = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNSSEC: - arg_set_dnssec = optarg; + OPTION_LONG("set-dnssec", "MODE", "Set per-interface DNSSEC mode"): + arg_set_dnssec = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_NTA: - r = strv_extend(&arg_set_nta, optarg); + OPTION_LONG("set-nta", "DOMAIN", "Set per-interface DNSSEC NTA"): + r = strv_extend(&arg_set_nta, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_REVERT_LINK: + OPTION_LONG("revert", NULL, "Revert per-interface configuration"): arg_mode = MODE_REVERT_LINK; break; - case ARG_RAW: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): if (on_tty()) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Refusing to write binary data to tty."); - if (optarg == NULL || streq(optarg, "payload")) + if (opts.arg == NULL || streq(opts.arg, "payload")) arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) + else if (streq(opts.arg, "packet")) arg_raw = RAW_PACKET; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --raw specifier \"%s\".", - optarg); + opts.arg); arg_legend = false; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_type == 0 && arg_class != 0) @@ -3623,253 +3529,209 @@ static int compat_parse_argv(int argc, char *argv[]) { "--set-dns=, --set-domain=, --set-llmnr=, --set-mdns=, --set-dnsovertls=, --set-dnssec=, --set-nta= and --revert require --interface=."); } + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -static int native_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_CNAME, - ARG_VALIDATE, - ARG_SYNTHESIZE, - ARG_CACHE, - ARG_ZONE, - ARG_TRUST_ANCHOR, - ARG_NETWORK, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_RAW, - ARG_SEARCH, - ARG_NO_PAGER, - ARG_NO_ASK_PASSWORD, - ARG_JSON, - ARG_STALE_DATA, - ARG_RELAX_SINGLE_LABEL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "validate", required_argument, NULL, ARG_VALIDATE }, - { "synthesize", required_argument, NULL, ARG_SYNTHESIZE }, - { "cache", required_argument, NULL, ARG_CACHE }, - { "zone", required_argument, NULL, ARG_ZONE }, - { "trust-anchor", required_argument, NULL, ARG_TRUST_ANCHOR }, - { "network", required_argument, NULL, ARG_NETWORK }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - { "stale-data", required_argument, NULL, ARG_STALE_DATA }, - { "relax-single-label", required_argument, NULL, ARG_RELAX_SINGLE_LABEL }, - {} - }; - - int c, r; +static int native_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "resolvectl" }; - while ((c = getopt_long(argc, argv, "h46i:t:c:p:j", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvectl"): {} + + OPTION_COMMON_HELP: return native_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 'p': - r = parse_protocol(optarg); + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); if (r <= 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - case ARG_VALIDATE: - r = parse_boolean_argument("--validate=", optarg, NULL); + OPTION_LONG("validate", "BOOL", "Allow DNSSEC validation (default: yes)"): + r = parse_boolean_argument("--validate=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_VALIDATE, r == 0); break; - case ARG_SYNTHESIZE: - r = parse_boolean_argument("--synthesize=", optarg, NULL); + OPTION_LONG("synthesize", "BOOL", "Allow synthetic response (default: yes)"): + r = parse_boolean_argument("--synthesize=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SYNTHESIZE, r == 0); break; - case ARG_CACHE: - r = parse_boolean_argument("--cache=", optarg, NULL); + OPTION_LONG("cache", "BOOL", "Allow response from cache (default: yes)"): + r = parse_boolean_argument("--cache=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CACHE, r == 0); break; - case ARG_STALE_DATA: - r = parse_boolean_argument("--stale-data=", optarg, NULL); + OPTION_LONG("stale-data", "BOOL", + "Allow response from cache with stale data (default: yes)"): + r = parse_boolean_argument("--stale-data=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_STALE, r == 0); break; - case ARG_RELAX_SINGLE_LABEL: - r = parse_boolean_argument("--relax-single-label=", optarg, NULL); + OPTION_LONG("relax-single-label", "BOOL", + "Allow single label lookups to go upstream (default: no)"): + r = parse_boolean_argument("--relax-single-label=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); break; - case ARG_ZONE: - r = parse_boolean_argument("--zone=", optarg, NULL); + OPTION_LONG("zone", "BOOL", + "Allow response from locally registered mDNS/LLMNR records (default: yes)"): + r = parse_boolean_argument("--zone=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ZONE, r == 0); break; - case ARG_TRUST_ANCHOR: - r = parse_boolean_argument("--trust-anchor=", optarg, NULL); + OPTION_LONG("trust-anchor", "BOOL", + "Allow response from local trust anchor (default: yes)"): + r = parse_boolean_argument("--trust-anchor=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TRUST_ANCHOR, r == 0); break; - case ARG_NETWORK: - r = parse_boolean_argument("--network=", optarg, NULL); + OPTION_LONG("network", "BOOL", "Allow response from network (default: yes)"): + r = parse_boolean_argument("--network=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); break; - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_RAW: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): if (on_tty()) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Refusing to write binary data to tty."); - if (optarg == NULL || streq(optarg, "payload")) + if (opts.arg == NULL || streq(opts.arg, "payload")) arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) + else if (streq(opts.arg, "packet")) arg_raw = RAW_PACKET; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --raw specifier \"%s\".", - optarg); + opts.arg); arg_legend = false; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); if (r < 0) return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.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(); } if (arg_type == 0 && arg_class != 0) @@ -3882,140 +3744,107 @@ static int native_parse_argv(int argc, char *argv[]) { if (arg_class != 0 && arg_type == 0) arg_type = DNS_TYPE_A; + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -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, 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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); -} - -static int translate(const char *verb, const char *single_arg, size_t num_args, char **args) { +static int translate(const char *verb, const char *single_arg, char **args) { char **fake, **p; size_t num; assert(verb); - assert(num_args == 0 || args); - num = !!single_arg + num_args + 1; + num = !!single_arg + strv_length(args) + 1; p = fake = newa0(char *, num + 1); *p++ = (char *) verb; if (single_arg) *p++ = (char *) single_arg; - FOREACH_ARRAY(arg, args, num_args) - *p++ = *arg; + STRV_FOREACH(a, args) + *p++ = *a; - optind = 0; - return native_main((int) num, fake); + return dispatch_verb_with_args(fake, /* userdata= */ NULL); } -static int compat_main(int argc, char *argv[]) { +static int compat_main(char **args) { int r = 0; switch (arg_mode) { case MODE_RESOLVE_HOST: case MODE_RESOLVE_RECORD: - return translate("query", NULL, argc - optind, argv + optind); + return translate("query", NULL, args); case MODE_RESOLVE_SERVICE: - return translate("service", NULL, argc - optind, argv + optind); + return translate("service", NULL, args); case MODE_RESOLVE_OPENPGP: - return translate("openpgp", NULL, argc - optind, argv + optind); + return translate("openpgp", NULL, args); case MODE_RESOLVE_TLSA: - return translate("tlsa", arg_service_family, argc - optind, argv + optind); + return translate("tlsa", arg_service_family, args); case MODE_STATISTICS: - return translate("statistics", NULL, 0, NULL); + return translate("statistics", NULL, NULL); case MODE_RESET_STATISTICS: - return translate("reset-statistics", NULL, 0, NULL); + return translate("reset-statistics", NULL, NULL); case MODE_FLUSH_CACHES: - return translate("flush-caches", NULL, 0, NULL); + return translate("flush-caches", NULL, NULL); case MODE_RESET_SERVER_FEATURES: - return translate("reset-server-features", NULL, 0, NULL); + return translate("reset-server-features", NULL, NULL); case MODE_STATUS: - return translate("status", NULL, argc - optind, argv + optind); + return translate("status", NULL, args); case MODE_SET_LINK: assert(arg_ifname); if (arg_disable_default_route) { - r = translate("default-route", arg_ifname, 1, STRV_MAKE("no")); + r = translate("default-route", arg_ifname, STRV_MAKE("no")); if (r < 0) return r; } if (arg_set_dns) { - r = translate("dns", arg_ifname, strv_length(arg_set_dns), arg_set_dns); + r = translate("dns", arg_ifname, arg_set_dns); if (r < 0) return r; } if (arg_set_domain) { - r = translate("domain", arg_ifname, strv_length(arg_set_domain), arg_set_domain); + r = translate("domain", arg_ifname, arg_set_domain); if (r < 0) return r; } if (arg_set_nta) { - r = translate("nta", arg_ifname, strv_length(arg_set_nta), arg_set_nta); + r = translate("nta", arg_ifname, arg_set_nta); if (r < 0) return r; } if (arg_set_llmnr) { - r = translate("llmnr", arg_ifname, 1, (char **) &arg_set_llmnr); + r = translate("llmnr", arg_ifname, STRV_MAKE(arg_set_llmnr)); if (r < 0) return r; } if (arg_set_mdns) { - r = translate("mdns", arg_ifname, 1, (char **) &arg_set_mdns); + r = translate("mdns", arg_ifname, STRV_MAKE(arg_set_mdns)); if (r < 0) return r; } if (arg_set_dns_over_tls) { - r = translate("dnsovertls", arg_ifname, 1, (char **) &arg_set_dns_over_tls); + r = translate("dnsovertls", arg_ifname, STRV_MAKE(arg_set_dns_over_tls)); if (r < 0) return r; } if (arg_set_dnssec) { - r = translate("dnssec", arg_ifname, 1, (char **) &arg_set_dnssec); + r = translate("dnssec", arg_ifname, STRV_MAKE(arg_set_dnssec)); if (r < 0) return r; } @@ -4025,7 +3854,7 @@ static int compat_main(int argc, char *argv[]) { case MODE_REVERT_LINK: assert(arg_ifname); - return translate("revert", arg_ifname, 0, NULL); + return translate("revert", arg_ifname, NULL); case _MODE_INVALID: assert_not_reached(); @@ -4035,6 +3864,7 @@ static int compat_main(int argc, char *argv[]) { } static int run(int argc, char **argv) { + char **args = NULL; bool compat = false; int r; @@ -4046,16 +3876,16 @@ static int run(int argc, char **argv) { r = resolvconf_parse_argv(argc, argv); } else if (invoked_as(argv, "systemd-resolve")) { compat = true; - r = compat_parse_argv(argc, argv); + r = compat_parse_argv(argc, argv, &args); } else - r = native_parse_argv(argc, argv); + r = native_parse_argv(argc, argv, &args); if (r <= 0) return r; if (compat) - return compat_main(argc, argv); + return compat_main(args); - return native_main(argc, argv); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From 366071143d60987f44d6173730ffc3fb875fd804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:54:12 +0200 Subject: [PATCH 1517/2155] resolvconf-compat: convert to OPTION macros Use the "resolvconf" namespace to keep these options separate from the resolvectl/systemd-resolve sets. Co-developed-by: Claude Opus 4.7 --- src/resolve/resolvconf-compat.c | 131 +++++++++++++------------------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index d8e68d524a094..d3ddec755a244 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "resolvconf-compat.h" #include "resolvectl.h" #include "string-util.h" @@ -21,36 +22,32 @@ typedef enum LookupType { } LookupType; static int resolvconf_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("resolvconf", &options); if (r < 0) - return log_oom(); - - printf("%1$s -a INTERFACE < FILE\n" - "%1$s -d INTERFACE\n" - "\n" - "Register DNS server and domain configuration with systemd-resolved.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -a Register per-interface DNS server and domain data\n" - " -d Unregister per-interface DNS server and domain data\n" - " -p Do not use this interface as default route\n" - " -f Ignore if specified interface does not exist\n" - " -x Send DNS traffic preferably over this interface\n" - "\n" + return r; + + help_cmdline("-a INTERFACE = 0); assert(argv); @@ -204,89 +182,86 @@ int resolvconf_parse_argv(int argc, char *argv[]) { arg_mode = _MODE_INVALID; - while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "resolvconf" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvconf"): {} + + OPTION_COMMON_HELP: return resolvconf_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); /* -a and -d is what everybody can agree on */ - case 'a': + OPTION_SHORT('a', NULL, "Register per-interface DNS server and domain data"): arg_mode = MODE_SET_LINK; break; - case 'd': + OPTION_SHORT('d', NULL, "Unregister per-interface DNS server and domain data"): arg_mode = MODE_REVERT_LINK; break; - /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ - case 'x': - lookup_type = LOOKUP_TYPE_EXCLUSIVE; - break; - - case 'p': + OPTION_SHORT('p', NULL, "Do not use this interface as default route"): lookup_type = LOOKUP_TYPE_PRIVATE; break; - case 'f': + OPTION_SHORT('f', NULL, "Ignore if specified interface does not exist"): arg_ifindex_permissive = true; break; + /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ + OPTION_SHORT('x', NULL, "Send DNS traffic preferably over this interface"): + lookup_type = LOOKUP_TYPE_EXCLUSIVE; + break; + /* The metrics stuff is an openresolv invention we ignore (and don't really need) */ - case 'm': - log_debug("Switch -%c ignored.", c); + OPTION_SHORT('m', "ARG", /* help= */ NULL): + log_debug("Switch -%c ignored.", opts.opt->short_code); break; /* -u supposedly should "update all subscribers". We have no subscribers, hence let's make this a NOP, and exit immediately, cleanly. */ - case 'u': - log_info("Switch -%c ignored.", c); + OPTION_SHORT('u', NULL, /* help= */ NULL): + log_info("Switch -%c ignored.", opts.opt->short_code); return 0; /* The following options are openresolv inventions we don't support. */ - case 'I': - case 'i': - case 'l': - case 'R': - case 'r': - case 'v': - case 'V': + OPTION_SHORT('I', NULL, /* help= */ NULL): {} + OPTION_SHORT('i', "ARG", /* help= */ NULL): {} + OPTION_SHORT('l', "ARG", /* help= */ NULL): {} + OPTION_SHORT('R', NULL, /* help= */ NULL): {} + OPTION_SHORT('r', "ARG", /* help= */ NULL): {} + OPTION_SHORT('v', NULL, /* help= */ NULL): {} + OPTION_SHORT('V', NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Switch -%c not supported.", c); + "Switch -%c not supported.", opts.opt->short_code); /* The Debian resolvconf commands we don't support. */ - case ARG_ENABLE_UPDATES: + OPTION_LONG("enable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --enable-updates not supported."); - case ARG_DISABLE_UPDATES: + OPTION_LONG("disable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --disable-updates not supported."); - case ARG_UPDATES_ARE_ENABLED: + OPTION_LONG("updates-are-enabled", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --updates-are-enabled not supported."); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_mode == _MODE_INVALID) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected either -a or -d on the command line."); - if (optind+1 != argc) + if (option_parser_get_n_args(&opts) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected interface name as argument."); - r = ifname_resolvconf_mangle(argv[optind]); + r = ifname_resolvconf_mangle(option_parser_get_arg(&opts, 0)); if (r <= 0) return r; - optind++; if (arg_mode == MODE_SET_LINK) { r = parse_stdin(lookup_type); From 62a489fbf97ae3cbaf36cc1b2ad5260032423385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 21:43:32 +0200 Subject: [PATCH 1518/2155] tree-wide: rename unref_and_replace_full to unref_and_replace_new_ref We have a number of *_unref_and_replace macros. One could think that they are like the various free_and_replace variants, but they actually create a new ref to the passed object. The free_and_replace variants take ownership of the argument. This inconsistency is surprising. Rename all those functions to have "_new_ref" at the end to make the difference clear. --- src/basic/cleanup-util.h | 14 +++++++------- src/libsystemd-network/dhcp-lease-internal.h | 4 ++-- src/libsystemd-network/sd-dhcp-client.c | 6 +++--- src/libsystemd-network/sd-dhcp6-client.c | 2 +- src/libsystemd/sd-device/device-util.h | 4 ++-- src/network/networkd-dhcp6.c | 2 +- src/network/networkd-link.c | 2 +- src/network/networkd-wiphy.c | 4 ++-- src/shared/blockdev-util.c | 2 +- src/udev/udev-manager.c | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/basic/cleanup-util.h b/src/basic/cleanup-util.h index 041c37530aaa9..e3a2ade4ed984 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -19,13 +19,13 @@ typedef void* (*mfree_func_t)(void *p); /* This is similar to free_and_replace_full(), but NULL is not assigned to 'b', and its reference counter is * increased. */ -#define unref_and_replace_full(a, b, ref_func, unref_func) \ - ({ \ - typeof(a)* _a = &(a); \ - typeof(b) _b = ref_func(b); \ - unref_func(*_a); \ - *_a = _b; \ - 0; \ +#define unref_and_replace_new_ref(a, b, ref_func, unref_func) \ + ({ \ + typeof(a)* _a = &(a); \ + typeof(b) _b = ref_func(b); \ + unref_func(*_a); \ + *_a = _b; \ + 0; \ }) #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 744580fc48a88..1eab7f89b616c 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -97,5 +97,5 @@ void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *time int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); -#define dhcp_lease_unref_and_replace(a, b) \ - unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) +#define dhcp_lease_unref_and_replace_new_ref(a, b) \ + unref_and_replace_new_ref(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 9742ab833bd5b..922dd5881ca9e 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1594,7 +1594,7 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage dhcp_lease_set_timestamp(lease, timestamp); - dhcp_lease_unref_and_replace(client->lease, lease); + dhcp_lease_unref_and_replace_new_ref(client->lease, lease); if (client->lease->rapid_commit) { log_dhcp_client(client, "ACK"); @@ -1678,7 +1678,7 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_ else r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - dhcp_lease_unref_and_replace(client->lease, lease); + dhcp_lease_unref_and_replace_new_ref(client->lease, lease); log_dhcp_client(client, "ACK"); return r; @@ -2281,7 +2281,7 @@ sd_event* sd_dhcp_client_get_event(sd_dhcp_client *client) { int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev) { assert_return(client, -EINVAL); - return device_unref_and_replace(client->dev, dev); + return device_unref_and_replace_new_ref(client->dev, dev); } static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index ee67664364c9f..5e71d63de5a43 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -1533,7 +1533,7 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev) { assert_return(client, -EINVAL); - return device_unref_and_replace(client->dev, dev); + return device_unref_and_replace_new_ref(client->dev, dev); } static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h index 3bbe321f61649..e4350a679b690 100644 --- a/src/libsystemd/sd-device/device-util.h +++ b/src/libsystemd/sd-device/device-util.h @@ -6,8 +6,8 @@ #include "sd-forward.h" #include "log.h" -#define device_unref_and_replace(a, b) \ - unref_and_replace_full(a, b, sd_device_ref, sd_device_unref) +#define device_unref_and_replace_new_ref(a, b) \ + unref_and_replace_new_ref(a, b, sd_device_ref, sd_device_unref) #define FOREACH_DEVICE_PROPERTY(device, key, value) \ for (const char *value, *key = sd_device_get_property_first(device, &value); \ diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index c230a86587464..799caeb15b312 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -364,7 +364,7 @@ static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) if (r < 0) return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); - unref_and_replace_full(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); + unref_and_replace_new_ref(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); link_dirty(link); return 0; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index c6bc8fc4b1c4d..dcd081039bf55 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1678,7 +1678,7 @@ static int link_initialized(Link *link, sd_device *device) { /* Always replace with the new sd_device object. As the sysname (and possibly other properties * or sysattrs) may be outdated. */ - device_unref_and_replace(link->dev, device); + device_unref_and_replace_new_ref(link->dev, device); r = link_managed_by_us(link); if (r <= 0) diff --git a/src/network/networkd-wiphy.c b/src/network/networkd-wiphy.c index 1dde69a43b44d..f715f244d6b29 100644 --- a/src/network/networkd-wiphy.c +++ b/src/network/networkd-wiphy.c @@ -469,7 +469,7 @@ int manager_udev_process_wiphy(Manager *m, sd_device *device, sd_device_action_t return 0; } - return device_unref_and_replace(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); + return device_unref_and_replace_new_ref(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); } int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_t action) { @@ -501,5 +501,5 @@ int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_ return 0; } - return device_unref_and_replace(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); + return device_unref_and_replace_new_ref(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); } diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 12a4f59c28a69..83eb67c54152d 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -207,7 +207,7 @@ int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **r r = block_device_get_originating(dev_whole_disk, &dev_origin, /* recursive= */ false); if (r >= 0) - device_unref_and_replace(dev, dev_origin); + device_unref_and_replace_new_ref(dev, dev_origin); else if (r != -ENOENT) return r; } diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 7c2530f17fec8..44d2ee6400362 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -683,7 +683,7 @@ static void event_find_blocker(Event *event) { log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, event->seqnum, e->seqnum); - unref_and_replace_full(event->blocker, e, event_ref, event_unref); + unref_and_replace_new_ref(event->blocker, e, event_ref, event_unref); return; } From f3ee3510a3ce67bcde3016c6f26faf3a6a9f5150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 22:34:11 +0200 Subject: [PATCH 1519/2155] sd-dhcp-client: avoid taking and dropping a reference The helper would create a new ref, even though we had one handy and didn't need to create a new ref. So change the helper to take an existing reference. --- src/libsystemd-network/dhcp-lease-internal.h | 4 ++-- src/libsystemd-network/sd-dhcp-client.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 1eab7f89b616c..3f5d638d67d39 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -97,5 +97,5 @@ void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *time int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); -#define dhcp_lease_unref_and_replace_new_ref(a, b) \ - unref_and_replace_new_ref(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) +#define dhcp_lease_unref_and_replace(a, b) \ + free_and_replace_full(a, b, sd_dhcp_lease_unref) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 922dd5881ca9e..c6a4d02f965a4 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1594,7 +1594,7 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage dhcp_lease_set_timestamp(lease, timestamp); - dhcp_lease_unref_and_replace_new_ref(client->lease, lease); + dhcp_lease_unref_and_replace(client->lease, lease); if (client->lease->rapid_commit) { log_dhcp_client(client, "ACK"); @@ -1678,7 +1678,7 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_ else r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - dhcp_lease_unref_and_replace_new_ref(client->lease, lease); + dhcp_lease_unref_and_replace(client->lease, lease); log_dhcp_client(client, "ACK"); return r; From f2361469e41454c573200c562b9d89481205f76b Mon Sep 17 00:00:00 2001 From: Dirga Yuza Date: Fri, 8 May 2026 07:10:40 +0700 Subject: [PATCH 1520/2155] hwdb: add force-release to Nitro AN515-58 backlight keys This fixes an incomplete mapping introduced in PR #39769 for the Acer Nitro 5 AN515-58. The previous PR mapped the physical keyboard backlight keys (scancodes `0xef` and `0xf0`) to `kbdillumup` and `kbdillumdown` to prevent them from dropping screen brightness. However, the embedded controller on this Acer model only emits "make" (press) scancodes and fails to emit "break" (release) scancodes for these specific keys. Without a release event, the input subsystem registers the keys as continously held down (auto-repeat). In desktop environments like KDE Plasma, pressing the key once causes the brightness UI slider to get stuck in an infinite adjustment loop. This issue is previously unnoticed as this model did not expose any keyboard backlight control. The fix is done by prepending the `!` (force-release) flag to the keycodes. This instructs `evdev` to synthesize a key release event. The fix is verified locally on an Acer Nitro AN515-58. `evtest` now correctly reports `value 1` immediately followed by `value` 0, and KDE Plasma brightness OSD no longer gets stuck. Signed-off-by: Dirga Yuza --- hwdb.d/60-keyboard.hwdb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 8c1605930da97..ebc08560fe9e8 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -237,8 +237,8 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-47:pvr* # Nitro AN515-58 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-58:pvr* - KEYBOARD_KEY_ef=kbdillumup # Fn+F10 - KEYBOARD_KEY_f0=kbdillumdown # Fn+F9 + KEYBOARD_KEY_ef=!kbdillumup # Fn+F10 + KEYBOARD_KEY_f0=!kbdillumdown # Fn+F9 KEYBOARD_KEY_8a=micmute # Microphone mute button KEYBOARD_KEY_55=power From 88d7e4439216b1ad2e3ba061dcf6ef99d9af9574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 22:53:27 +0200 Subject: [PATCH 1521/2155] Add json_variant_unref_and_replace and use it where appropriate JSON_VARIANT_REPLACE is similar, but doesn't try to nullify the second arg, so it can be used with an inline expression. --- src/home/homectl.c | 8 ++----- src/home/user-record-util.c | 32 +++++--------------------- src/libsystemd/sd-json/json-util.h | 13 +++++++---- src/libsystemd/sd-json/sd-json.c | 16 ++++++------- src/libsystemd/sd-json/test-json.c | 3 +-- src/libsystemd/sd-varlink/sd-varlink.c | 5 +--- src/resolve/resolvectl.c | 6 ++--- src/resolve/resolved-manager.c | 2 +- src/shared/user-record.c | 6 ++--- 9 files changed, 30 insertions(+), 61 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index ce54da0d8c2b7..80ee6e2155a94 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1038,8 +1038,7 @@ static int apply_identity_changes(sd_json_variant **_v) { if (r < 0) return log_error_errno(r, "Failed to allocate new perMachine array: %m"); - sd_json_variant_unref(per_machine); - per_machine = TAKE_PTR(npm); + json_variant_unref_and_replace(per_machine, npm); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *positive = sd_json_variant_ref(arg_identity_extra_this_machine), *negative = sd_json_variant_ref(arg_identity_extra_other_machines); @@ -1122,10 +1121,7 @@ static int apply_identity_changes(sd_json_variant **_v) { } } - sd_json_variant_unref(*_v); - *_v = TAKE_PTR(v); - - return 0; + return json_variant_unref_and_replace(*_v, v); } static int add_disposition(sd_json_variant **v) { diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index b27d993922a60..b419a85b6842d 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -376,8 +376,7 @@ int user_record_add_binding( if (r < 0) return r; - sd_json_variant_unref(new_binding_entry); - new_binding_entry = TAKE_PTR(be); + json_variant_unref_and_replace(new_binding_entry, be); } } @@ -787,12 +786,8 @@ int user_record_update_last_changed(UserRecord *h, bool with_password) { } h->last_change_usec = n; - - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->mask |= USER_RECORD_REGULAR; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend) { @@ -1131,12 +1126,8 @@ int user_record_set_password_change_now(UserRecord *h, int b) { SET_FLAG(h->mask, USER_RECORD_PER_MACHINE, !sd_json_variant_is_blank_array(array)); } - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(w); - h->password_change_now = b; - - return 0; + return json_variant_unref_and_replace(h->json, w); } int user_record_merge_secret(UserRecord *h, UserRecord *secret) { @@ -1227,14 +1218,10 @@ int user_record_good_authentication(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->good_authentication_counter = counter; h->last_good_authentication_usec = usec; - h->mask |= USER_RECORD_STATUS; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_bad_authentication(UserRecord *h) { @@ -1282,14 +1269,10 @@ int user_record_bad_authentication(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->bad_authentication_counter = counter; h->last_bad_authentication_usec = usec; - h->mask |= USER_RECORD_STATUS; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_ratelimit(UserRecord *h) { @@ -1344,13 +1327,10 @@ int user_record_ratelimit(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->ratelimit_begin_usec = new_ratelimit_begin_usec; h->ratelimit_count = new_ratelimit_count; - h->mask |= USER_RECORD_STATUS; + json_variant_unref_and_replace(h->json, v); return 1; } diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 0db1e445e62ac..6f06f6fb63500 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -9,14 +9,17 @@ #include "sd-forward.h" #include "string-util.h" /* IWYU pragma: keep */ -#define JSON_VARIANT_REPLACE(v, q) \ - do { \ - typeof(v)* _v = &(v); \ - typeof(q) _q = (q); \ +#define JSON_VARIANT_REPLACE(v, q) \ + do { \ + typeof(v)* _v = &(v); \ + typeof(q) _q = (q); \ sd_json_variant_unref(*_v); \ - *_v = _q; \ + *_v = _q; \ } while(false) +#define json_variant_unref_and_replace(a, b) \ + free_and_replace_full(a, b, sd_json_variant_unref) + static inline int json_variant_set_field_non_null(sd_json_variant **v, const char *field, sd_json_variant *value) { return value && !sd_json_variant_is_null(value) ? sd_json_variant_set_field(v, field, value) : 0; } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 659dffb2bac7e..fe8b8225c96d3 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -1974,7 +1974,7 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { return r; json_variant_propagate_sensitive(*v, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return (int) n; } @@ -2043,7 +2043,7 @@ _public_ int sd_json_variant_set_field(sd_json_variant **v, const char *field, s return r; json_variant_propagate_sensitive(*v, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return 1; } @@ -2183,7 +2183,7 @@ _public_ int sd_json_variant_merge_object(sd_json_variant **v, sd_json_variant * json_variant_propagate_sensitive(*v, w); json_variant_propagate_sensitive(m, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return 1; } @@ -2262,9 +2262,7 @@ _public_ int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant * } json_variant_propagate_sensitive(*v, nv); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(nv)); - - return 0; + return json_variant_unref_and_replace(*v, nv); } _public_ int sd_json_variant_append_arrayb(sd_json_variant **v, ...) { @@ -2511,7 +2509,7 @@ static int json_variant_set_source(sd_json_variant **v, JsonSource *source, unsi w->line = line; w->column = column; - JSON_VARIANT_REPLACE(*v, w); + json_variant_unref_and_replace(*v, w); return 1; } @@ -5855,7 +5853,7 @@ _public_ int sd_json_variant_sort(sd_json_variant **v) { if (!n->sorted) /* Check if this worked. This will fail if there are multiple identical keys used. */ return -ENOTUNIQ; - JSON_VARIANT_REPLACE(*v, TAKE_PTR(n)); + json_variant_unref_and_replace(*v, n); return 1; } @@ -5910,7 +5908,7 @@ _public_ int sd_json_variant_normalize(sd_json_variant **v) { goto finish; } - JSON_VARIANT_REPLACE(*v, TAKE_PTR(n)); + json_variant_unref_and_replace(*v, n); r = 1; diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index 1785ef416f5b1..3be4b09660b14 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -548,8 +548,7 @@ TEST(depth) { assert_se(r >= 0); - sd_json_variant_unref(v); - v = TAKE_PTR(w); + json_variant_unref_and_replace(v, w); } sd_json_variant_dump(v, 0, stdout, NULL); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 2a5f677ef37d0..8e43e38800bde 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -772,10 +772,7 @@ static int varlink_sanitize_incoming_parameters(sd_json_variant **v) { r = sd_json_variant_new_object(&empty, NULL, 0); if (r < 0) return r; - /* sd_json_variant_unref() is a NOP if *v is NULL */ - sd_json_variant_unref(*v); - *v = TAKE_PTR(empty); - return 0; + return json_variant_unref_and_replace(*v, empty); } /* Ensure we have an object */ diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 015345fdba7d9..e8ae7c5412acc 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1251,8 +1251,7 @@ static int status_json_filter_links(sd_json_variant **configuration, char **link return r; } - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*configuration, v); } static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { @@ -1281,8 +1280,7 @@ static int status_json_filter_fields(sd_json_variant **configuration, StatusMode return r; } - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*configuration, v); } static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 0a52e922c4ff5..d7d707726587d 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2335,7 +2335,7 @@ int manager_send_dns_configuration_changed(Manager *m, Link *l, bool reset) { if (sd_json_variant_equal(configuration, m->dns_configuration_json)) return 0; - JSON_VARIANT_REPLACE(m->dns_configuration_json, TAKE_PTR(configuration)); + json_variant_unref_and_replace(m->dns_configuration_json, configuration); r = varlink_many_notify(m->varlink_dns_configuration_subscription, m->dns_configuration_json); if (r < 0) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 191870c8ea62d..7c64171bf58ec 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2374,8 +2374,7 @@ static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_jso return r; } - JSON_VARIANT_REPLACE(*target, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*target, v); } static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) { @@ -2460,8 +2459,7 @@ static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h return r; } - JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*ret, v); } int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) { From 576ca62b9b1b76c5621d29b9a3895ab21e645740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 22:55:00 +0200 Subject: [PATCH 1522/2155] userdbctl: split out parse_from_file() parse_from_file doesn't set arg_from_file itself, but returns a sd_json_variant ref to the caller. I think the change of arg_from_file is more readable with this structure. --- src/userdb/userdbctl.c | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 6b4371aa509b1..75b30215b1da3 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -19,6 +19,7 @@ #include "format-util.h" #include "fs-util.h" #include "io-util.h" +#include "json-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" @@ -1590,6 +1591,30 @@ static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } +static int parse_from_file(const char *arg, sd_json_variant **ret) { + sd_json_variant *v = NULL; + int r; + + assert(ret); + + if (!isempty(arg)) { + const char *fn = streq(arg, "-") ? NULL : arg; + unsigned line = 0; + 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."); + } + + *ret = v; + return 0; +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1832,20 +1857,13 @@ static int parse_argv(int argc, char *argv[]) { break; case 'F': { - if (isempty(optarg)) { - arg_from_file = sd_json_variant_unref(arg_from_file); - break; - } + sd_json_variant *v = NULL; /* initialization to appease gcc-14 */ - _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_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); + r = parse_from_file(optarg, &v); if (r < 0) - return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "", line, r, "JSON parse failure."); + return r; - sd_json_variant_unref(arg_from_file); - arg_from_file = TAKE_PTR(v); + json_variant_unref_and_replace(arg_from_file, v); break; } From 244d80bbf85c7a3ac2834b426e9bd5d8c9b73144 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 6 May 2026 19:57:19 +0100 Subject: [PATCH 1523/2155] test: try to make TEST-04-JOURNAL.journalctl-varlink less flaky The io.systemd.JournalAccess server occasionally returns NoEntries for a unit-filter query right after the unit logged its message, e.g. from a failing CI run: [ 1204.967910] TEST-04-JOURNAL.sh[15025]: ++ varlinkctl call --more /run/systemd/io.systemd.JournalAccess io.systemd.JournalAccess.GetEntries '{"units": ["test-journalctl-varlink-1-13583.service", "test-journalctl-varlink-2-25039.service"]}' [ 1205.017361] journalctl[15026]: varlink-3-3: Received message: {"method":"io.systemd.JournalAccess.GetEntries","parameters":{"units":["test-journalctl-varlink-1-13583.service","test-journalctl-varlink-2-25039.service"]},"more":true} [ 1205.017498] journalctl[15026]: Failed to open journal file /var/log/journal/ce54feb228124e639f3b7779beeaff60/system.journal: No data available [ 1205.017823] journalctl[15026]: varlink-3-3: Sending message: {"error":"io.systemd.JournalAccess.NoEntries"} [ 1205.017936] TEST-04-JOURNAL.sh[15025]: Method call failed: io.systemd.JournalAccess.NoEntries [ 1205.499083] TEST-04-JOURNAL.sh[146]: Subtest /usr/lib/systemd/tests/testdata/units/TEST-04-JOURNAL.journalctl-varlink.sh failed Wrap the calls that expect data in a helper that retries up to 3 times on NoEntries, syncing the journal between attempts. Follow-up for a109189fabe6a4c307528459f891c2d545361622 Co-developed-by: Claude Opus 4.7 --- .../TEST-04-JOURNAL.journalctl-varlink.sh | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh index 4f86fa2a541ff..d1738487f5df3 100755 --- a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh +++ b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh @@ -5,6 +5,26 @@ set -o pipefail VARLINK_SOCKET="/run/systemd/io.systemd.JournalAccess" +# Wrapper around varlinkctl that retries up to 3 times when the server returns +# NoEntries to avoid spurious flaky failures +varlinkctl_get_entries() { + local output rc + for _ in 1 2 3; do + output="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "$@" 2>&1)" && rc=0 || rc=$? + if [[ $rc -eq 0 ]]; then + printf '%s\n' "$output" + return 0 + fi + if ! grep -q 'io.systemd.JournalAccess.NoEntries' <<<"$output"; then + printf '%s\n' "$output" >&2 + return $rc + fi + journalctl --sync || true + done + printf '%s\n' "$output" >&2 + return $rc +} + # ensure the varlink basics work varlinkctl list-interfaces "$VARLINK_SOCKET" | grep io.systemd.JournalAccess varlinkctl introspect "$VARLINK_SOCKET" | grep "method GetEntries(" @@ -16,18 +36,18 @@ systemd-cat -t "$TAG" -p warning echo "varlink-test-warning" journalctl --sync # most basic call works -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq . +varlinkctl_get_entries '{}' | jq --seq . # validate the JSON has some basic properties (similar to journalctls json output) -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq '.entry | {MESSAGE, PRIORITY, _UID}' +varlinkctl_get_entries '{}' | jq --seq '.entry | {MESSAGE, PRIORITY, _UID}' # check that default limit works (100), we don't know how many entries we have so we just check # bounds -ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | wc -l) +ENTRIES=$(varlinkctl_get_entries '{}' | wc -l) test "$ENTRIES" -gt 0 test "$ENTRIES" -le 100 # check explicit limit -ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"limit": 3}' | wc -l) +ENTRIES=$(varlinkctl_get_entries '{"limit": 3}' | wc -l) test "$ENTRIES" -le 3 # check unit filter: use transient units to get deterministic results @@ -38,16 +58,16 @@ systemd-run --unit="$UNIT_NAME_2" --wait bash -c 'echo hello-from-varlink-test-2 journalctl --sync # single unit filter -SINGLE_OUTPUT="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}")" +SINGLE_OUTPUT="$(varlinkctl_get_entries "{\"units\": [\"$UNIT_NAME_1\"]}")" grep "hello-from-varlink-test-1" >/dev/null <<<"$SINGLE_OUTPUT" (! grep "hello-from-varlink-test-2" >/dev/null <<<"$SINGLE_OUTPUT") # multi unit filter -MULTI_OUTPUT="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}")" +MULTI_OUTPUT="$(varlinkctl_get_entries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}")" grep "hello-from-varlink-test-1" >/dev/null <<<"$MULTI_OUTPUT" grep "hello-from-varlink-test-2" >/dev/null <<<"$MULTI_OUTPUT" # check priority filter: priority 4 (warning) should include our warning message -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep "varlink-test-warning" >/dev/null +varlinkctl_get_entries '{"priority": 4, "limit": 1000}' | grep "varlink-test-warning" >/dev/null # check priority filter: priority 3 (error) should NOT include our warning (priority 4) (! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 3, "limit": 1000}' | grep "varlink-test-warning") From cb8359877ecb4b361a4ff74ac839a4a55986560c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 8 May 2026 08:25:10 +0200 Subject: [PATCH 1524/2155] userdbctl: convert to OPTION and VERB macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The situation with --chain is complicated. The old code tried to use "+…" in getopt_long() to stop option parsing. But it didn't actually work. This logic was originally added in 8072a7e6a9eaf2de120797dd16c5e0baea606219. ef9c12b157a50d63e8a8eb710c013d16c2cea319 added an comment about 'optind=0' which explains why the code doesn't work, but the code wasn't changed. To wit: $ userdbctl.old --no-pager --chain ssh-authorized-keys zbyszek -- /bin/echo --asdf --asdf $ userdbctl.old --no-pager --chain ssh-authorized-keys zbyszek /bin/echo -- --asdf --asdf $ userdbctl.old --no-pager --chain ssh-authorized-keys zbyszek /bin/echo --asdf userdbctl.old: unrecognized option '--asdf' (Basically, if "--" is used, it can be anywhere, since getopt_long() doesn't do anything special after --chain and looks for the next option. There were some tests of --chain, but they all used the username as the positional argument, so it wasn't misinterpreted as an option.) This behaviour is preserved in the conversion. --help is generally the same except for expected formatting changes. --json= is moved above between --output= and -j. For some reason it was further down. Co-developed-by: Claude Opus 4.7 --- src/userdb/userdbctl.c | 328 ++++++++++++++++------------------------- 1 file changed, 128 insertions(+), 200 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 75b30215b1da3..60a6ff051a3b8 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1,10 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include "alloc-util.h" +#include "ansi-color.h" #include "bitfield.h" #include "build.h" #include "copy.h" @@ -18,14 +18,16 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" #include "io-util.h" #include "json-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" -#include "pretty-print.h" #include "recurse-dir.h" #include "socket-util.h" #include "string-table.h" @@ -408,6 +410,8 @@ static int table_add_uid_map( return n_added; } +VERB(verb_display_user, "user", "[USER…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "Inspect user"); static int verb_display_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; @@ -751,6 +755,8 @@ static int add_unavailable_gid(Table *table, uid_t start, uid_t end) { return 2; } +VERB(verb_display_group, "group", "[GROUP…]", VERB_ANY, VERB_ANY, 0, + "Inspect group"); static int verb_display_group(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; @@ -952,6 +958,10 @@ static int show_membership(const char *user, const char *group, Table *table) { return 0; } +VERB(verb_display_memberships, "users-in-group", "[GROUP…]", VERB_ANY, VERB_ANY, 0, + "Show users that are members of specified groups"); +VERB(verb_display_memberships, "groups-of-user", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show groups the specified users are members of"); static int verb_display_memberships(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int ret = 0, r; @@ -1048,6 +1058,8 @@ static int verb_display_memberships(int argc, char *argv[], uintptr_t _data, voi return ret; } +VERB(verb_display_services, "services", NULL, VERB_ANY, 1, 0, + "Show enabled database services"); 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; @@ -1111,8 +1123,11 @@ static int verb_display_services(int argc, char *argv[], uintptr_t _data, void * return 0; } +VERB(verb_ssh_authorized_keys, "ssh-authorized-keys", "USER", 2, VERB_ANY, 0, + "Show SSH authorized keys for user"); static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *username; char **chain_invocation; int r; @@ -1121,6 +1136,8 @@ static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, voi if (arg_from_file) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--from-file= not supported when showing SSH authorized keys, refusing."); + username = argv[1]; + if (arg_chain) { /* If --chain is specified, the rest of the command line is the chain command */ @@ -1147,18 +1164,18 @@ static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, voi chain_invocation = NULL; } - r = userdb_by_name(argv[1], /* match= */ NULL, arg_userdb_flags, &ur); + r = userdb_by_name(username, /* match= */ NULL, arg_userdb_flags, &ur); if (r == -ESRCH) - log_error_errno(r, "User %s does not exist.", argv[1]); + log_error_errno(r, "User %s does not exist.", username); else if (r == -EHOSTDOWN) log_error_errno(r, "Selected user database service is not available for this request."); else if (r == -EINVAL) - log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", argv[1]); + log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", username); else if (r < 0) - log_error_errno(r, "Failed to find user %s: %m", argv[1]); + log_error_errno(r, "Failed to find user %s: %m", username); else { if (strv_isempty(ur->ssh_authorized_keys)) - log_debug("User record for %s has no public SSH keys.", argv[1]); + log_debug("User record for %s has no public SSH keys.", username); else STRV_FOREACH(i, ur->ssh_authorized_keys) printf("%s\n", *i); @@ -1494,6 +1511,8 @@ static int load_credential_one( return 0; } +VERB(verb_load_credentials, "load-credentials", NULL, VERB_ANY, 1, 0, + "Write static user/group records from credentials"); static int verb_load_credentials(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -1530,66 +1549,39 @@ static int verb_load_credentials(int argc, char *argv[], uintptr_t _data, void * } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = 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); + pager_open(arg_pager_flags); - r = terminal_urlify_man("userdbctl", "1", &link); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Show user and group information."); + + help_section("Commands"); + r = table_print_or_warn(verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sShow user and group information.%s\n" - "\nCommands:\n" - " user [USER…] Inspect user\n" - " group [GROUP…] Inspect group\n" - " users-in-group [GROUP…] Show users that are members of specified groups\n" - " groups-of-user [USER…] Show groups the specified users are members of\n" - " services Show enabled database services\n" - " ssh-authorized-keys USER Show SSH authorized keys for user\n" - " load-credentials Write static user/group records from credentials\n" - "\nOptions:\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" - " --output=MODE Select output mode (classic, friendly, table, json)\n" - " -j Equivalent to --output=json\n" - " -s --service=SERVICE[:SERVICE…]\n" - " Query the specified service\n" - " --with-nss=BOOL Control whether to include glibc NSS data\n" - " -N Do not synthesize or include glibc NSS data\n" - " (Same as --synthesize=no --with-nss=no)\n" - " --synthesize=BOOL Synthesize root/nobody user\n" - " --with-dropin=BOOL Control whether to include drop-in records\n" - " --with-varlink=BOOL Control whether to talk to services at all\n" - " --multiplexer=BOOL Control whether to use the multiplexer\n" - " --json=pretty|short JSON output mode\n" - " --chain Chain another command\n" - " --uid-min=ID Filter by minimum UID/GID (default 0)\n" - " --uid-max=ID Filter by maximum UID/GID (default 4294967294)\n" - " --uuid=UUID Filter by UUID\n" - " -z --fuzzy Do a fuzzy name search\n" - " --disposition=VALUE Filter by disposition\n" - " -I Equivalent to --disposition=intrinsic\n" - " -S Equivalent to --disposition=system\n" - " -R Equivalent to --disposition=regular\n" - " --boundaries=BOOL Show/hide UID/GID range boundaries in output\n" - " -B Equivalent to --boundaries=no\n" - " -F --from-file=PATH Read JSON record from file\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("userdbctl", "1"); 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_from_file(const char *arg, sd_json_variant **ret) { sd_json_variant *v = NULL; @@ -1615,56 +1607,13 @@ static int parse_from_file(const char *arg, sd_json_variant **ret) { return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_OUTPUT, - ARG_WITH_NSS, - ARG_WITH_DROPIN, - ARG_WITH_VARLINK, - ARG_SYNTHESIZE, - ARG_MULTIPLEXER, - ARG_JSON, - ARG_CHAIN, - ARG_UID_MIN, - ARG_UID_MAX, - ARG_UUID, - ARG_DISPOSITION, - ARG_BOUNDARIES, - }; - - 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 }, - { "output", required_argument, NULL, ARG_OUTPUT }, - { "service", required_argument, NULL, 's' }, - { "with-nss", required_argument, NULL, ARG_WITH_NSS }, - { "with-dropin", required_argument, NULL, ARG_WITH_DROPIN }, - { "with-varlink", required_argument, NULL, ARG_WITH_VARLINK }, - { "synthesize", required_argument, NULL, ARG_SYNTHESIZE }, - { "multiplexer", required_argument, NULL, ARG_MULTIPLEXER }, - { "json", required_argument, NULL, ARG_JSON }, - { "chain", no_argument, NULL, ARG_CHAIN }, - { "uid-min", required_argument, NULL, ARG_UID_MIN }, - { "uid-max", required_argument, NULL, ARG_UID_MAX }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "fuzzy", no_argument, NULL, 'z' }, - { "disposition", required_argument, NULL, ARG_DISPOSITION }, - { "boundaries", required_argument, NULL, ARG_BOUNDARIES }, - { "from-file", required_argument, NULL, 'F' }, - {} - }; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { const char *e; int r; assert(argc >= 0); assert(argv); + assert(remaining_args); /* We are going to update this environment variable with our own, hence let's first read what is already set */ e = getenv("SYSTEMD_ONLY_USERDB"); @@ -1679,122 +1628,137 @@ static int parse_argv(int argc, char *argv[]) { arg_services = l; } - /* 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; - - for (;;) { - int c; - - c = getopt_long(argc, argv, - arg_chain ? "+hjs:NISRzBF:" : "hjs:NISRzBF:", /* When --chain was used disable parsing of further switches */ - options, NULL); - if (c < 0) - break; + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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_OUTPUT: - if (streq(optarg, "help")) + OPTION_LONG("output", "MODE", + "Select output mode (classic, friendly, table, json)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output, Output, _OUTPUT_MAX); - arg_output = output_from_string(optarg); + arg_output = output_from_string(opts.arg); if (arg_output < 0) - return log_error_errno(arg_output, "Invalid --output= mode: %s", optarg); + return log_error_errno(arg_output, "Invalid --output= mode: %s", opts.arg); arg_json_format_flags = arg_output == OUTPUT_JSON ? SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO : SD_JSON_FORMAT_OFF; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; arg_output = sd_json_format_enabled(arg_json_format_flags) ? OUTPUT_JSON : _OUTPUT_INVALID; break; - case 'j': + OPTION_SHORT('j', NULL, "Equivalent to --output=json"): arg_json_format_flags = SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO; arg_output = OUTPUT_JSON; break; - case 's': - if (isempty(optarg)) + OPTION('s', "service", "SERVICE[:SERVICE…]", "Query the specified service"): + if (isempty(opts.arg)) arg_services = strv_free(arg_services); else { - r = strv_split_and_extend(&arg_services, optarg, ":", /* filter_duplicates= */ true); + r = strv_split_and_extend(&arg_services, opts.arg, ":", /* filter_duplicates= */ true); if (r < 0) return log_error_errno(r, "Failed to parse -s/--service= argument: %m"); } break; - case 'N': + OPTION_LONG("with-nss", "BOOL", "Control whether to include glibc NSS data"): + r = parse_boolean_argument("--with-nss=", opts.arg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_NSS, !r); + break; + + OPTION_SHORT('N', NULL, + "Do not synthesize or include glibc NSS data " + "(Same as --synthesize=no --with-nss=no)"): arg_userdb_flags |= USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN; break; - case ARG_WITH_NSS: - r = parse_boolean_argument("--with-nss=", optarg, NULL); + OPTION_LONG("synthesize", "BOOL", "Synthesize root/nobody user"): + r = parse_boolean_argument("--synthesize=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_NSS, !r); + SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r); break; - case ARG_WITH_DROPIN: - r = parse_boolean_argument("--with-dropin=", optarg, NULL); + OPTION_LONG("with-dropin", "BOOL", "Control whether to include drop-in records"): + r = parse_boolean_argument("--with-dropin=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_DROPIN, !r); break; - case ARG_WITH_VARLINK: - r = parse_boolean_argument("--with-varlink=", optarg, NULL); + OPTION_LONG("with-varlink", "BOOL", "Control whether to talk to services at all"): + r = parse_boolean_argument("--with-varlink=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_VARLINK, !r); break; - case ARG_SYNTHESIZE: - r = parse_boolean_argument("--synthesize=", optarg, NULL); + OPTION_LONG("multiplexer", "BOOL", "Control whether to use the multiplexer"): + r = parse_boolean_argument("--multiplexer=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r); + SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r); break; - case ARG_MULTIPLEXER: - r = parse_boolean_argument("--multiplexer=", optarg, NULL); + OPTION_LONG("chain", NULL, "Chain another command"): + arg_chain = true; + break; + + OPTION_LONG("uid-min", "ID", "Filter by minimum UID/GID (default 0)"): + r = parse_uid(opts.arg, &arg_uid_min); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --uid-min= value: %s", opts.arg); + break; - SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r); + OPTION_LONG("uid-max", "ID", "Filter by maximum UID/GID (default 4294967294)"): + r = parse_uid(opts.arg, &arg_uid_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --uid-max= value: %s", opts.arg); break; - case ARG_CHAIN: - arg_chain = true; + OPTION_LONG("uuid", "UUID", "Filter by UUID"): + r = sd_id128_from_string(opts.arg, &arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to parse --uuid= value: %s", opts.arg); + break; + + OPTION('z', "fuzzy", NULL, "Do a fuzzy name search"): + arg_fuzzy = true; break; - case ARG_DISPOSITION: { - UserDisposition d = user_disposition_from_string(optarg); + OPTION_LONG("disposition", "VALUE", "Filter by disposition"): { + UserDisposition d = user_disposition_from_string(opts.arg); if (d < 0) - return log_error_errno(d, "Unknown user disposition: %s", optarg); + return log_error_errno(d, "Unknown user disposition: %s", opts.arg); if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; @@ -1803,80 +1767,54 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'I': + OPTION_SHORT('I', NULL, "Equivalent to --disposition=intrinsic"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_INTRINSIC; break; - case 'S': + OPTION_SHORT('S', NULL, "Equivalent to --disposition=system"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_SYSTEM; break; - case 'R': + OPTION_SHORT('R', NULL, "Equivalent to --disposition=regular"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_REGULAR; break; - case ARG_UID_MIN: - r = parse_uid(optarg, &arg_uid_min); - if (r < 0) - return log_error_errno(r, "Failed to parse --uid-min= value: %s", optarg); - break; - - case ARG_UID_MAX: - r = parse_uid(optarg, &arg_uid_max); - if (r < 0) - return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg); - break; - - case ARG_UUID: - r = sd_id128_from_string(optarg, &arg_uuid); - if (r < 0) - return log_error_errno(r, "Failed to parse --uuid= value: %s", optarg); - break; - - case 'z': - arg_fuzzy = true; - break; - - case ARG_BOUNDARIES: - r = parse_boolean_argument("boundaries", optarg, &arg_boundaries); + OPTION_LONG("boundaries", "BOOL", + "Show/hide UID/GID range boundaries in output"): + r = parse_boolean_argument("boundaries", opts.arg, &arg_boundaries); if (r < 0) return r; break; - case 'B': + OPTION_SHORT('B', NULL, "Equivalent to --boundaries=no"): arg_boundaries = false; break; - case 'F': { + OPTION('F', "from-file", "PATH", "Read JSON record from file"): { sd_json_variant *v = NULL; /* initialization to appease gcc-14 */ - r = parse_from_file(optarg, &v); + r = parse_from_file(opts.arg, &v); if (r < 0) return r; json_variant_unref_and_replace(arg_from_file, v); break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_uid_min > arg_uid_max) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", arg_uid_min, arg_uid_max); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", + arg_uid_min, arg_uid_max); /* If not mask was specified, use the all bits on mask */ if (arg_disposition_mask == UINT64_MAX) @@ -1885,27 +1823,17 @@ static int parse_argv(int argc, char *argv[]) { if (arg_from_file) arg_boundaries = false; + *remaining_args = option_parser_get_args(&opts); return 1; } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "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 }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1923,7 +1851,7 @@ static int run(int argc, char *argv[]) { } else assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 0a5e09404d56081049266124f586c9a47889c063 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Apr 2026 21:18:19 +0200 Subject: [PATCH 1525/2155] curl-util: Make some curl symbols private --- src/shared/curl-util.c | 18 ++++++++++-------- src/shared/curl-util.h | 9 --------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 9254b83dd74fb..0a6bdeefe5589 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -29,17 +29,19 @@ DLSYM_PROTOTYPE(curl_easy_strerror) = NULL; 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; +static DLSYM_PROTOTYPE(curl_multi_add_handle) = NULL; +static DLSYM_PROTOTYPE(curl_multi_assign) = NULL; +static DLSYM_PROTOTYPE(curl_multi_cleanup) = NULL; +static DLSYM_PROTOTYPE(curl_multi_info_read) = NULL; +static DLSYM_PROTOTYPE(curl_multi_init) = NULL; +static DLSYM_PROTOTYPE(curl_multi_remove_handle) = NULL; +static DLSYM_PROTOTYPE(curl_multi_setopt) = NULL; +static DLSYM_PROTOTYPE(curl_multi_socket_action) = NULL; DLSYM_PROTOTYPE(curl_slist_append) = NULL; DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); + static void curl_glue_check_finished(CurlGlue *g) { int r; diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 112649f371ba7..33ab0a5fb204b 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -18,14 +18,6 @@ extern DLSYM_PROTOTYPE(curl_easy_strerror); 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); @@ -66,7 +58,6 @@ 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); #endif From 87cec65cae656f6ac2e702bd60dad6dd4fdae636 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 1 May 2026 09:08:35 +0000 Subject: [PATCH 1526/2155] curl-util: bring CurlGlue/CurlSlot in line with sd-bus and qmp-client Refactor curl-util to use the same per-request, refcounted, cancellable slot model as sd-bus, sd-varlink and qmp-client. CurlGlue becomes opaque and refcounted, and dispatches per-slot completion callbacks through CURLOPT_PRIVATE instead of a single g->on_finished demux that every caller had to switch on. The new curl_glue_perform_async(g, easy, cb, userdata, &slot) replaces curl_glue_add + the on_finished/userdata wiring. CurlSlot is the per-request handle: it owns the easy handle, curl_slot_unref does curl_multi_remove_handle + curl_easy_cleanup (which doubles as cancel since remove aborts in-flight transfers without queuing CURLMSG_DONE), and floating slots (ret_slot=NULL) are kept alive in the glue's slot set until the callback fires. Drop the userdata parameter from curl_glue_make: CURLOPT_PRIVATE is now used internally to route completions to the slot. Migrate pull-job and the pull-{oci,raw,tar} drivers, and imdsd, to the new shape. PullJob.curl becomes PullJob.slot; pull_job_curl_on_finished becomes a per-slot callback. imdsd routes its token-vs-data branch off slot identity rather than easy-handle pointer comparison. Both daemons drop the global on_finished/userdata wiring on the glue. pull_job_finish and context_fail{,_full} now return int (always 0) so the callbacks stay in the `return finish(...);` style. Add test-curl-util covering glue lifecycle, easy-handle defaults, floating and non-floating perform paths, cancel-via-slot-unref (verified by a sentinel request that drives the loop to completion), and three concurrent requests on a single glue. Tests fetch local files via file:// URLs so no network is needed; libcurl availability is probed once via dlopen_curl in intro(). --- src/imds/imdsd.c | 101 ++++++------- src/import/pull-job.c | 162 +++++++++------------ src/import/pull-job.h | 5 +- src/import/pull-oci.c | 3 - src/import/pull-raw.c | 3 - src/import/pull-tar.c | 3 - src/shared/curl-util.c | 176 ++++++++++++++++++++--- src/shared/curl-util.h | 45 ++++-- src/shared/shared-forward.h | 2 + src/test/meson.build | 4 + src/test/test-curl-util.c | 280 ++++++++++++++++++++++++++++++++++++ 11 files changed, 585 insertions(+), 199 deletions(-) create mode 100644 src/test/test-curl-util.c diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index a0c54ad84d7af..9c194c09005a0 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -182,8 +182,8 @@ struct Context { /* 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; + CurlSlot *slot_token; + CurlSlot *slot_data; struct curl_slist *request_header_token, *request_header_data; sd_event_source *retry_source; unsigned n_retry; @@ -247,15 +247,8 @@ static void context_reset_for_refresh(Context *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; - } + c->slot_token = curl_slot_unref(c->slot_token); + c->slot_data = curl_slot_unref(c->slot_data); sym_curl_slist_free_all(c->request_header_token); c->request_header_token = NULL; @@ -325,11 +318,12 @@ static void context_done(Context *c) { c->system_bus = sd_bus_flush_close_unref(c->system_bus); } -static void context_fail_full(Context *c, int r, const char *varlink_error) { +static int context_fail_full(Context *c, int r, const char *varlink_error) { assert(c); assert(r != 0); - /* Called whenever the current retrieval fails asynchronously */ + /* Called whenever the current retrieval fails asynchronously. Returns 0 so callers in + * int-returning paths can `return context_fail_full(...)` directly. */ r = -abs(r); @@ -349,10 +343,11 @@ static void context_fail_full(Context *c, int r, const char *varlink_error) { sd_event_exit(c->event, r); context_reset_full(c); + return 0; } -static void context_fail(Context *c, int r) { - context_fail_full(c, r, /* varlink_error= */ NULL); +static int context_fail(Context *c, int r) { + return context_fail_full(c, r, /* varlink_error= */ NULL); } static void context_success(Context *c) { @@ -898,17 +893,12 @@ static int context_save_data(Context *c) { return 0; } -static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { +static int curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result, void *userdata) { + Context *c = ASSERT_PTR(userdata); int r; - assert(g); - /* Called whenever libcurl did its thing and reports a download being complete or having failed */ - Context *c = NULL; - if (sym_curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**) &c) != CURLE_OK) - return; - switch (result) { case CURLE_OK: /* yay! */ @@ -934,7 +924,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (r < 0) return context_fail(c, r); - return; + return 0; default: return context_fail_full( @@ -951,12 +941,12 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { return context_fail(c, r); if (r == 0) { /* We shall retry */ (void) context_schedule_retry(c); - return; + return 0; } 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) { + if (slot == c->slot_token) { r = context_validate_token_http_status(c, status); if (r < 0) return context_fail(c, r); @@ -975,7 +965,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (r < 0) return context_fail(c, r); - } else if (curl == c->curl_data) { + } else if (slot == c->slot_data) { r = context_validate_data_http_status(c, status); if (r == -ENOENT) @@ -983,7 +973,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (r < 0) return context_fail(c, r); if (r == 0) /* Immediately restarted */ - return; + return 0; context_log(c, LOG_DEBUG, "Data download successful."); @@ -994,6 +984,8 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { context_success(c); } else assert_not_reached(); + + return 0; } static int context_acquire_glue(Context *c) { @@ -1010,9 +1002,6 @@ static int context_acquire_glue(Context *c) { 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; } @@ -1028,13 +1017,13 @@ static size_t data_write_callback(void *contents, size_t size, size_t nmemb, voi (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 + * restart, abort the write here. Note that the curl_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); + r = context_acquire_http_status(c, curl_slot_get_easy(c->slot_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; /* fail the thing, so that curl_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_on_finished() too */ return 0; if (sz > UINT64_MAX - c->data_size || @@ -1103,7 +1092,8 @@ static int context_acquire_data(Context *c) { if (!url) return context_log_oom(c); - r = curl_glue_make(&c->curl_data, url, c); + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, url); if (r < 0) return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for data: %m"); @@ -1122,30 +1112,31 @@ 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 (sym_curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) + if (sym_curl_easy_setopt(easy, 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 (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); - if (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); - if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); - if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); - if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORT, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_LOCALPORT, 1L) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port"); - if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORTRANGE, 1023L) != CURLE_OK) + if (sym_curl_easy_setopt(easy, 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); + r = curl_glue_perform_async(c->glue, easy, curl_on_finished, c, &c->slot_data); if (r < 0) return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + TAKE_PTR(easy); return 0; } @@ -1163,10 +1154,10 @@ static size_t token_write_callback(void *contents, size_t size, size_t nmemb, vo /* Before we use acquired data, let's verify the HTTP status */ long status; - r = context_acquire_http_status(c, c->curl_token, &status); + r = context_acquire_http_status(c, curl_slot_get_easy(c->slot_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; /* fail the thing, so that curl_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_on_finished() */ return 0; if (sz > SIZE_MAX - c->token.iov_len || @@ -1199,7 +1190,8 @@ static int context_acquire_token(Context *c) { if (r < 0) return r; - r = curl_glue_make(&c->curl_token, arg_token_url, c); + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, arg_token_url); if (r < 0) return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for API token: %m"); @@ -1216,27 +1208,28 @@ static int context_acquire_token(Context *c) { return context_log_oom(c); } - if (sym_curl_easy_setopt(c->curl_token, CURLOPT_HTTPHEADER, c->request_header_token) != CURLE_OK) + if (sym_curl_easy_setopt(easy, 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 (sym_curl_easy_setopt(c->curl_token, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request method."); - if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); - if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); - if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); - if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(easy, 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); + r = curl_glue_perform_async(c->glue, easy, curl_on_finished, c, &c->slot_token); if (r < 0) return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + TAKE_PTR(easy); return 0; } diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 4c3fb05dd3533..5b8aa6da26942 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -53,7 +53,7 @@ PullJob* pull_job_unref(PullJob *j) { pull_job_close_disk_fd(j); - curl_glue_remove_and_free(j->glue, j->curl); + curl_slot_unref(j->slot); sym_curl_slist_free_all(j->request_header); j->compress = compressor_free(j->compress); @@ -83,11 +83,13 @@ static const char* pull_job_description(PullJob *j) { return j->description ?: j->url; } -static void pull_job_finish(PullJob *j, int ret) { +static int pull_job_finish(PullJob *j, int ret) { assert(j); + /* Returns 0 so callers in int-returning paths can `return pull_job_finish(...)` directly. */ + if (IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) - return; + return 0; if (ret == 0) { j->state = PULL_JOB_DONE; @@ -100,6 +102,8 @@ static void pull_job_finish(PullJob *j, int ret) { if (j->on_finished) j->on_finished(j); + + return 0; } int pull_job_restart(PullJob *j, const char *new_url) { @@ -134,8 +138,7 @@ int pull_job_restart(PullJob *j, const char *new_url) { j->expected_content_length = UINT64_MAX; } - curl_glue_remove_and_free(j->glue, j->curl); - j->curl = NULL; + j->slot = curl_slot_unref(j->slot); j->compress = compressor_free(j->compress); @@ -160,23 +163,18 @@ static uint64_t pull_job_content_length_effective(PullJob *j) { return j->content_length; } -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { - PullJob *j = NULL; +static int pull_job_curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result, void *userdata) { + PullJob *j = ASSERT_PTR(userdata); char *scheme = NULL; CURLcode code; int r; - 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; + if (IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) + return 0; 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; - } + if (code != CURLE_OK || !scheme) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve URL scheme.")); if (strcaseeq(scheme, "FILE") && result == CURLE_FILE_COULDNT_READ_FILE && j->on_not_found) { _cleanup_free_ char *new_url = NULL; @@ -184,43 +182,37 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ r = j->on_not_found(j, &new_url); if (r < 0) - goto finish; + return pull_job_finish(j, r); if (r > 0) { /* A new url to use */ assert(new_url); r = pull_job_restart(j, new_url); if (r < 0) - goto finish; + return pull_job_finish(j, r); - return; + return 0; } /* if this didn't work, handle like any other error below */ } - if (result != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", sym_curl_easy_strerror(result)); - goto finish; - } + if (result != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", sym_curl_easy_strerror(result))); if (STRCASE_IN_SET(scheme, "HTTP", "HTTPS")) { long 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", sym_curl_easy_strerror(code)); - goto finish; - } + if (code != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code))); if (http_status_etag_exists(status)) { log_info("Image already downloaded. Skipping download."); j->etag_exists = true; - r = 0; - goto finish; + return pull_job_finish(j, 0); } else if (http_status_need_authentication(status)) { log_info("Access to image requires authentication."); - r = -ENOKEY; - goto finish; + return pull_job_finish(j, -ENOKEY); } else if (status >= 300) { if (status == 404 && j->on_not_found) { @@ -229,81 +221,64 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ r = j->on_not_found(j, &new_url); if (r < 0) - goto finish; + return pull_job_finish(j, r); if (r > 0) { /* A new url to use */ assert(new_url); r = pull_job_restart(j, new_url); if (r < 0) - goto finish; + return pull_job_finish(j, r); - 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", sym_curl_easy_strerror(code)); - goto finish; - } + code = sym_curl_easy_getinfo(curl_slot_get_easy(j->slot), CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code))); if (status == 0) - return; + return 0; } } - r = log_notice_errno( + return pull_job_finish(j, log_notice_errno( status == 404 ? SYNTHETIC_ERRNO(ENOMEDIUM) : SYNTHETIC_ERRNO(EIO), /* Make the most common error recognizable */ - "HTTP request to %s failed with code %li.", j->url, status); - goto finish; - } else if (status < 200) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request to %s finished with unexpected code %li.", j->url, status); - goto finish; - } + "HTTP request to %s failed with code %li.", j->url, status)); + } else if (status < 200) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request to %s finished with unexpected code %li.", j->url, status)); } - if (j->state != PULL_JOB_RUNNING) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Premature connection termination."); - goto finish; - } + if (j->state != PULL_JOB_RUNNING) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Premature connection termination.")); uint64_t cl = pull_job_content_length_effective(j); if (cl != UINT64_MAX && - cl != j->written_compressed) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Download truncated."); - goto finish; - } + cl != j->written_compressed) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Download truncated.")); if (j->checksum_ctx) { unsigned checksum_len; iovec_done(&j->checksum); j->checksum.iov_base = malloc(EVP_MAX_MD_SIZE); - if (!j->checksum.iov_base) { - r = log_oom(); - goto finish; - } + if (!j->checksum.iov_base) + return pull_job_finish(j, log_oom()); 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; - } + if (r == 0) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum.")); assert(checksum_len <= EVP_MAX_MD_SIZE); j->checksum.iov_len = checksum_len; if (DEBUG_LOGGING) { _cleanup_free_ char *h = hexmem(j->checksum.iov_base, j->checksum.iov_len); - if (!h) { - r = log_oom(); - goto finish; - } + if (!h) + return pull_job_finish(j, log_oom()); 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) && - !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; - } + !iovec_equal(&j->checksum, &j->expected_checksum)) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes.")); } /* Do a couple of finishing disk operations, but only if we are the sole owner of the file (i.e. no @@ -318,10 +293,8 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (j->written_compressed > 0) { /* Make sure the file size is right, in case the file was sparse and * we just moved to the last part. */ - if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) { - r = log_error_errno(errno, "Failed to truncate file: %m"); - goto finish; - } + if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) + return pull_job_finish(j, log_error_errno(errno, "Failed to truncate file: %m")); } if (j->etag) @@ -345,27 +318,20 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (j->sync) { r = fsync_full(j->disk_fd); - if (r < 0) { - log_error_errno(r, "Failed to synchronize file to disk: %m"); - goto finish; - } + if (r < 0) + return pull_job_finish(j, log_error_errno(r, "Failed to synchronize file to disk: %m")); } } else if (S_ISBLK(j->disk_stat.st_mode) && j->sync) { - if (fsync(j->disk_fd) < 0) { - r = log_error_errno(errno, "Failed to synchronize block device: %m"); - goto finish; - } + if (fsync(j->disk_fd) < 0) + return pull_job_finish(j, log_error_errno(errno, "Failed to synchronize block device: %m")); } } log_info("Acquired %s for %s.", FORMAT_BYTES(j->written_uncompressed), pull_job_description(j)); - r = 0; - -finish: - pull_job_finish(j, r); + return pull_job_finish(j, 0); } static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata) { @@ -595,7 +561,7 @@ static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb assert(j->state == PULL_JOB_ANALYZING); - code = sym_curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(curl_slot_get_easy(j->slot), CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto fail; @@ -809,7 +775,8 @@ int pull_job_begin(PullJob *j) { if (j->state != PULL_JOB_INIT) return -EBUSY; - r = curl_glue_make(&j->curl, j->url, j); + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, j->url); if (r < 0) return r; @@ -830,34 +797,35 @@ int pull_job_begin(PullJob *j) { } if (j->request_header) { - if (sym_curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) return -EIO; } - if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_WRITEDATA, j) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_HEADERDATA, j) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_XFERINFODATA, j) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) return -EIO; - r = curl_glue_add(j->glue, j->curl); + r = curl_glue_perform_async(j->glue, easy, pull_job_curl_on_finished, j, &j->slot); if (r < 0) return r; + TAKE_PTR(easy); j->state = PULL_JOB_ANALYZING; diff --git a/src/import/pull-job.h b/src/import/pull-job.h index 0b878292f096b..00d001680ff20 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -7,7 +7,6 @@ #include "shared-forward.h" -typedef struct CurlGlue CurlGlue; typedef struct PullJob PullJob; typedef void (*PullJobFinished)(PullJob *job); @@ -46,7 +45,7 @@ typedef struct PullJob { PullJobNotFound on_not_found; CurlGlue *glue; - CURL *curl; + CurlSlot *slot; struct curl_slist *request_header; char *etag; @@ -95,8 +94,6 @@ PullJob* pull_job_unref(PullJob *job); int pull_job_begin(PullJob *j); -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result); - void pull_job_close_disk_fd(PullJob *j); int pull_job_add_request_header(PullJob *j, const char *hdr); diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index acea93b09de9a..c1c76fc898017 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -195,9 +195,6 @@ int oci_pull_new( .userns_fd = -EBADF, }; - i->glue->on_finished = pull_job_curl_on_finished; - i->glue->userdata = i; - *ret = TAKE_PTR(i); return 0; diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 0ddde7c091962..c63a453177cff 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -149,9 +149,6 @@ int raw_pull_new( .offset = UINT64_MAX, }; - p->glue->on_finished = pull_job_curl_on_finished; - p->glue->userdata = p; - *ret = TAKE_PTR(p); return 0; diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index fe18636eb7d9d..453ad1187cf7b 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -153,9 +153,6 @@ int tar_pull_new( .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, }; - p->glue->on_finished = pull_job_curl_on_finished; - p->glue->userdata = p; - *ret = TAKE_PTR(p); return 0; diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 0a6bdeefe5589..e438ddf61a4d2 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -12,6 +12,7 @@ #include "dlfcn-util.h" #include "fd-util.h" #include "hashmap.h" +#include "set.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -42,6 +43,77 @@ DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); +struct CurlGlue { + unsigned n_ref; + sd_event *event; + CURLM *curl; + sd_event_source *timer; + Hashmap *ios; + sd_event_source *defer; + Set *slots; /* CurlSlot* — back-pointer set; floating slots are kept alive here */ +}; + +struct CurlSlot { + unsigned n_ref; + CurlGlue *glue; /* NULL once disconnected (callback fired, cancelled, or glue died) */ + CURL *easy; /* owned; cleared once the easy handle has been freed */ + bool floating; + curl_finished_t callback; + void *userdata; +}; + +static void curl_slot_disconnect(CurlSlot *slot, bool unref) { + assert(slot); + + /* Tear down the slot's connection to the glue: pull the easy handle out of the multi, + * curl_easy_cleanup() it, and remove the slot from the glue's lookup set. Floating + * slots are owned by that set, so on disconnect we drop the implicit ref (when + * unref=true; the recursive call from curl_slot_free passes false to avoid infinite + * recursion). Non-floating slots release the back-ref they held on the glue. + * + * Idempotent: once slot->glue is NULL, subsequent calls are no-ops. */ + + if (!slot->glue) + return; + + CurlGlue *glue = slot->glue; + + if (slot->easy) { + if (glue->curl) + (void) sym_curl_multi_remove_handle(glue->curl, slot->easy); + sym_curl_easy_cleanup(slot->easy); + slot->easy = NULL; + } + + set_remove(glue->slots, slot); + slot->glue = NULL; + + if (!slot->floating) + curl_glue_unref(glue); + else if (unref) + curl_slot_unref(slot); +} + +static CurlSlot* curl_slot_free(CurlSlot *slot) { + if (!slot) + return NULL; + + curl_slot_disconnect(slot, /* unref= */ false); + return mfree(slot); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(CurlSlot, curl_slot, curl_slot_free); + +CURL* curl_slot_get_easy(CurlSlot *slot) { + assert(slot); + return slot->easy; +} + +CurlGlue* curl_slot_get_glue(CurlSlot *slot) { + assert(slot); + return slot->glue; +} + static void curl_glue_check_finished(CurlGlue *g) { int r; @@ -60,8 +132,27 @@ static void curl_glue_check_finished(CurlGlue *g) { if (!msg) return; - if (msg->msg == CURLMSG_DONE && g->on_finished) - g->on_finished(g, msg->easy_handle, msg->data.result); + if (msg->msg == CURLMSG_DONE) { + CURL *easy = msg->easy_handle; + CURLcode code = msg->data.result; + CurlSlot *slot = NULL; + + if (sym_curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char**) &slot) == CURLE_OK && slot) { + /* Pin the slot across the callback: a floating slot's only + * reference is the one held via the glue's slots set, and + * disconnect drops it. */ + curl_slot_ref(slot); + + if (slot->callback) { + r = slot->callback(slot, easy, code, slot->userdata); + if (r < 0) + log_debug_errno(r, "Curl finished callback returned error, ignoring: %m"); + } + + curl_slot_disconnect(slot, /* unref= */ true); + curl_slot_unref(slot); + } + } /* This is a queue, process another item soon, but do so in a later event loop iteration. */ (void) sd_event_source_set_enabled(g->defer, SD_EVENT_ONESHOT); @@ -212,12 +303,22 @@ static int curl_glue_on_defer(sd_event_source *s, void *userdata) { return 0; } -CurlGlue *curl_glue_unref(CurlGlue *g) { +static CurlGlue* curl_glue_free(CurlGlue *g) { sd_event_source *io; + CurlSlot *slot; if (!g) return NULL; + /* Drain any slots still hanging off us. By construction only floating slots can + * be here: connected non-floating slots hold a glue back-ref, so glue's last ref + * couldn't have dropped while one was attached. disconnect(unref=true) does the + * floating slot's free as part of its work. set_steal_first() pops up front so + * forward progress doesn't depend on disconnect's internal set_remove(). */ + while ((slot = set_steal_first(g->slots))) + curl_slot_disconnect(slot, /* unref= */ true); + g->slots = set_free(g->slots); + if (g->curl) sym_curl_multi_cleanup(g->curl); @@ -232,6 +333,8 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { return mfree(g); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(CurlGlue, curl_glue, curl_glue_free); + int curl_glue_new(CurlGlue **glue, sd_event *event) { _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; _cleanup_(curl_multi_cleanupp) CURLM *c = NULL; @@ -261,6 +364,7 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { return -ENOMEM; *g = (CurlGlue) { + .n_ref = 1, .event = TAKE_PTR(e), .curl = TAKE_PTR(c), }; @@ -288,7 +392,7 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { return 0; } -int curl_glue_make(CURL **ret, const char *url, void *userdata) { +int curl_glue_make(CURL **ret, const char *url) { _cleanup_(curl_easy_cleanupp) CURL *c = NULL; const char *useragent; int r; @@ -310,9 +414,6 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) { if (sym_curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) return -EIO; - if (sym_curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) - return -EIO; - useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION); if (sym_curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) return -EIO; @@ -342,26 +443,61 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) { return 0; } -int curl_glue_add(CurlGlue *g, CURL *c) { +int curl_glue_perform_async( + CurlGlue *g, + CURL *easy, + curl_finished_t cb, + void *userdata, + CurlSlot **ret_slot) { + + int r; + assert(g); - assert(c); + assert(easy); - if (sym_curl_multi_add_handle(g->curl, c) != CURLM_OK) - return -EIO; + _cleanup_(curl_slot_unrefp) CurlSlot *slot = new(CurlSlot, 1); + if (!slot) + return -ENOMEM; - return 0; -} + *slot = (CurlSlot) { + .n_ref = 1, + .glue = NULL, /* wired up below, after we've committed to the multi */ + .easy = easy, + .floating = !ret_slot, + .callback = cb, + .userdata = userdata, + }; -void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { - assert(g); + r = set_ensure_put(&g->slots, &trivial_hash_ops, slot); + if (r < 0) + return r; + assert(r > 0); - if (!c) - return; + if (sym_curl_multi_add_handle(g->curl, easy) != CURLM_OK) { + set_remove(g->slots, slot); + return -EIO; + } - if (g->curl) - sym_curl_multi_remove_handle(g->curl, c); + /* Stash the slot pointer on the easy handle so curl_glue_check_finished() can recover + * it on completion. Set this only after we've fully committed to the multi, so that + * error paths above don't leave a dangling pointer on the easy handle. */ + if (sym_curl_easy_setopt(easy, CURLOPT_PRIVATE, slot) != CURLE_OK) { + sym_curl_multi_remove_handle(g->curl, easy); + set_remove(g->slots, slot); + return -EIO; + } + + slot->glue = g; + if (!slot->floating) + curl_glue_ref(g); - sym_curl_easy_cleanup(c); + /* Transfer the slot's single reference: to the caller for non-floating slots, or to + * the glue's slot set (implicitly, until disconnect drops it) for floating ones. */ + if (ret_slot) + *ret_slot = slot; + + TAKE_PTR(slot); + return 0; } struct curl_slist *curl_slist_new(const char *first, ...) { diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 33ab0a5fb204b..3436188952fbc 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -30,27 +30,42 @@ extern DLSYM_PROTOTYPE(curl_slist_free_all); code == CURLE_OK; \ }) -typedef struct CurlGlue CurlGlue; - -typedef struct CurlGlue { - sd_event *event; - CURLM *curl; - sd_event_source *timer; - Hashmap *ios; - sd_event_source *defer; - - void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); - void *userdata; -} CurlGlue; +typedef int (*curl_finished_t)(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata); int curl_glue_new(CurlGlue **glue, sd_event *event); +CurlGlue* curl_glue_ref(CurlGlue *glue); CurlGlue* curl_glue_unref(CurlGlue *glue); DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); -int curl_glue_make(CURL **ret, const char *url, void *userdata); -int curl_glue_add(CurlGlue *g, CURL *c); -void curl_glue_remove_and_free(CurlGlue *g, CURL *c); +/* Build a CURL easy handle with sane defaults. The caller configures any + * additional options (headers, write callbacks, …) before handing it off to + * curl_glue_perform_async(). */ +int curl_glue_make(CURL **ret, const char *url); + +/* Hand a configured CURL easy handle off to the multi for execution. The slot + * takes ownership of the easy handle: once the slot is released (the callback + * has fired, the caller has dropped its last ref, or the glue is being freed), + * the handle is removed from the multi and freed. + * + * If ret_slot is NULL the slot is allocated as floating: the glue keeps it + * alive until the callback fires or the glue is torn down. Otherwise a + * reference is returned to the caller; releasing that reference cancels the + * call. */ +int curl_glue_perform_async( + CurlGlue *g, + CURL *easy, + curl_finished_t cb, + void *userdata, + CurlSlot **ret_slot); + +CURL* curl_slot_get_easy(CurlSlot *slot); +CurlGlue* curl_slot_get_glue(CurlSlot *slot); + +CurlSlot* curl_slot_ref(CurlSlot *slot); +CurlSlot* curl_slot_unref(CurlSlot *slot); + +DEFINE_TRIVIAL_CLEANUP_FUNC(CurlSlot*, curl_slot_unref); 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); diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index e850d8982bd30..751a6f71dc359 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -57,6 +57,8 @@ typedef struct Condition Condition; typedef struct ConfigSection ConfigSection; typedef struct ConfigTableItem ConfigTableItem; typedef struct CPUSet CPUSet; +typedef struct CurlGlue CurlGlue; +typedef struct CurlSlot CurlSlot; typedef struct DissectedImage DissectedImage; typedef struct DnsAnswer DnsAnswer; typedef struct DnsPacket DnsPacket; diff --git a/src/test/meson.build b/src/test/meson.build index f4288119f94ba..ba890e7341017 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -348,6 +348,10 @@ executables += [ 'sources' : files('test-kexec.c'), 'link_with' : [libshared], }, + test_template + { + 'sources' : files('test-curl-util.c'), + 'conditions' : ['HAVE_LIBCURL'], + }, test_template + { 'sources' : files('test-libcrypt-util.c'), 'conditions' : ['HAVE_LIBCRYPT'], diff --git a/src/test/test-curl-util.c b/src/test/test-curl-util.c new file mode 100644 index 0000000000000..fb3d278200671 --- /dev/null +++ b/src/test/test-curl-util.c @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "curl-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +#define ASSERT_CURL_OK(expr) \ + ({ \ + CURLcode _code = (expr); \ + if (_code != CURLE_OK) \ + log_test_failed("Expected \"%s\" to be CURLE_OK, but got %d/%s",\ + #expr, (int) _code, sym_curl_easy_strerror(_code)); \ + }) + +/* Per-request context: the write callback appends bytes to ->body, and the + * on_finished callback stashes the CURLcode plus a "fired" flag. Each test + * uses one or more of these and cleans them up via context_done(). */ +typedef struct Context { + sd_event *event; + char *body; + size_t body_len; + bool finished; + CURLcode result; +} Context; + +static void context_done(Context *f) { + f->event = sd_event_unref(f->event); + f->body = mfree(f->body); +} + +static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *f = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + + if (!GREEDY_REALLOC(f->body, f->body_len + sz + 1)) + return 0; + memcpy(f->body + f->body_len, contents, sz); + f->body[f->body_len + sz] = 0; + f->body_len += sz; + return sz; +} + +static int on_finished(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata) { + Context *f = ASSERT_PTR(userdata); + + f->finished = true; + f->result = code; + + return sd_event_exit(f->event, 0); +} + +static int make_tmp_url(char **ret_path, char **ret_url, const char *body) { + const char *t; + ASSERT_OK(tmp_dir(&t)); + + _cleanup_(unlink_and_freep) char *path = ASSERT_NOT_NULL(strjoin(t, "/test-curl-util.XXXXXX")); + + _cleanup_close_ int fd = ASSERT_OK(mkostemp_safe(path)); + ASSERT_OK(loop_write(fd, body, strlen(body))); + + char *url = ASSERT_NOT_NULL(strjoin("file://", path)); + + *ret_url = url; + *ret_path = TAKE_PTR(path); + return 0; +} + +static int build_easy(const char *url, Context *f, CURL **ret) { + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(curl_glue_make(&easy, url)); + + ASSERT_CURL_OK(sym_curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_callback)); + ASSERT_CURL_OK(sym_curl_easy_setopt(easy, CURLOPT_WRITEDATA, f)); + + *ret = TAKE_PTR(easy); + return 0; +} + +TEST(curl_glue_lifecycle) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + /* ref/unref roundtrip */ + ASSERT_PTR_EQ(curl_glue_ref(g), g); + ASSERT_NULL(curl_glue_unref(g)); +} + +TEST(curl_glue_make) { + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(curl_glue_make(&easy, "file:///dev/null")); + ASSERT_NOT_NULL(easy); +} + +TEST(curl_perform_floating) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "hello world")); + + _cleanup_(context_done) Context f = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(build_easy(url, &f, &easy)); + + /* Floating: pass NULL for ret_slot. The glue owns the slot until completion. */ + ASSERT_OK(curl_glue_perform_async(g, easy, on_finished, &f, /* ret_slot= */ NULL)); + TAKE_PTR(easy); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(f.finished); + ASSERT_CURL_OK(f.result); + ASSERT_STREQ(f.body, "hello world"); +} + +TEST(curl_perform_slot) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "slot test")); + + _cleanup_(context_done) Context f = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(build_easy(url, &f, &easy)); + + _cleanup_(curl_slot_unrefp) CurlSlot *slot = NULL; + ASSERT_OK(curl_glue_perform_async(g, easy, on_finished, &f, &slot)); + TAKE_PTR(easy); + + ASSERT_NOT_NULL(slot); + ASSERT_NOT_NULL(curl_slot_get_easy(slot)); + ASSERT_PTR_EQ(curl_slot_get_glue(slot), g); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(f.finished); + ASSERT_CURL_OK(f.result); + ASSERT_STREQ(f.body, "slot test"); + + /* After completion, disconnect has cleared the slot's back-pointers; the slot itself + * is still alive because we hold a ref. Releasing it must be a clean no-op. */ + ASSERT_NULL(curl_slot_get_easy(slot)); + ASSERT_NULL(curl_slot_get_glue(slot)); +} + +TEST(curl_perform_cancel) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "payload")); + + /* Two requests: cancelled is unref'd before we run the loop; sentinel runs to + * completion and exits the loop. After the loop returns we know the dispatcher had + * an opportunity to fire any pending completion — so cancelled.finished staying false + * means our cancel actually prevented the callback from running, not just outraced it. */ + _cleanup_(context_done) Context cancelled = { .event = sd_event_ref(event) }; + _cleanup_(context_done) Context sentinel = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy_cancelled = NULL, *easy_sentinel = NULL; + ASSERT_OK(build_easy(url, &cancelled, &easy_cancelled)); + ASSERT_OK(build_easy(url, &sentinel, &easy_sentinel)); + + _cleanup_(curl_slot_unrefp) CurlSlot *slot = NULL; + ASSERT_OK(curl_glue_perform_async(g, easy_cancelled, on_finished, &cancelled, &slot)); + TAKE_PTR(easy_cancelled); + + /* Cancel by dropping our only reference: removes the easy handle from the multi and + * cleans it up. The callback must not fire afterwards. */ + slot = curl_slot_unref(slot); + + /* The sentinel runs as floating; its callback will exit the loop on completion. */ + ASSERT_OK(curl_glue_perform_async(g, easy_sentinel, on_finished, &sentinel, /* ret_slot= */ NULL)); + TAKE_PTR(easy_sentinel); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(sentinel.finished); + ASSERT_FALSE(cancelled.finished); +} + +typedef struct ConcurrentReq { + Context ctx; + const char *expected; + unsigned *remaining; +} ConcurrentReq; + +static int concurrent_on_finished(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata) { + ConcurrentReq *cr = ASSERT_PTR(userdata); + + cr->ctx.finished = true; + cr->ctx.result = code; + + (*cr->remaining)--; + if (*cr->remaining == 0) + return sd_event_exit(cr->ctx.event, 0); + return 0; +} + +TEST(curl_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path_a = NULL, *path_b = NULL, *path_c = NULL; + _cleanup_free_ char *url_a = NULL, *url_b = NULL, *url_c = NULL; + ASSERT_OK(make_tmp_url(&path_a, &url_a, "alpha")); + ASSERT_OK(make_tmp_url(&path_b, &url_b, "bravo")); + ASSERT_OK(make_tmp_url(&path_c, &url_c, "charlie")); + + unsigned remaining = 3; + ConcurrentReq reqs[3] = { + { .ctx = { .event = sd_event_ref(event) }, .expected = "alpha", .remaining = &remaining }, + { .ctx = { .event = sd_event_ref(event) }, .expected = "bravo", .remaining = &remaining }, + { .ctx = { .event = sd_event_ref(event) }, .expected = "charlie", .remaining = &remaining }, + }; + + _cleanup_(curl_easy_cleanupp) CURL *ea = NULL, *eb = NULL, *ec = NULL; + ASSERT_OK(build_easy(url_a, &reqs[0].ctx, &ea)); + ASSERT_OK(build_easy(url_b, &reqs[1].ctx, &eb)); + ASSERT_OK(build_easy(url_c, &reqs[2].ctx, &ec)); + + /* All three fire as floating slots; the only way the loop exits is through the + * remaining-counter hitting zero, which means every callback fired with the right + * userdata routed to its respective body. */ + ASSERT_OK(curl_glue_perform_async(g, ea, concurrent_on_finished, &reqs[0], NULL)); + TAKE_PTR(ea); + ASSERT_OK(curl_glue_perform_async(g, eb, concurrent_on_finished, &reqs[1], NULL)); + TAKE_PTR(eb); + ASSERT_OK(curl_glue_perform_async(g, ec, concurrent_on_finished, &reqs[2], NULL)); + TAKE_PTR(ec); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_EQ(remaining, 0u); + + FOREACH_ARRAY(r, reqs, ELEMENTSOF(reqs)) { + ASSERT_TRUE(r->ctx.finished); + ASSERT_CURL_OK(r->ctx.result); + ASSERT_STREQ(r->ctx.body, r->expected); + context_done(&r->ctx); + } +} + +static int intro(void) { + if (dlopen_curl(LOG_DEBUG) < 0) + return log_tests_skipped("libcurl not available"); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); From b2c6cc6a8e0c11bb59b46999ab4c865dd448ccba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 8 May 2026 15:47:18 +0200 Subject: [PATCH 1527/2155] userdbctl: actually implement option parsing stop after --chain The basic idea is that --chain should stop option parsing. But previously this didn't work, so --chain could be specified anywhere in the command line. To maintain with compatibility with that, allow --chain to be specified anywhere until the first positional arg or option in the command string. This allows options to be passed in the expected fashion: userdbctl --chain ssh-authorized-keys user cmd --opt1 --opt2 userdbctl --chain ssh-authorized-keys user -- cmd --opt1 --opt2 but also allows the invocations which worked previously: userdbctl ssh-authorized-keys user --chain cmd userdbctl ssh-authorized-keys user cmd --chain Fixes 8072a7e6a9eaf2de120797dd16c5e0baea606219. The error messages are extended a bit. "binary path" is misleading: we support all kinds of executables, not only compiled programs. --- src/userdb/userdbctl.c | 34 ++++++++++++++++++++++++++++------ test/units/TEST-46-HOMED.sh | 9 +++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 60a6ff051a3b8..35a17c50217da 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1148,11 +1148,13 @@ static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, voi /* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */ if (!path_is_absolute(argv[2])) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument."); + "Chain invocation of ssh-authorized-keys commands requires an absolute program path (got '%s').", + argv[2]); if (!path_is_normalized(argv[2])) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument."); + "Chain invocation of ssh-authorized-keys commands requires a normalized program path (got '%s').", + argv[2]); chain_invocation = argv + 2; } else { @@ -1628,9 +1630,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_services = l; } - OptionParser opts = { argc, argv }; + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + _cleanup_strv_free_ char **args = NULL; - FOREACH_OPTION_OR_RETURN(c, &opts) + FOREACH_OPTION_OR_RETURN(c, &opts) { switch (c) { OPTION_COMMON_HELP: @@ -1733,6 +1736,12 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_chain = true; break; + OPTION_POSITIONAL: + r = strv_extend(&args, opts.arg); + if (r < 0) + return log_oom(); + break; + OPTION_LONG("uid-min", "ID", "Filter by minimum UID/GID (default 0)"): r = parse_uid(opts.arg, &arg_uid_min); if (r < 0) @@ -1811,6 +1820,15 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { } } + /* When --chain was seen, stop parsing switches after the second positional argument: + * [OPTS0…, VERB, OPTS1…, USERNAME, OPTS2…, COMMAND, OPTS3…] + * We shall parse OPTS0, OPTS1, OPTS2, but OPTS3 are for COMMAND. + * --chain can be anywhere in OPTS0, OPTS1, OPTS2, or first in OPTS3. + */ + if (arg_chain && strv_length(args) >= 3) + opts.state = OPTION_PARSER_DONE; + } + if (arg_uid_min > arg_uid_max) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", @@ -1823,12 +1841,16 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { if (arg_from_file) arg_boundaries = false; - *remaining_args = option_parser_get_args(&opts); + /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */ + if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0) + return log_oom(); + + *remaining_args = TAKE_PTR(args); return 1; } static int run(int argc, char *argv[]) { - char **args = NULL; + _cleanup_strv_free_ char **args = NULL; int r; log_setup(); diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 4b81799ef3dea..5afa42d73968e 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -586,6 +586,15 @@ EOF (! userdbctl ssh-authorized-keys dropin-user --chain '') (! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /usr/bin/false) + # Check that invocations with --chain work as expected + userdbctl ssh-authorized-keys --chain dropin-user /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys dropin-user --chain /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys dropin-user /bin/echo --chain --asdf | grep -e --asdf + userdbctl ssh-authorized-keys --chain dropin-user -- /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys --chain -- dropin-user /bin/echo --asdf | grep -e --asdf + userdbctl --chain -- ssh-authorized-keys dropin-user /bin/echo --asdf | grep -e --asdf + (! userdbctl --chain -- ssh-authorized-keys dropin-user -- /bin/echo --asdf) + (! userdbctl '') for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do (! userdbctl "--$opt=''") From 480449db851f1ec8875ccbf992357d418dd75779 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 7 May 2026 12:59:18 +0200 Subject: [PATCH 1528/2155] machine: require normalized paths in Copy* D-Bus/Varlink methods Also, do the same for the Varlink BindMount method, since its D-Bus counterpart already does it. --- src/machine/machine-dbus.c | 8 ++++---- src/machine/machine-varlink.c | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 28f64b3c9b683..624ec4848ae08 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -593,13 +593,13 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro copy_flags |= COPY_REPLACE; } - if (!path_is_absolute(src)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); + if (!path_is_absolute(src) || !path_is_normalized(src)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized."); if (isempty(dest)) dest = src; - else if (!path_is_absolute(dest)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); + else if (!path_is_absolute(dest) || !path_is_normalized(dest)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized."); if (manager->runtime_scope != RUNTIME_SCOPE_USER) { const char *details[] = { diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index fcdeeb7ae8b10..d9524c75e1747 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -821,10 +821,10 @@ 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(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 }, - { "mkdir", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, mkdir), 0 }, + { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, src), SD_JSON_MANDATORY|SD_JSON_STRICT }, + { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, dest), SD_JSON_STRICT }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, read_only), 0 }, + { "mkdir", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, mkdir), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -844,7 +844,7 @@ int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varli if (r != 0) return r; - /* There is no need for extra validation since json_dispatch_const_path() does path_is_valid() and path_is_absolute(). */ + /* There is no need for extra validation since json_dispatch_const_path() with SD_JSON_STRICT does path_is_normalized() and path_is_absolute(). */ const char *dest = p.dest ?: p.src; Machine *machine; @@ -931,9 +931,9 @@ static int copy_done(Operation *operation, int ret, sd_bus_error *error) { int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, bool copy_from) { static const sd_json_dispatch_field dispatch_table[] = { VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineCopyParameters), - { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, src), SD_JSON_MANDATORY }, - { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, dest), 0 }, - { "replace", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineCopyParameters, replace), 0 }, + { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, src), SD_JSON_MANDATORY|SD_JSON_STRICT }, + { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, dest), SD_JSON_STRICT }, + { "replace", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineCopyParameters, replace), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -954,7 +954,7 @@ int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_va if (r != 0) return r; - /* There is no need for extra validation since json_dispatch_const_path() does path_is_valid() and path_is_absolute(). */ + /* There is no need for extra validation since json_dispatch_const_path() with SD_JSON_STRICT does path_is_normalized() and path_is_absolute(). */ const char *dest = p.dest ?: p.src; const char *container_path = copy_from ? p.src : dest; const char *host_path = copy_from ? dest : p.src; From d0c912899a33436d6676b2564eb1ac506f378571 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 02:16:51 -0700 Subject: [PATCH 1529/2155] test: add missing varlink IDL enum tests for Job and ServiceType PR #41583 (io.systemd.Unit.StartTransient) introduced several new varlink IDL enum types without corresponding enum consistency tests: - JobType, JobState, JobResult in the new io.systemd.Job interface - ServiceType in the Unit interface's ServiceContext Add a new test-varlink-idl-job test file covering all three Job enums, and add ServiceType coverage to the existing test-varlink-idl-unit test. Export vl_type_ServiceType (was static) so it can be referenced from the test. Co-developed-by: Claude Opus 4.6 --- src/shared/varlink-io.systemd.Unit.c | 2 +- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/meson.build | 5 ++++- src/test/test-varlink-idl-job.c | 14 ++++++++++++++ src/test/test-varlink-idl-unit.c | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/test/test-varlink-idl-job.c diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 2b1f0f2b1058b..be5c942fcc717 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -976,7 +976,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Service-specific types */ /* Keep in sync with service_type_table[] in src/core/service.c */ -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( ServiceType, SD_VARLINK_DEFINE_ENUM_VALUE(simple), SD_VARLINK_DEFINE_ENUM_VALUE(exec), diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index a39407133844c..f12ac60701ee6 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -32,3 +32,4 @@ 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; +extern const sd_varlink_symbol vl_type_ServiceType; diff --git a/src/test/meson.build b/src/test/meson.build index f4288119f94ba..828e309c19ef4 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -491,11 +491,14 @@ executables += [ 'conditions' : ['ENABLE_UTMP'], }, core_test_template + { - 'sources' : files('test-varlink-idl-unit.c'), + 'sources' : files('test-varlink-idl-job.c'), }, core_test_template + { 'sources' : files('test-varlink-idl-manager.c'), }, + core_test_template + { + 'sources' : files('test-varlink-idl-unit.c'), + }, test_template + { 'sources' : files('test-varlink-idl-machine.c'), 'objects' : ['systemd-machined'], diff --git a/src/test/test-varlink-idl-job.c b/src/test/test-varlink-idl-job.c new file mode 100644 index 0000000000000..23c75d573b50f --- /dev/null +++ b/src/test/test-varlink-idl-job.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "job.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Job.h" + +TEST(job_enums_idl) { + TEST_IDL_ENUM(JobType, job_type, vl_type_JobType); + TEST_IDL_ENUM(JobState, job_state, vl_type_JobState); + TEST_IDL_ENUM(JobResult, job_result, vl_type_JobResult); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 28b49a0659258..2469859411931 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -7,6 +7,7 @@ #include "mount.h" #include "numa-util.h" #include "process-util.h" +#include "service.h" #include "tests.h" #include "test-varlink-idl-util.h" #include "unit.h" @@ -59,6 +60,9 @@ TEST(unit_enums_idl) { /* MountRuntime enums */ TEST_IDL_ENUM(MountResult, mount_result, vl_type_MountResult); + /* ServiceContext enums */ + TEST_IDL_ENUM(ServiceType, service_type, vl_type_ServiceType); + /* UnitContext enums */ TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode); TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); From c0cda5d91c8a6e08839ee004044c0bdea2faafa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 8 May 2026 17:25:41 +0200 Subject: [PATCH 1530/2155] userdbctl: fix erroneous errno Fixes 1604937f83d3154fb1c3b5ef053f7fccd0825ce6. --- src/userdb/userdbctl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 35a17c50217da..dd6fc0f5c12f1 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1866,7 +1866,8 @@ static int run(int argc, char *argv[]) { if (!e) return log_oom(); - if (setenv("SYSTEMD_ONLY_USERDB", e, true) < 0) + r = RET_NERRNO(setenv("SYSTEMD_ONLY_USERDB", e, true)); + if (r < 0) return log_error_errno(r, "Failed to set $SYSTEMD_ONLY_USERDB: %m"); log_info("Enabled services: %s", e); From 7af304d601e63b892bd965e5d0c7536d6ab31621 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 8 May 2026 18:02:40 +0100 Subject: [PATCH 1531/2155] test: make TEST-07-PID1.user-namespace-path more robust The test occasionally fails because lsns returns empty output for the transient unit, even though the process is running. e.g.: [ 1843.556046] TEST-07-PID1.sh[8560]: + systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/8608/ns/user --property=NetworkNamespacePath=/proc/8608/ns/net sleep 3600 [ 1844.205927] TEST-07-PID1.sh[8616]: ++ systemctl show newservice -p MainPID [ 1844.221425] TEST-07-PID1.sh[8618]: ++ lsns -p 8608 -o NS -t net -n [ 1844.229653] TEST-07-PID1.sh[8619]: ++ lsns -p 8614 -o NS -t net -n [ 1844.235563] TEST-07-PID1.sh[8620]: FAIL: expected: '' actual: '4026532522' This could be a race, so switch to Type=notify to try and make it more robust. --- test/units/TEST-07-PID1.user-namespace-path.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/units/TEST-07-PID1.user-namespace-path.sh b/test/units/TEST-07-PID1.user-namespace-path.sh index fda83a9566f80..f868b0ce6804d 100755 --- a/test/units/TEST-07-PID1.user-namespace-path.sh +++ b/test/units/TEST-07-PID1.user-namespace-path.sh @@ -7,10 +7,10 @@ set -o pipefail . "$(dirname "$0")"/util.sh # Only reuse the user namespace -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" @@ -19,10 +19,10 @@ assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS - systemctl stop oldservice newservice # Reuse the user and network namespaces -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') assert_eq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" @@ -31,10 +31,10 @@ assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS - systemctl stop oldservice newservice # Delegate the network namespace -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" From 9bbadefe432ab6e7c2778631349e307bde5bef22 Mon Sep 17 00:00:00 2001 From: albertescanes <225928304+albertescanes@users.noreply.github.com> Date: Fri, 8 May 2026 15:43:34 +0200 Subject: [PATCH 1532/2155] man: update Fedora image name in vmspawn example Update the Fedora Cloud image name to the current one and use importctl instead of machinectl. --- man/systemd-vmspawn.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index b23c66514221d..d75993846f754 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -852,16 +852,16 @@ $ systemd-vmspawn --image=image.raw - Import and run a Fedora &fedora_latest_version; Cloud image using machinectl + Import and run a Fedora &fedora_latest_version; Cloud image using importctl $ curl -L \ - -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz \ + -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-&fedora_latest_version;-&fedora_cloud_release;.x86_64.qcow2 \ -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM \ -O https://fedoraproject.org/fedora.gpg $ gpgv --keyring ./fedora.gpg Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM $ sha256sum -c Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM -# machinectl import-raw Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz fedora-&fedora_latest_version;-cloud +# importctl import-raw -m Fedora-Cloud-Base-Generic-&fedora_latest_version;-&fedora_cloud_release;.x86_64.qcow2 fedora-&fedora_latest_version;-cloud # systemd-vmspawn -M fedora-&fedora_latest_version;-cloud From 73fd578f8667ad4cd4a71eb31ca144aa29e36656 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 8 May 2026 21:28:36 +0200 Subject: [PATCH 1533/2155] mkosi: drop libucontext again Turns out it's possible to implement fibers without unnecessary system calls and without ucontext.h so there's no need for libucontext anymore, so drop it from the package list. --- mkosi/mkosi.conf.d/arch/mkosi.conf | 1 - mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 1 - mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf | 1 - mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf | 1 - mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf | 1 - mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf | 1 - 6 files changed, 6 deletions(-) diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index d3c284a2f4bc4..f3503b3789381 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -29,7 +29,6 @@ Packages= iproute iputils knot - libucontext liburing linux man-db diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index 4bf316eb89201..925078fbb76a4 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -44,7 +44,6 @@ Packages= knot libcap-ng-utils libmicrohttpd - libucontext liburing man-db nmap-ncat diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf index 59c7ed6cae9ab..d7a79d11c1051 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf @@ -10,4 +10,3 @@ Packages= diffutils erofs-utils git - libucontext diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf index 35bc886f40c11..4d0ca8917d83f 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf @@ -12,6 +12,5 @@ Packages= git-core libasan libubsan - libucontext-devel rpm-build which diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf index 2fcf333324afb..99592efc01960 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf @@ -10,7 +10,6 @@ Packages= clang-tools-extra github-cli lcov - libucontext liburing musl mypy diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf index 06ff1b66258f2..2715d1494e488 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf @@ -12,6 +12,5 @@ Packages= rpm-build libasan libubsan - libucontext-devel liburing-devel compiler-rt From ba932407580677225a3d126332deb5087f1e1aa8 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Fri, 8 May 2026 19:12:43 -0400 Subject: [PATCH 1534/2155] man/tmpfiles: fix missing 'as' in %t details column This was missing when the details were added in 5a8575ef013 (tmpfiles: also add %t/%S/%C/%L specifiers, 2017-11-23). --- man/tmpfiles.d.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 39fcad850d71e..8a908c412519f 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -776,7 +776,7 @@ d /tmp/foo/bar - - - bmA:1h - %t System or user runtime directory - In mode, this is the same $XDG_RUNTIME_DIR, and /run/ otherwise. + In mode, this is the same as $XDG_RUNTIME_DIR, and /run/ otherwise. From 89148192f8fb6fef31616af458a942ad0fdd5acc Mon Sep 17 00:00:00 2001 From: Valentin David Date: Sat, 28 Mar 2026 19:48:36 +0100 Subject: [PATCH 1535/2155] tmpfiles: Add commands for file capabilites --- man/tmpfiles.d.xml | 42 +++- src/tmpfiles/tmpfiles.c | 314 ++++++++++++++++++++++++++++++ test/units/TEST-22-TMPFILES.22.sh | 63 ++++++ 3 files changed, 416 insertions(+), 3 deletions(-) create mode 100755 test/units/TEST-22-TMPFILES.22.sh diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 8a908c412519f..e5f694611a60d 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -76,6 +76,10 @@ a /path-or-glob/to/set/acls - - - - POSIX a+ /path-or-glob/to/append/acls - - - - POSIX ACLs A /path-or-glob/to/set/acls/recursively - - - - POSIX ACLs A+ /path-or-glob/to/append/acls/recursively - - - - POSIX ACLs +k /path-or-glob/to/set/caps - - - - file capabilities +k+ /path-or-glob/to/adjust/caps - - - - file capabilities +K /path-or-glob/to/set/caps/recursively - - - - file capabilities +K+ /path-or-glob/to/adjust/caps/recursively - - - - file capabilities @@ -484,6 +488,37 @@ L /tmp/foobar - - - - /dev/null + + + k + k+ + Set file capabilities, see capabilities7. + Lines of this type accept shell-style globs in place of normal path names. Does not follow + symlinks. + + The syntax follows cap_text_formats7. It + also supports rootuid=INT for the user namespace root + user ID. + + If suffixed with +, current capabilities on the file that are not touched by the expression + will be kept. For example, if all cap_setuid capabilities need to be removed but + others should be kept, one can use k+ with cap_setuid= or + cap_setuid-eip. + + + + + + K + K+ + Same as k and + k+, but recursive. Does not follow + symlinks. + + + @@ -565,8 +600,8 @@ w- /proc/sys/vm/swappiness - - - - 10 -, the default is used: 0755 for directories, 0644 for all other file objects. For z, Z lines, if omitted or when set to -, the file access mode will not be modified. This parameter is ignored for x, - r, R, L, t, and - a lines. + r, R, L, t, + a, and k lines. Optionally, if prefixed with ~, the access mode is masked based on the already set access bits for existing file or directories: if the existing file has all executable bits unset, @@ -707,7 +742,8 @@ d /tmp/foo/bar - - - bmA:1h - suffixed by a newline. For C, specifies the source file or directory. For t and T, determines extended attributes to be set. For a and A, determines ACL attributes to be set. For h and H, - determines the file attributes to set. Ignored for all other lines. + determines the file attributes to set. For k and K, determines + file capabilities to be set. Ignored for all other lines. This field can contain specifiers, see below. diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 44843f3ca77ec..b6007f6207ed6 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -14,6 +14,7 @@ #include "bitfield.h" #include "btrfs-util.h" #include "build.h" +#include "capability-list.h" #include "capability-util.h" #include "chase.h" #include "chattr-util.h" @@ -104,6 +105,8 @@ typedef enum ItemType { RECURSIVE_SET_XATTR = 'T', SET_ACL = 'a', RECURSIVE_SET_ACL = 'A', + SET_FCAPS = 'k', + RECURSIVE_SET_FCAPS = 'K', SET_ATTRIBUTE = 'h', RECURSIVE_SET_ATTRIBUTE = 'H', IGNORE_PATH = 'x', @@ -126,6 +129,18 @@ typedef enum AgeBy { AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME, } AgeBy; +typedef struct FCapsPatch { + uint64_t mask; + uint64_t set; +} FCapsPatch; + +typedef struct FCapsUpdate { + uid_t rootuid; + FCapsPatch inheritable; + FCapsPatch permitted; + FCapsPatch effective; +} FCapsUpdate; + typedef struct Item { ItemType type; @@ -139,6 +154,7 @@ typedef struct Item { acl_t acl_access_exec; acl_t acl_default; #endif + FCapsUpdate fcaps; uid_t uid; gid_t gid; mode_t mode; @@ -171,6 +187,8 @@ typedef struct Item { bool ignore_if_target_missing:1; + bool fcaps_set:1; + OperationMask done; } Item; @@ -408,6 +426,8 @@ static bool needs_glob(ItemType t) { RECURSIVE_SET_XATTR, SET_ACL, RECURSIVE_SET_ACL, + SET_FCAPS, + RECURSIVE_SET_FCAPS, SET_ATTRIBUTE, RECURSIVE_SET_ATTRIBUTE, IGNORE_PATH, @@ -1502,6 +1522,271 @@ static int path_set_acls( return r; } +static int capability_vfs_from_string(const char *s, FCapsUpdate *ret) { + FCapsUpdate set = { + .rootuid = UID_INVALID, + }; + + assert(s); + assert(ret); + + for (const char *p = s;;) { + _cleanup_free_ char *word = NULL, *keys = NULL; + char *value, sep; + int r; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); + if (r < 0) + return log_debug_errno(r, "Failed to split words from '%s': %m", p); + if (r == 0) + break; + + value = strpbrk(word, "=+-"); + if (!value) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse key/value '%s': %m", word); + keys = strndup(word, value - word); + if (!keys) + return log_oom(); + sep = *value; + value++; + + if (sep == '=' && streq(keys, "rootuid")) { + r = parse_uid(value, &set.rootuid); + if (r < 0) + return log_debug_errno(r, "Failed to parse rootuid value '%s': %m", value); + } else { + uint64_t caps = 0; + + if (!in_charset(value, "eip")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse value '%s': %m", value); + + if (STR_IN_SET(keys, "all", "")) + caps = all_capabilities(); + else + for (const char *remaining_keys = keys; remaining_keys && *remaining_keys;) { + _cleanup_free_ char *key = NULL; + + r = extract_first_word(&remaining_keys, &key, ",", /* flags= */0); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability list '%s': %m", keys); + if (r == 0) + break; + if (streq(key, "all")) + caps = all_capabilities(); + else { + r = capability_from_name(key); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability '%s': %m", key); + caps |= UINT64_C(1) << r; + } + } + + if (sep == '=') { + set.permitted.mask |= caps; + set.inheritable.mask |= caps; + set.effective.mask |= caps; + } + if (IN_SET(sep, '=', '+')) { + if (strchr(value, 'p')) + set.permitted.set |= caps; + if (strchr(value, 'i')) + set.inheritable.set |= caps; + if (strchr(value, 'e')) + set.effective.set |= caps; + } else { + if (strchr(value, 'p')) { + set.permitted.mask |= caps; + set.permitted.set &= ~caps; + } + if (strchr(value, 'i')) { + set.inheritable.mask |= caps; + set.inheritable.set &= ~caps; + } + if (strchr(value, 'e')) { + set.effective.mask |= caps; + set.effective.set &= ~caps; + } + } + } + } + + *ret = set; + + return 0; +} + +static size_t cap_data_size(uint32_t revision) { + switch (revision) { + case VFS_CAP_REVISION_1: + return XATTR_CAPS_SZ_1; + case VFS_CAP_REVISION_2: + return XATTR_CAPS_SZ_2; + case VFS_CAP_REVISION_3: + return XATTR_CAPS_SZ_3; + default: + return SIZE_MAX; + } +} + +static bool inode_type_can_fcaps(mode_t mode) { + return S_ISREG(mode); +} + +static int apply_fcaps(int fd, const char *path, bool append, const FCapsUpdate *set) { + struct vfs_ns_cap_data val = { + .magic_etc = htole32(VFS_CAP_REVISION), + }; + le32_t effective[VFS_CAP_U32] = {}; + struct stat st; + int r; + + assert(fd >= 0); + assert(path); + assert(set); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", path); + + if (hardlink_vulnerable(&st)) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Refusing to set file capabilities on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", + path); + + if (!inode_type_can_fcaps(st.st_mode)) { + log_debug("Skipping file capabilities for '%s' (inode type does not support file capabilities).", path); + return 0; + } + + if (append) { + _cleanup_free_ char *xattr_data = NULL; + size_t xattr_data_len; + + r = fgetxattr_malloc(fd, "security.capability", &xattr_data, &xattr_data_len); + if (r == -ENODATA) + log_debug("No capabilities found for '%s'", path); + else if (r < 0) + return log_error_errno(r, "Failed to read capabilities of '%s': %m", path); + else { + _cleanup_free_ struct vfs_ns_cap_data *original = NULL; + + if (xattr_data_len < endoffsetof_field(struct vfs_ns_cap_data, magic_etc)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extended attributes for capabilities are too small"); + + original = realloc0(xattr_data, sizeof(struct vfs_ns_cap_data)); + if (!original) + return log_oom(); + xattr_data = NULL; + + size_t expected_size = cap_data_size(le32toh(original->magic_etc) & VFS_CAP_REVISION_MASK); + if (expected_size == SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type of file capabilities"); + if (xattr_data_len != expected_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Size of file capabilities does not match its type"); + + if (FLAGS_SET(le32toh(original->magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + effective[n] = original->data[n].permitted | original->data[n].inheritable; + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + val.data[n].permitted = original->data[n].permitted; + val.data[n].inheritable = original->data[n].inheritable; + } + + val.rootid = original->rootid; + } + } + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + size_t bit_shift = 32*n; + val.data[n].inheritable &= htole32(~(set->inheritable.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].inheritable |= htole32((set->inheritable.set >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted &= htole32(~(set->permitted.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted |= htole32((set->permitted.set >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] &= htole32(~(set->effective.mask >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] |= htole32((set->effective.set >> bit_shift) & UINT32_C(0xffffffff)); + if (effective[n] != 0) + val.magic_etc |= htole32(VFS_CAP_FLAGS_EFFECTIVE); + } + + if (FLAGS_SET(le32toh(val.magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + if ((val.data[n].permitted | val.data[n].inheritable) != effective[n]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Inconsistent effective bits"); + + if (set->rootuid != UID_INVALID) + val.rootid = htole32(set->rootuid); + + log_action("Would try to set", "Trying to set", + "%s capabilities on %s", path); + + if (!arg_dry_run) { + r = xsetxattr_full(fd, /* path= */ NULL, AT_EMPTY_PATH, "security.capability", (void*)&val, sizeof(val), /* xattr_flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to setcap '%s': %m", path); + } + + return 0; +} + +static int parse_caps_from_arg(Item *item) { + FCapsUpdate fcaps; + int r; + + assert(item); + + r = capability_vfs_from_string(item->argument, &fcaps); + if (r < 0) { + log_full_errno(arg_graceful ? LOG_DEBUG : LOG_WARNING, + r, "Failed to parse capabilities \"%s\", ignoring: %m", item->argument); + return 0; + } + + item->fcaps_set = true; + item->fcaps = fcaps; + + return 0; +} + +static int fd_set_caps( + Context *c, + Item *item, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + assert(c); + assert(item); + assert(fd >= 0); + assert(path); + + if (!item->fcaps_set) + return 0; + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + +static int path_set_caps( + Context *c, + Item *item, + const char *path, + CreationMode creation) { + _cleanup_close_ int fd = -EBADF; + + assert(c); + assert(item); + assert(path); + + if (!item->fcaps_set) + return 0; + + fd = path_open_safe(path); + if (fd == -ENOENT) + return 0; + if (fd < 0) + return fd; + + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + static int parse_attribute_from_arg(Item *item) { static const struct { char character; @@ -2955,6 +3240,18 @@ static int create_item(Context *c, Item *i) { return r; break; + case SET_FCAPS: + r = glob_item(c, i, path_set_caps); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_FCAPS: + r = glob_item_recursively(c, i, fd_set_caps); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: r = glob_item(c, i, path_set_attribute); if (r < 0) @@ -3816,6 +4113,23 @@ static int parse_line( return r; break; + case SET_FCAPS: + case RECURSIVE_SET_FCAPS: + if (unbase64) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for capabilities."); + } + if (!i.argument) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Set capabilities requires argument."); + } + r = parse_caps_from_arg(&i); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: case RECURSIVE_SET_ATTRIBUTE: if (unbase64) { diff --git a/test/units/TEST-22-TMPFILES.22.sh b/test/units/TEST-22-TMPFILES.22.sh new file mode 100755 index 0000000000000..37c9f709b75ce --- /dev/null +++ b/test/units/TEST-22-TMPFILES.22.sh @@ -0,0 +1,63 @@ +#!/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 + +rm -f /tmp/setcap +touch /tmp/setcap + +systemd-tmpfiles --dry-run --create - < Date: Sat, 9 May 2026 05:59:10 +0000 Subject: [PATCH 1536/2155] po: Translated using Weblate (Romanian) Currently translated at 68.7% (183 of 266 strings) Co-authored-by: Petru Rebeja Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ro/ Translation: systemd/main --- po/ro.po | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/po/ro.po b/po/ro.po index 876dc27773b09..97dcf594331a3 100644 --- a/po/ro.po +++ b/po/ro.po @@ -4,21 +4,22 @@ # va511e , 2015. # Daniel Șerbănescu , 2015, 2017. # Vlad , 2020, 2021. +# Petru Rebeja , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2021-01-12 17:36+0000\n" -"Last-Translator: Vlad \n" +"PO-Revision-Date: 2026-05-09 05:59+0000\n" +"Last-Translator: Petru Rebeja \n" "Language-Team: Romanian \n" +"systemd/main/ro/>\n" "Language: ro\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==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" -"X-Generator: Weblate 4.4\n" +"X-Generator: Weblate 5.17.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -125,16 +126,12 @@ msgstr "" "utilizator." #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" -msgstr "Actualizează un spațiu personal" +msgstr "Actualizați-vă spațiu personal" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "" -"Autentificarea este necesară pentru a actualiza spațiul personal al unui " -"utilizator." +msgstr "Pentru a-ți actualiza spațiul personal, este necesară autentificarea." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" @@ -158,16 +155,14 @@ msgstr "" "al unui utilizator." #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "Crează un spațiu personal" +msgstr "Activează un spațiu personal" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." msgstr "" -"Autentificarea este necesară pentru a crea spațiul personal al unui " -"utilizator." +"Pentru a activa spațiul personal al unui utilizator este necesară " +"autentificarea." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" From b7be9ccc8f4299269f72bde49e426a7a9d484da9 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 9 May 2026 08:53:01 -0300 Subject: [PATCH 1537/2155] hwdb/keyboard: fix KP_Enter on Clevo PA70ES The ITE keyboard controller firmware (version 0xAB83) is shared between the Clevo PA70ES and the X+ piccolo series. The piccolo's hwdb rule matches by input device ID (evdev:input:b0011v0001p0001eAB83*) and remaps scan code 0x9c (KP_Enter) to Enter, since the piccolo has no numpad and its main Enter key sends the wrong scan code. The Clevo PA70ES has a real numpad. The piccolo rule matches it because both laptops use the same ITE controller firmware, which breaks KP_Enter on the PA70ES. Add a DMI-specific override that restores KEY_KPENTER for 0x9c on the PA70ES. The piccolo rule should ideally be narrowed to use DMI matching instead of input device ID to avoid catching other laptops with the same ITE controller firmware. --- hwdb.d/60-keyboard.hwdb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index ebc08560fe9e8..23023740901ac 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -344,6 +344,15 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*BenQ*:pn*Joybook*R22*:* # Clevo ########################################################### +# Clevo PA70ES (Avell C73) +# The ITE keyboard controller firmware (version 0xAB83) is shared with +# the X+ piccolo. The piccolo rule (below) matches by input device ID +# and remaps KP_Enter to Enter since the piccolo has no numpad and its +# main Enter sends the wrong scan code. The PA70ES has a real numpad, +# so the remap breaks KP_Enter. This restores the correct mapping. +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnPA70ES:* + KEYBOARD_KEY_9c=kpenter + evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnW65_67SZ:* KEYBOARD_KEY_a0=!mute KEYBOARD_KEY_a2=!playpause From cd57308303ecffaef01a5b27d7fa4ef7e7d6a9ce Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 8 May 2026 15:09:25 +0100 Subject: [PATCH 1538/2155] test: workaroud flaky TEST-53-TIMER.restart-trigger against journald cgroup attribution race The restart-trigger subtest occasionally fails on CI with: + assert_eq 0 1 FAIL: expected: '1' actual: '0' even though the timer fires correctly and the echo message is in fact written to the journal. The failure happens because the test relies on `journalctl --unit=$UNIT_NAME` to find the message, and that filter is based on the cgroup journald looks up for the writer PID at the time the stdout message is received. For very short-lived processes spawned via systemd-executor (like `echo`), that lookup is racy: the writer's `/proc/$PID/cgroup` can still resolve to `/init.scope` (systemd-executor's own cgroup) rather than the service's cgroup, so the message ends up attributed to `init.scope` and `--unit=` filtering misses it. __CURSOR=s=6f90ff5b6a0e47c3a527a9b4892af965;i=f8ed;b=3dad0cc689a04781879e4dd846d24432;m=17703dc;t=6513be1be2506;x=8d3009a687724b5e __REALTIME_TIMESTAMP=1778167492519174 __MONOTONIC_TIMESTAMP=24576988 __SEQNUM=63725 __SEQNUM_ID=6f90ff5b6a0e47c3a527a9b4892af965 _BOOT_ID=3dad0cc689a04781879e4dd846d24432 _HOSTNAME=H PRIORITY=6 SYSLOG_FACILITY=3 _UID=0 _GID=0 _CAP_EFFECTIVE=1ffffffffff _SYSTEMD_CGROUP=/init.scope _SYSTEMD_UNIT=init.scope _SYSTEMD_SLICE=-.slice _EXE=/usr/lib/systemd/systemd-executor _TRANSPORT=stdout _COMM=18 _MACHINE_ID=89ef83adc0bc4a33a83a227201b57203 _RUNTIME_SCOPE=system _PID=816 _CMDLINE=/usr/lib/systemd/systemd-executor --deserialize 50 --log-level debug,console:info --log-target journal-or-kmsg _STREAM_ID=8e8e4166c99e40afaa58bcd04a50a7f4 SYSLOG_IDENTIFIER=echo MESSAGE=Hello from timer 29581 Note _SYSTEMD_UNIT=init.scope / _SYSTEMD_CGROUP=/init.scope on the echo output: this is what causes `--unit=timer-restart-14362` to return 0 hits. The test failure logs from the same run confirm this: + JOURNAL_TS=1778160292 + journalctl -p info --since=@1778160292 --unit=timer-restart-14362 '--grep=Hello from timer 29581' -- No entries -- + systemctl restart timer-restart-14362.timer ... + date '--set=+2 hours' Thu May 7 15:24:52 UTC 2026 + sleep 1 ... echo[816]: Hello from timer 29581 ... ++ journalctl -q -p info --since=@1778160292 --unit=timer-restart-14362 '--grep=Hello from timer 29581' ++ wc -l + assert_eq 0 1 FAIL: expected: '1' actual: '0' For comparison, in a passing local run the same message is attributed correctly to the service unit (_SYSTEMD_UNIT=timer-restart-24147.service), so `--unit=` matches. Work around the underlying journald race in the test by setting an explicit `SyslogIdentifier=` on the service and matching with `-t` plus the unique grep pattern: `SyslogIdentifier` is carried over the stdout stream protocol and is not affected by the cgroup lookup race. Co-developed-by: Claude Opus 4.7 --- test/units/TEST-53-TIMER.restart-trigger.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-53-TIMER.restart-trigger.sh b/test/units/TEST-53-TIMER.restart-trigger.sh index 71de99cf2762e..6a4add9d638d5 100755 --- a/test/units/TEST-53-TIMER.restart-trigger.sh +++ b/test/units/TEST-53-TIMER.restart-trigger.sh @@ -25,13 +25,15 @@ EOF cat >"/run/systemd/system/$UNIT_NAME.service" < Date: Fri, 8 May 2026 16:16:04 +0100 Subject: [PATCH 1539/2155] test: fix flaky TEST-07-PID1.socket-defer.sh The socket's SubState transitions from 'running' to 'listening' shortly after the triggered service becomes inactive, so the assert can race and observe the stale 'running' state: [ 1882.425335] systemd[1]: TEST-07-PID1-socket-defer-23279.service: Changed dead -> running [ 1882.495150] TEST-07-PID1.sh[20535]: ++ systemctl show TEST-07-PID1-socket-defer-23279.socket -P SubState [ 1882.514239] TEST-07-PID1.sh[20509]: + assert_eq running listening [ 1882.510529] systemd[1]: TEST-07-PID1-socket-defer-23279.socket: Flushing socket before listening. [ 1882.510559] systemd[1]: TEST-07-PID1-socket-defer-23279.socket: Changed running -> listening Poll for 30s instead of directly asserting to try and make it more robust --- test/units/TEST-07-PID1.socket-defer.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-07-PID1.socket-defer.sh b/test/units/TEST-07-PID1.socket-defer.sh index 304c3bce3d90c..d8a8ff02e0499 100755 --- a/test/units/TEST-07-PID1.socket-defer.sh +++ b/test/units/TEST-07-PID1.socket-defer.sh @@ -60,7 +60,9 @@ wait_for_start() { wait_for_stop() { timeout 30 bash -c "while systemctl -q is-active '$UNIT_NAME.service'; do sleep .5; done" - assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "listening" + # The socket's SubState transitions from 'running' to 'listening' shortly after the triggered + # service becomes inactive, so wait for that transition instead of checking once and racing. + timeout 30 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'listening' ]]; do sleep .5; done" } # DeferTrigger=no: job mode replace From 0bf094b7636471d4c25df5b60d1e8fe2512601e5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 8 May 2026 20:25:56 +0100 Subject: [PATCH 1540/2155] test: bump TEST-58-REPART timeouts with sanitizers The test is flaky under sanitizers as the timeouts seem to be too short, bump them like we do in other tests to try and make it more robust when running with sanitizers --- test/units/TEST-58-REPART.sh | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index 7b536fa09209f..df6f935c98035 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -20,6 +20,13 @@ export PAGER=cat # Disable use of special glyphs such as → export SYSTEMD_UTF8=0 +# Sanitizer runs are significantly slower, so give udevadm wait 3 times longer timeouts +if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then + UDEVADM_WAIT_TIMEOUT=180 +else + UDEVADM_WAIT_TIMEOUT=60 +fi + seed=750b6cd5c4ae4012a15e7be3c29e6a47 esp_guid=C12A7328-F81F-11D2-BA4B-00A0C93EC93B @@ -378,7 +385,7 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F fi loop="$(losetup -P --show --find "$imgs/zzz")" - udevadm wait --timeout=60 --settle "${loop:?}p7" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p7" cryptsetup luksDump "${loop}p7" | grep 'Flags:[[:space:]]*allow-discards' >/dev/null @@ -438,7 +445,7 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F $imgs/zzz8 : start= 6422488, size= 131072, type=4D21B016-B534-45C2-A9FB-5C16E091FD2D, uuid=329B9DB2-DFD9-4F39-8EBF-53B582B05FCD, name=\"luks-no-discards\", attrs=\"GUID:59\"" loop="$(losetup -P --show --find "$imgs/zzz")" - udevadm wait --timeout=60 --settle "${loop:?}p8" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p8" cryptsetup luksDump "${loop}p8" | grep 'Flags:[[:space:]]*(no flags)' >/dev/null losetup -d "$loop" @@ -1058,7 +1065,7 @@ EOF # shellcheck disable=SC2064 trap "rm -rf '$defs' '$imgs' ; losetup -d '$loop'" RETURN ERR - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" # Check that the verity block sizes are as expected veritysetup dump "${loop}p2" | grep 'Data block size:' | grep '4096' >/dev/null @@ -1118,7 +1125,7 @@ EOF # shellcheck disable=SC2064 trap "rm -rf '$defs' '$imgs' ; losetup -d '$loop'" RETURN ERR - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" output=$(sfdisk -J "$loop") @@ -1200,7 +1207,7 @@ EOF fi loop=$(losetup -P --show -f "$imgs/zzz") - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" # Test that /usr/def did not end up in the root partition but other files did. mkdir "$imgs/mnt" @@ -1425,7 +1432,7 @@ EOF truncate -s 100m "$imgs/$sector.img" loop=$(losetup -b "$sector" -P --show -f "$imgs/$sector.img" ) - udevadm wait --timeout=60 --settle "${loop:?}" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}" systemd-repart --offline="$OFFLINE" \ --pretty=yes \ @@ -1808,7 +1815,7 @@ EOF # shellcheck disable=SC2064 trap "umount '$imgs/mount' 2>/dev/null || true; losetup -d '$loop' 2>/dev/null || true; rm -rf '$defs' '$imgs'" RETURN echo "Loop device: $loop" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" mkdir -p "$imgs/mount" mount -t btrfs "${loop:?}p1" "$imgs/mount" @@ -1936,7 +1943,7 @@ EOF "$imgs/encint.img" loop="$(losetup -P --show --find "$imgs/encint.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" volume="test-repart-luksint-$RANDOM" dmstatus="$imgs/dmsetup-$RANDOM" @@ -2041,7 +2048,7 @@ EOF "$imgs/enckeyhash.img" loop="$(losetup -P --show --find "$imgs/enckeyhash.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" touch "$imgs/empty-password" @@ -2101,7 +2108,7 @@ EOF "$imgs/fstabcrypttabrepart.img" loop="$(losetup -P --show --find "$imgs/fstabcrypttabrepart.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" touch "$imgs/empty-password" From 87d282a73d9db5cfb2885f37d7fb06194a8ca65e Mon Sep 17 00:00:00 2001 From: favilances Date: Sat, 9 May 2026 21:52:04 +0300 Subject: [PATCH 1541/2155] test-path-util: add coverage for path edge cases Path utility helpers are used throughout systemd for validation, comparison and manipulation of filesystem paths. Add coverage for additional corner cases around absolute path detection, normalization and prefix matching so regressions in these common helpers are easier to catch. Co-developed-by: OpenAI Codex Signed-off-by: favilances --- src/test/test-path-util.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index ef4f29172d838..1dc260a46732e 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -24,8 +24,12 @@ TEST(print_paths) { } TEST(path) { + assert_se(!path_is_absolute(NULL)); + assert_se(!path_is_absolute("")); assert_se( path_is_absolute("/")); + assert_se( path_is_absolute("//")); assert_se(!path_is_absolute("./")); + assert_se(!path_is_absolute("foo/bar")); assert_se( PATH_IN_SET("/bin", "/", "/bin", "/foo")); assert_se( PATH_IN_SET("/bin", "/bin")); @@ -42,6 +46,21 @@ TEST(path) { assert_se(!path_equal(NULL, "a")); } +TEST(path_is_normalized) { + assert_se( path_is_normalized("/")); + assert_se( path_is_normalized("/usr/bin")); + assert_se( path_is_normalized("usr/bin")); + + assert_se(!path_is_normalized("")); + assert_se(!path_is_normalized(".")); + assert_se(!path_is_normalized("./usr/bin")); + assert_se(!path_is_normalized("/usr//bin")); + assert_se(!path_is_normalized("/usr/./bin")); + assert_se(!path_is_normalized("/usr/bin/.")); + assert_se(!path_is_normalized("../usr/bin")); + assert_se(!path_is_normalized("/usr/../bin")); +} + TEST(is_path) { assert_se(!is_path("foo")); assert_se(!is_path("dos.ext")); @@ -760,6 +779,9 @@ TEST(path_startswith) { test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL); + test_path_startswith_one("/usr/binary", "/usr/bin", NULL, NULL); + test_path_startswith_one("/foo/barista", "/foo/bar", NULL, NULL); + test_path_startswith_one("foo/barista", "foo/bar", NULL, NULL); } static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) { From 20594fdd6e11dcfef1b833f63c9b5a0d44dbc175 Mon Sep 17 00:00:00 2001 From: Ambareesh Balaji Date: Sun, 10 May 2026 23:25:13 +0000 Subject: [PATCH 1542/2155] elf-util: pass exectable path to dwfl_core_file_report Without it, stack trace symbols fail to resolve with .gnu_debuglink split debug info. --- src/shared/elf-util.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 97188ca41483e..7c3d10f9e3374 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -619,6 +619,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, static int parse_core( int fd, + const char *executable, const char *root, char **ret, sd_json_variant **ret_package_metadata, @@ -667,7 +668,7 @@ static int parse_core( log_warning("Compiled without dwfl_set_sysroot() support, ignoring provided root directory."); #endif - if (sym_dwfl_core_file_report(c.dwfl, c.elf, NULL) < 0) + if (sym_dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not parse core file, dwfl_core_file_report() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); if (sym_dwfl_report_end(c.dwfl, NULL, NULL) != 0) @@ -734,7 +735,7 @@ static int parse_elf( if (elf_header.e_type == ET_CORE) { _cleanup_free_ char *out = NULL; - r = parse_core(fd, root, ret ? &out : NULL, &package_metadata, &dlopen_metadata); + r = parse_core(fd, executable, root, ret ? &out : NULL, &package_metadata, &dlopen_metadata); if (r < 0) return log_warning_errno(r, "Failed to inspect core file: %m"); From 8787415123b87b74170e29b180fb5cb9ac32b7e9 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 03:15:15 -0700 Subject: [PATCH 1543/2155] core: implement PathContext/Runtime for io.systemd.Unit.List + tests Add varlink context and runtime builders for .path units: PathContext: Paths (array of PathType enum + path specs), Unit, MakeDirectory, DirectoryMode, TriggerLimit PathRuntime: Result (PathResult enum) Both PathType and PathResult are exposed as proper varlink enum types. Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-path.c | 48 +++++++++++++++++++ src/core/varlink-path.h | 7 +++ src/core/varlink-unit.c | 3 ++ src/shared/varlink-io.systemd.Unit.c | 56 +++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 2 + src/test/test-varlink-idl-unit.c | 7 +++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 6 +++ 8 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/core/varlink-path.c create mode 100644 src/core/varlink-path.h diff --git a/src/core/meson.build b/src/core/meson.build index d24fc3b6d3788..0dae1e50cc9de 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -73,6 +73,7 @@ libcore_sources = files( 'varlink-manager.c', 'varlink-metrics.c', 'varlink-mount.c', + 'varlink-path.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-path.c b/src/core/varlink-path.c new file mode 100644 index 0000000000000..c6ba45a04d74a --- /dev/null +++ b/src/core/varlink-path.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "path.h" +#include "varlink-path.h" + +static int path_specs_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + PathSpec *specs = userdata; + int r; + + assert(ret); + + LIST_FOREACH(spec, k, specs) { + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("type", path_type_to_string(k->type)), + SD_JSON_BUILD_PAIR_STRING("path", k->path)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int path_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Path *p = ASSERT_PTR(PATH(userdata)); + Unit *trigger = UNIT_TRIGGER(UNIT(p)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Paths", path_specs_build_json, p->specs), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", trigger ? trigger->id : NULL), + SD_JSON_BUILD_PAIR_BOOLEAN("MakeDirectory", p->make_directory), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", p->directory_mode), + JSON_BUILD_PAIR_RATELIMIT("TriggerLimit", &p->trigger_limit)); +} + +int path_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Path *p = ASSERT_PTR(PATH(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", path_result_to_string(p->result))); +} diff --git a/src/core/varlink-path.h b/src/core/varlink-path.h new file mode 100644 index 0000000000000..a466949e37c98 --- /dev/null +++ b/src/core/varlink-path.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int path_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int path_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 5b1dc77aae0b4..ca357cf899e8a 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -29,6 +29,7 @@ #include "varlink-execute.h" #include "varlink-kill.h" #include "varlink-mount.h" +#include "varlink-path.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -162,6 +163,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void 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, + [UNIT_PATH] = path_context_build_json, [UNIT_SERVICE] = service_context_build_json, }; @@ -328,6 +330,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void 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, + [UNIT_PATH] = path_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 be5c942fcc717..4e8e4f04b7fa2 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -973,6 +973,49 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Remount command"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecRemount, ExecCommand, SD_VARLINK_NULLABLE)); +/* PathContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + PathType, + SD_VARLINK_DEFINE_ENUM_VALUE(PathExists), + SD_VARLINK_DEFINE_ENUM_VALUE(PathExistsGlob), + SD_VARLINK_DEFINE_ENUM_VALUE(DirectoryNotEmpty), + SD_VARLINK_DEFINE_ENUM_VALUE(PathChanged), + SD_VARLINK_DEFINE_ENUM_VALUE(PathModified)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathSpec, + SD_VARLINK_FIELD_COMMENT("Path spec type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(type, PathType, 0), + SD_VARLINK_FIELD_COMMENT("Path"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#PathExists="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Paths, PathSpec, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#Unit="), + SD_VARLINK_DEFINE_FIELD(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#MakeDirectory="), + SD_VARLINK_DEFINE_FIELD(MakeDirectory, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.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.path.html#TriggerLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TriggerLimit, RateLimit, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PathResult, + 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(unit_start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(trigger_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathRuntime, + SD_VARLINK_FIELD_COMMENT("Result of path operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, PathResult, 0)); + /* Service-specific types */ /* Keep in sync with service_type_table[] in src/core/service.c */ @@ -1165,7 +1208,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The automount context of the unit"), 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)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The path context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1339,7 +1384,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The automount runtime of the unit"), 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)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The path runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1501,6 +1548,11 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_MountContext, &vl_type_MountResult, &vl_type_MountRuntime, + &vl_type_PathType, + &vl_type_PathSpec, + &vl_type_PathContext, + &vl_type_PathResult, + &vl_type_PathRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index f12ac60701ee6..bc43d7b59fba8 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -30,6 +30,8 @@ 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_PathType; +extern const sd_varlink_symbol vl_type_PathResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; extern const sd_varlink_symbol vl_type_ServiceType; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 2469859411931..6c0f834a7a360 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -6,6 +6,7 @@ #include "kill.h" #include "mount.h" #include "numa-util.h" +#include "path.h" #include "process-util.h" #include "service.h" #include "tests.h" @@ -63,6 +64,12 @@ TEST(unit_enums_idl) { /* ServiceContext enums */ TEST_IDL_ENUM(ServiceType, service_type, vl_type_ServiceType); + /* PathContext enums */ + TEST_IDL_ENUM(PathType, path_type, vl_type_PathType); + + /* PathRuntime enums */ + TEST_IDL_ENUM(PathResult, path_result, vl_type_PathResult); + /* 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 3e6d4a9a257e6..1db22b85c118b 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -246,6 +246,12 @@ test -n "$mount_id" mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' +# test for PathContext/Runtime +path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$path_id" +path_params=$(jq -cn --arg name "$path_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From e11867ebddbac497a6be7ada029595dbfb7e862d Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 05:47:59 -0700 Subject: [PATCH 1544/2155] shared: move OOMPolicy varlink enum to varlink-idl-common OOMPolicy is used by both io.systemd.Manager (DefaultOOMPolicy) and io.systemd.Unit (ScopeContext.OOMPolicy), so it belongs in the shared common types alongside ManagedOOMMode and EmergencyAction. --- src/shared/varlink-idl-common.c | 6 ++++++ src/shared/varlink-idl-common.h | 1 + src/shared/varlink-io.systemd.Manager.c | 6 ------ src/shared/varlink-io.systemd.Manager.h | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/shared/varlink-idl-common.c b/src/shared/varlink-idl-common.c index 24eec3bfcf48a..a543c92a3b8a3 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -98,6 +98,12 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(auto), SD_VARLINK_DEFINE_ENUM_VALUE(kill)); +SD_VARLINK_DEFINE_ENUM_TYPE( + OOMPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(continue), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + SD_VARLINK_DEFINE_ENUM_TYPE( EmergencyAction, SD_VARLINK_DEFINE_ENUM_VALUE(none), diff --git a/src/shared/varlink-idl-common.h b/src/shared/varlink-idl-common.h index fdfbfc7986faa..a42df7118bdc1 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -12,4 +12,5 @@ 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; +extern const sd_varlink_symbol vl_type_OOMPolicy; extern const sd_varlink_symbol vl_type_EmergencyAction; diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index 81b3e894a348d..d4702b249d251 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -15,12 +15,6 @@ SD_VARLINK_DEFINE_ENUM_TYPE( 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"), diff --git a/src/shared/varlink-io.systemd.Manager.h b/src/shared/varlink-io.systemd.Manager.h index 48247dd350679..620d80e3a6ff6 100644 --- a/src/shared/varlink-io.systemd.Manager.h +++ b/src/shared/varlink-io.systemd.Manager.h @@ -6,4 +6,3 @@ 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; From 1b8a2b6abe28a105493a456a9e2aa4ae3960e136 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 11 May 2026 11:34:22 +0200 Subject: [PATCH 1545/2155] fileio: teach read_one_line_file_at() XAT_FDROOT support --- src/basic/fileio.c | 18 ++++++---- src/basic/socket-util.c | 17 +++++++-- src/test/test-fileio.c | 76 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 661667a6b2a1e..31e9af2e4e03f 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -420,11 +420,11 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { _cleanup_fclose_ FILE *f = NULL; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(filename); assert(ret); - r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f); + r = fopen_unlocked_at(dir_fd, filename, "re", /* open_flags= */ 0, &f); if (r < 0) return r; @@ -1010,13 +1010,19 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int /* A combination of fopen() with openat() */ - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(mode); assert(ret); if (dir_fd == AT_FDCWD && path && open_flags == 0) f = fopen(path, mode); - else { + else if (dir_fd == XAT_FDROOT && path && open_flags == 0) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + f = fopen(j, mode); + } else { _cleanup_close_ int fd = -EBADF; int mode_flags; @@ -1051,7 +1057,7 @@ static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_n FILE *f; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); @@ -1099,7 +1105,7 @@ int xfopenat_full( FILE *f = NULL; /* avoid false maybe-uninitialized warning */ int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(mode); assert(ret); diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 2e0ee684ff98b..d53208f138990 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1619,13 +1619,17 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { _cleanup_close_ int inode_fd = -EBADF; assert(fd >= 0); - assert(dir_fd == AT_FDCWD || dir_fd >= 0); + assert(IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) || dir_fd >= 0); /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */ - if (!path) + if (!path) { + if (dir_fd < 0) + return -EISDIR; + return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */ + } /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract * namespace path, since first char is NUL */ @@ -1640,7 +1644,14 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { * exist. If the path is too long, we also need to take the indirect route, since we can't fit this * into a sockaddr_un directly. */ - inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (dir_fd == XAT_FDROOT) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + inode_fd = open(j, O_PATH|O_CLOEXEC); + } else + inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); if (inode_fd < 0) return -errno; diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index a752b1c7cda23..d27bf7ab5d824 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -747,4 +747,80 @@ TEST(write_data_file_atomic_at) { ASSERT_OK_ERRNO(rmdir("/tmp/zzz")); } +TEST(read_one_line_file_at_xat_fdroot) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *fn = NULL, *buf = NULL; + + ASSERT_OK(mkdtemp_malloc("/tmp/test-r1lf-xatfd-XXXXXX", &t)); + ASSERT_TRUE(path_is_absolute(t)); + + ASSERT_NOT_NULL(fn = path_join(t, "hello")); + ASSERT_OK(write_string_file(fn, "first line\nsecond line", WRITE_STRING_FILE_CREATE)); + + /* XAT_FDROOT is supposed to root the path at the host's "/"; the implementation prepends a "/" so + * we pass the path without leading slash. */ + ASSERT_OK_EQ(read_one_line_file_at(XAT_FDROOT, fn + 1, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* Sanity check: AT_FDCWD with the absolute path gives the same result. */ + ASSERT_OK_EQ(read_one_line_file_at(AT_FDCWD, fn, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* /proc/version should always be readable via XAT_FDROOT (some build envs may restrict it; tolerate + * that). */ + int r = read_one_line_file_at(XAT_FDROOT, "proc/version", &buf); + if (!ERRNO_IS_NEG_PRIVILEGE(r)) { + ASSERT_OK(r); + ASSERT_FALSE(isempty(buf)); + buf = mfree(buf); + } + + /* Non-existent path through XAT_FDROOT should yield -ENOENT. */ + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, "tmp/this/path/really/should/not/exist", &buf), ENOENT); + + /* Now create a Unix socket in the same temp dir, and verify that read_one_line_file_at() returns + * -ENXIO when pointed at it via XAT_FDROOT — read_one_line_file_at() does not enable the socket + * fallback. */ + _cleanup_free_ char *sockpath = NULL; + ASSERT_NOT_NULL(sockpath = path_join(t, "socket")); + + _cleanup_close_ int listener = -EBADF; + ASSERT_OK(listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, /* protocol= */ 0)); + + union sockaddr_union sa; + ASSERT_OK(sockaddr_un_set_path(&sa.un, sockpath)); + ASSERT_OK_ERRNO(bind(listener, &sa.sa, sockaddr_un_len(&sa.un))); + ASSERT_OK_ERRNO(listen(listener, 1)); + + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, sockpath + 1, &buf), ENXIO); + + /* But read_full_file_full() with READ_FULL_FILE_CONNECT_SOCKET *does* enable the socket fallback, + * which routes through xfopenat_unix_socket() and connect_unix_path() — both now teach the + * XAT_FDROOT codepath. Use that to exercise the socket open via XAT_FDROOT. */ + static const char test_sock_str[] = "hello via xat_fdroot socket\n"; + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int pr = ASSERT_OK(pidref_safe_fork("(server)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref)); + if (pr == 0) { + _cleanup_close_ int rfd = -EBADF; + ASSERT_OK(rfd = accept4(listener, /* addr= */ NULL, /* addrlen= */ NULL, SOCK_CLOEXEC)); + ASSERT_OK_EQ_ERRNO(write(rfd, test_sock_str, sizeof(test_sock_str) - 1), + (ssize_t) sizeof(test_sock_str) - 1); + _exit(EXIT_SUCCESS); + } + + _cleanup_free_ char *data = NULL; + size_t size; + ASSERT_OK(read_full_file_full(XAT_FDROOT, sockpath + 1, + /* offset= */ UINT64_MAX, /* size= */ SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, /* bind_name= */ NULL, + &data, &size)); + ASSERT_EQ(size, sizeof(test_sock_str) - 1); + ASSERT_STREQ(data, test_sock_str); + + ASSERT_OK(pidref_wait_for_terminate_and_check("(server)", &pidref, WAIT_LOG)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From db8046535b419142e2850499552600e2fb0ac558 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 6 May 2026 15:17:36 +0200 Subject: [PATCH 1546/2155] fileio: add new read_boolean_file() helper --- src/basic/fileio.c | 14 +++++++++++ src/basic/fileio.h | 4 ++++ src/test/test-fileio.c | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 31e9af2e4e03f..8149bb8b0dd44 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -431,6 +431,20 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { return read_line(f, LONG_LINE_MAX, ret); } +int read_boolean_file_at(int dir_fd, const char *filename) { + _cleanup_free_ char *s = NULL; + int r; + + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(filename); + + r = read_one_line_file_at(dir_fd, filename, &s); + if (r < 0) + return r; + + return parse_boolean(s); +} + int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 274fdfbd7c89a..5e6e3de1fef57 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -63,6 +63,10 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret); static inline int read_one_line_file(const char *filename, char **ret) { return read_one_line_file_at(AT_FDCWD, filename, ret); } +int read_boolean_file_at(int dir_fd, const char *filename); +static inline int read_boolean_file(const char *filename) { + return read_boolean_file_at(AT_FDCWD, filename); +} int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size); static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) { return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index d27bf7ab5d824..d050522028f6d 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -823,4 +823,58 @@ TEST(read_one_line_file_at_xat_fdroot) { ASSERT_OK(pidref_wait_for_terminate_and_check("(server)", &pidref, WAIT_LOG)); } +TEST(read_boolean_file) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-read-boolean-file-XXXXXX"; + _cleanup_close_ int fd = -EBADF, dfd = -EBADF; + const char *rel; + + ASSERT_OK(fd = mkostemp_safe(fn)); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), true); + + ASSERT_OK(write_string_file(fn, "0", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), false); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), false); + + ASSERT_OK(write_string_file(fn, "true\nignored\n", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + + ASSERT_OK(write_string_file(fn, "garbage", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_ERROR(read_boolean_file(fn), EINVAL); + + ASSERT_ERROR(read_boolean_file("/tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* Now test XAT_FDROOT: filename is relative, looked up against "/" */ + ASSERT_TRUE(path_startswith(fn, "/")); + rel = fn + 1; + + ASSERT_OK(write_string_file(fn, "on", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK(write_string_file(fn, "off", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), false); + + ASSERT_ERROR(read_boolean_file_at(XAT_FDROOT, "tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* And confirm XAT_FDROOT ignores the cwd: chdir somewhere unrelated, then look up + * the same relative-to-/ path. */ + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir("/usr")); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK_ERRNO(chdir(cwd)); + + /* Also test the dir_fd >= 0 path using an actual fd for /tmp. */ + ASSERT_OK(dfd = open("/tmp", O_DIRECTORY|O_CLOEXEC)); + ASSERT_OK(write_string_file(fn, "1", WRITE_STRING_FILE_TRUNCATE)); + _cleanup_free_ char *bn = NULL; + ASSERT_OK(path_extract_filename(fn, &bn)); + ASSERT_OK_EQ(read_boolean_file_at(dfd, bn), true); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 4f63cff1f47f78c063680745e4ebc33c7d82057e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 11 May 2026 11:48:11 +0200 Subject: [PATCH 1547/2155] tree-wide: port various places to read_boolean_file() --- src/basic/terminal-util.c | 10 +--------- src/journal/journald-console.c | 10 ++-------- src/shared/apparmor-util.c | 13 +++---------- 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d241f5e7f8998..5b3fc8990d696 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -869,16 +869,8 @@ int vt_disallocate(const char *tty_path) { } static int vt_default_utf8(void) { - _cleanup_free_ char *b = NULL; - int r; - /* Read the default VT UTF8 setting from the kernel */ - - r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); - if (r < 0) - return r; - - return parse_boolean(b); + return read_boolean_file("/sys/module/vt/parameters/default_utf8"); } static int vt_reset_keyboard(int fd) { diff --git a/src/journal/journald-console.c b/src/journal/journald-console.c index 0f376f9e5a45c..0cd2215bd21ad 100644 --- a/src/journal/journald-console.c +++ b/src/journal/journald-console.c @@ -12,7 +12,6 @@ #include "journald-console.h" #include "journald-manager.h" #include "log.h" -#include "parse-util.h" #include "process-util.h" #include "stdio-util.h" #include "terminal-util.h" @@ -22,13 +21,8 @@ static bool prefix_timestamp(void) { static int cached_printk_time = -1; - if (_unlikely_(cached_printk_time < 0)) { - _cleanup_free_ char *p = NULL; - - cached_printk_time = - read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0 - && parse_boolean(p) > 0; - } + if (_unlikely_(cached_printk_time < 0)) + cached_printk_time = read_boolean_file("/sys/module/printk/parameters/time") > 0; return cached_printk_time; } diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index 5f01bfae01651..b784d26c5baf6 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -9,9 +9,7 @@ #include "sd-dlopen.h" -#include "alloc-util.h" #include "fileio.h" -#include "parse-util.h" static void *libapparmor_dl = NULL; @@ -31,18 +29,13 @@ bool mac_apparmor_use(void) { if (cached_use >= 0) return cached_use; - _cleanup_free_ char *p = NULL; - r = read_one_line_file("/sys/module/apparmor/parameters/enabled", &p); + r = read_boolean_file("/sys/module/apparmor/parameters/enabled"); if (r < 0) { if (r != -ENOENT) - log_debug_errno(r, "Failed to read /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); + log_debug_errno(r, "Failed to read and parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); return (cached_use = false); } - - r = parse_boolean(p); - if (r < 0) - log_debug_errno(r, "Failed to parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); - if (r <= 0) + if (r == 0) return (cached_use = false); if (dlopen_libapparmor(LOG_DEBUG) < 0) From 412b2d310137b46d7273ab76ef79f8767704c850 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 05:02:01 -0700 Subject: [PATCH 1548/2155] core: implement ScopeContext/Runtime for io.systemd.Unit.List + tests Add varlink context and runtime builders for .scope units: ScopeContext: OOMPolicy (enum), RuntimeMaxUSec, RuntimeRandomizedExtraUSec, TimeoutStopUSec ScopeRuntime: Result (ScopeResult enum) Both OOMPolicy and ScopeResult are exposed as proper varlink enum types. Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-scope.c | 26 +++++++++++++++ src/core/varlink-scope.h | 7 ++++ src/core/varlink-unit.c | 3 ++ src/shared/varlink-io.systemd.Unit.c | 37 ++++++++++++++++++++-- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/test-varlink-idl-unit.c | 5 +++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 6 ++++ 8 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/core/varlink-scope.c create mode 100644 src/core/varlink-scope.h diff --git a/src/core/meson.build b/src/core/meson.build index 0dae1e50cc9de..796669814bf35 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -74,6 +74,7 @@ libcore_sources = files( 'varlink-metrics.c', 'varlink-mount.c', 'varlink-path.c', + 'varlink-scope.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-scope.c b/src/core/varlink-scope.c new file mode 100644 index 0000000000000..d22a2da5701bb --- /dev/null +++ b/src/core/varlink-scope.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "scope.h" +#include "varlink-scope.h" + +int scope_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Scope *s = ASSERT_PTR(SCOPE(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("OOMPolicy", oom_policy_to_string(s->oom_policy)), + JSON_BUILD_PAIR_FINITE_USEC("RuntimeMaxUSec", s->runtime_max_usec), + JSON_BUILD_PAIR_FINITE_USEC("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStopUSec", s->timeout_stop_usec)); +} + +int scope_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Scope *s = ASSERT_PTR(SCOPE(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", scope_result_to_string(s->result))); +} diff --git a/src/core/varlink-scope.h b/src/core/varlink-scope.h new file mode 100644 index 0000000000000..0e941f873dd0f --- /dev/null +++ b/src/core/varlink-scope.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int scope_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int scope_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 ca357cf899e8a..30b912ddf1036 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -30,6 +30,7 @@ #include "varlink-kill.h" #include "varlink-mount.h" #include "varlink-path.h" +#include "varlink-scope.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -164,6 +165,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void [UNIT_AUTOMOUNT] = automount_context_build_json, [UNIT_MOUNT] = mount_context_build_json, [UNIT_PATH] = path_context_build_json, + [UNIT_SCOPE] = scope_context_build_json, [UNIT_SERVICE] = service_context_build_json, }; @@ -331,6 +333,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void [UNIT_AUTOMOUNT] = automount_runtime_build_json, [UNIT_MOUNT] = mount_runtime_build_json, [UNIT_PATH] = path_runtime_build_json, + [UNIT_SCOPE] = scope_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 4e8e4f04b7fa2..83a728e2cc680 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1016,6 +1016,31 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Result of path operation"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, PathResult, 0)); +/* ScopeContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.scope.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ScopeContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#OOMPolicy="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OOMPolicy, OOMPolicy, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#RuntimeMaxSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#RuntimeRandomizedExtraSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeRandomizedExtraUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#TimeoutStopSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStopUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ScopeResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(oom_kill)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ScopeRuntime, + SD_VARLINK_FIELD_COMMENT("Result of scope operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ScopeResult, 0)); + /* Service-specific types */ /* Keep in sync with service_type_table[] in src/core/service.c */ @@ -1210,7 +1235,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The mount context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The path context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The scope context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1386,7 +1413,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The mount runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The path runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1553,6 +1582,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_PathContext, &vl_type_PathResult, &vl_type_PathRuntime, + &vl_type_OOMPolicy, + &vl_type_ScopeContext, + &vl_type_ScopeResult, + &vl_type_ScopeRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index bc43d7b59fba8..54d2dad1c1339 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -32,6 +32,7 @@ extern const sd_varlink_symbol vl_type_AutomountResult; extern const sd_varlink_symbol vl_type_MountResult; extern const sd_varlink_symbol vl_type_PathType; extern const sd_varlink_symbol vl_type_PathResult; +extern const sd_varlink_symbol vl_type_ScopeResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; extern const sd_varlink_symbol vl_type_ServiceType; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 6c0f834a7a360..5e9ce4ce06122 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -8,7 +8,9 @@ #include "numa-util.h" #include "path.h" #include "process-util.h" +#include "scope.h" #include "service.h" +#include "swap.h" #include "tests.h" #include "test-varlink-idl-util.h" #include "unit.h" @@ -70,6 +72,9 @@ TEST(unit_enums_idl) { /* PathRuntime enums */ TEST_IDL_ENUM(PathResult, path_result, vl_type_PathResult); + /* ScopeRuntime enums */ + TEST_IDL_ENUM(ScopeResult, scope_result, vl_type_ScopeResult); + /* 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 1db22b85c118b..900eda1138c25 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -252,6 +252,12 @@ test -n "$path_id" path_params=$(jq -cn --arg name "$path_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' +# test for ScopeContext/Runtime +scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$scope_id" +scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From ed4740a0dc63d1f85e48d6959b8e077f63d1eef6 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 05:05:24 -0700 Subject: [PATCH 1549/2155] core: implement SwapContext/Runtime for io.systemd.Unit.List + tests Add varlink context and runtime builders for .swap units: SwapContext: What, Priority, Options, TimeoutUSec, ExecActivate, ExecDeactivate SwapRuntime: ControlPID, Result, CleanResult, UID, GID (SwapResult enum) SwapResult is exposed as a proper varlink enum type. Runtime follows the same pattern as MountRuntime (ControlPID, Result, CleanResult, ref UID/GID). The integration test is conditional since swap units may not be present on all systems. Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-swap.c | 35 +++++++++++++++ src/core/varlink-swap.h | 7 +++ src/core/varlink-unit.c | 3 ++ src/shared/varlink-io.systemd.Unit.c | 51 +++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/test-varlink-idl-unit.c | 3 ++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 7 +++ 8 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/core/varlink-swap.c create mode 100644 src/core/varlink-swap.h diff --git a/src/core/meson.build b/src/core/meson.build index 796669814bf35..e59a683c9a50b 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -75,6 +75,7 @@ libcore_sources = files( 'varlink-mount.c', 'varlink-path.c', 'varlink-scope.c', + 'varlink-swap.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-swap.c b/src/core/varlink-swap.c new file mode 100644 index 0000000000000..055e73a9cda40 --- /dev/null +++ b/src/core/varlink-swap.c @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "swap.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-swap.h" + +int swap_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Swap *s = ASSERT_PTR(SWAP(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_STRING("What", s->what), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Priority", swap_get_priority(s)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Options", swap_get_options(s)), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", s->timeout_usec), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecActivate", exec_command_build_json, &s->exec_command[SWAP_EXEC_ACTIVATE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecDeactivate", exec_command_build_json, &s->exec_command[SWAP_EXEC_DEACTIVATE])); +} + +int swap_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Swap *s = ASSERT_PTR(SWAP(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", swap_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("CleanResult", swap_result_to_string(s->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-swap.h b/src/core/varlink-swap.h new file mode 100644 index 0000000000000..f76aa63fb1582 --- /dev/null +++ b/src/core/varlink-swap.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int swap_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int swap_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 30b912ddf1036..fa017e955157f 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -31,6 +31,7 @@ #include "varlink-mount.h" #include "varlink-path.h" #include "varlink-scope.h" +#include "varlink-swap.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -167,6 +168,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void [UNIT_PATH] = path_context_build_json, [UNIT_SCOPE] = scope_context_build_json, [UNIT_SERVICE] = service_context_build_json, + [UNIT_SWAP] = swap_context_build_json, }; return sd_json_buildo( @@ -334,6 +336,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void [UNIT_MOUNT] = mount_runtime_build_json, [UNIT_PATH] = path_runtime_build_json, [UNIT_SCOPE] = scope_runtime_build_json, + [UNIT_SWAP] = swap_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 83a728e2cc680..4d77e64a740db 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1041,6 +1041,46 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Result of scope operation"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ScopeResult, 0)); +/* SwapContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.swap.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SwapContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.html#What="), + SD_VARLINK_DEFINE_FIELD(What, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.html#Priority="), + SD_VARLINK_DEFINE_FIELD(Priority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.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.swap.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Activate command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecActivate, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Deactivate command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecDeactivate, ExecCommand, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SwapResult, + 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)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SwapRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current swap control process"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of swap operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, SwapResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, SwapResult, 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)); + /* Service-specific types */ /* Keep in sync with service_type_table[] in src/core/service.c */ @@ -1237,7 +1277,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The path context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The scope context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The swap context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1415,7 +1457,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The path runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1586,6 +1630,9 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ScopeContext, &vl_type_ScopeResult, &vl_type_ScopeRuntime, + &vl_type_SwapContext, + &vl_type_SwapResult, + &vl_type_SwapRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 54d2dad1c1339..230fc5c43eeab 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -33,6 +33,7 @@ extern const sd_varlink_symbol vl_type_MountResult; extern const sd_varlink_symbol vl_type_PathType; extern const sd_varlink_symbol vl_type_PathResult; extern const sd_varlink_symbol vl_type_ScopeResult; +extern const sd_varlink_symbol vl_type_SwapResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; extern const sd_varlink_symbol vl_type_ServiceType; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 5e9ce4ce06122..eba3a7d96a520 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -75,6 +75,9 @@ TEST(unit_enums_idl) { /* ScopeRuntime enums */ TEST_IDL_ENUM(ScopeResult, scope_result, vl_type_ScopeResult); + /* SwapRuntime enums */ + TEST_IDL_ENUM(SwapResult, swap_result, vl_type_SwapResult); + /* 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 900eda1138c25..4d9649f2d702d 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -258,6 +258,13 @@ test -n "$scope_id" scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' +# test for SwapContext/Runtime (swap units may not be present on all systems) +swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +if test -n "$swap_id"; then + swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' +fi # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From ae619b12240b5eff5d580a5def3a7fc4b3cbb9dc Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 05:12:48 -0700 Subject: [PATCH 1550/2155] core: implement TimerContext/Runtime for io.systemd.Unit.List + tests Add varlink context and runtime builders for .timer units: TimerContext: MonotonicTimers (with MonotonicTimerBase enum), CalendarTimers, Unit, OnClockChange, OnTimezoneChange, AccuracyUSec, RandomizedDelayUSec, RandomizedOffsetUSec, FixedRandomDelay, Persistent, WakeSystem, RemainAfterElapse, DeferReactivation TimerRuntime: Result (TimerResult enum), NextElapseUSecRealtime, NextElapseUSecMonotonic, LastTriggerUSec MonotonicTimerSpec and CalendarTimerSpec are separate types since they have different value types (int vs string). MonotonicTimerBase and TimerResult are proper varlink enum types. Compared to the old io-systemd-Unit-List branch, this adds RandomizedOffsetUSec and DeferReactivation (both present in D-Bus but previously missing), and adds full runtime fields. Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-timer.c | 77 ++++++++++++++++++++++ src/core/varlink-timer.h | 7 ++ src/core/varlink-unit.c | 3 + src/shared/varlink-io.systemd.Unit.c | 77 +++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 2 + src/test/test-varlink-idl-unit.c | 11 ++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 6 ++ 8 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 src/core/varlink-timer.c create mode 100644 src/core/varlink-timer.h diff --git a/src/core/meson.build b/src/core/meson.build index e59a683c9a50b..eef53be94bc75 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -76,6 +76,7 @@ libcore_sources = files( 'varlink-path.c', 'varlink-scope.c', 'varlink-swap.c', + 'varlink-timer.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-timer.c b/src/core/varlink-timer.c new file mode 100644 index 0000000000000..e7858fa1d25bf --- /dev/null +++ b/src/core/varlink-timer.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "calendarspec.h" +#include "json-util.h" +#include "timer.h" +#include "varlink-timer.h" + +static int timers_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + TimerValue *values = userdata; + int r; + + assert(ret); + + LIST_FOREACH(value, value, values) { + _cleanup_free_ char *base_name = NULL; + + base_name = timer_base_to_usec_string(value->base); + if (!base_name) + return -ENOMEM; + + if (value->base == TIMER_CALENDAR) { + _cleanup_free_ char *calendar = NULL; + + r = calendar_spec_to_string(value->calendar_spec, &calendar); + if (r < 0) + return log_debug_errno(r, "Failed to convert calendar spec into string: %m"); + + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("base", base_name), + SD_JSON_BUILD_PAIR_STRING("calendar", calendar)); + } else + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("base", base_name), + SD_JSON_BUILD_PAIR_UNSIGNED("usec", value->value)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Timer *t = ASSERT_PTR(TIMER(userdata)); + Unit *trigger = UNIT_TRIGGER(UNIT(t)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Timers", timers_build_json, t->values), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", trigger ? trigger->id : NULL), + SD_JSON_BUILD_PAIR_BOOLEAN("OnClockChange", t->on_clock_change), + SD_JSON_BUILD_PAIR_BOOLEAN("OnTimezoneChange", t->on_timezone_change), + JSON_BUILD_PAIR_FINITE_USEC("AccuracyUSec", t->accuracy_usec), + JSON_BUILD_PAIR_FINITE_USEC("RandomizedDelayUSec", t->random_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC("RandomizedOffsetUSec", t->random_offset_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("FixedRandomDelay", t->fixed_random_delay), + SD_JSON_BUILD_PAIR_BOOLEAN("Persistent", t->persistent), + SD_JSON_BUILD_PAIR_BOOLEAN("WakeSystem", t->wake_system), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterElapse", t->remain_after_elapse), + SD_JSON_BUILD_PAIR_BOOLEAN("DeferReactivation", t->defer_reactivation)); +} + +int timer_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Timer *t = ASSERT_PTR(TIMER(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", timer_result_to_string(t->result)), + JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecRealtime", t->next_elapse_realtime), + JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecMonotonic", timer_next_elapse_monotonic(t)), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("LastTriggerUSec", &t->last_trigger)); +} diff --git a/src/core/varlink-timer.h b/src/core/varlink-timer.h new file mode 100644 index 0000000000000..e96387a55a9be --- /dev/null +++ b/src/core/varlink-timer.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int timer_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 fa017e955157f..2e3b1dd1961b8 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -32,6 +32,7 @@ #include "varlink-path.h" #include "varlink-scope.h" #include "varlink-swap.h" +#include "varlink-timer.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -169,6 +170,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void [UNIT_SCOPE] = scope_context_build_json, [UNIT_SERVICE] = service_context_build_json, [UNIT_SWAP] = swap_context_build_json, + [UNIT_TIMER] = timer_context_build_json, }; return sd_json_buildo( @@ -337,6 +339,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void [UNIT_PATH] = path_runtime_build_json, [UNIT_SCOPE] = scope_runtime_build_json, [UNIT_SWAP] = swap_runtime_build_json, + [UNIT_TIMER] = timer_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 4d77e64a740db..883154b2f1951 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1081,6 +1081,70 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Reference GID"), SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +/* TimerContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + TimerBase, + SD_VARLINK_DEFINE_ENUM_VALUE(OnActiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnBootUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnStartupUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitActiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitInactiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnCalendar)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerSpec, + SD_VARLINK_FIELD_COMMENT("Timer base type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(base, TimerBase, 0), + SD_VARLINK_FIELD_COMMENT("Timer value in microseconds (for monotonic timers)"), + SD_VARLINK_DEFINE_FIELD(usec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Calendar specification string (for calendar timers)"), + SD_VARLINK_DEFINE_FIELD(calendar, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnActiveSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timers, TimerSpec, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Unit="), + SD_VARLINK_DEFINE_FIELD(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="), + SD_VARLINK_DEFINE_FIELD(OnClockChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="), + SD_VARLINK_DEFINE_FIELD(OnTimezoneChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#AccuracySec="), + SD_VARLINK_DEFINE_FIELD(AccuracyUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedDelaySec="), + SD_VARLINK_DEFINE_FIELD(RandomizedDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedOffsetSec="), + SD_VARLINK_DEFINE_FIELD(RandomizedOffsetUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#FixedRandomDelay="), + SD_VARLINK_DEFINE_FIELD(FixedRandomDelay, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Persistent="), + SD_VARLINK_DEFINE_FIELD(Persistent, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#WakeSystem="), + SD_VARLINK_DEFINE_FIELD(WakeSystem, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RemainAfterElapse="), + SD_VARLINK_DEFINE_FIELD(RemainAfterElapse, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#DeferReactivation="), + SD_VARLINK_DEFINE_FIELD(DeferReactivation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + TimerResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerRuntime, + SD_VARLINK_FIELD_COMMENT("Result of timer operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, TimerResult, 0), + SD_VARLINK_FIELD_COMMENT("Next elapse time in realtime clock"), + SD_VARLINK_DEFINE_FIELD(NextElapseUSecRealtime, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Next elapse time in monotonic clock"), + SD_VARLINK_DEFINE_FIELD(NextElapseUSecMonotonic, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Last time the timer triggered"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LastTriggerUSec, Timestamp, SD_VARLINK_NULLABLE)); + /* Service-specific types */ /* Keep in sync with service_type_table[] in src/core/service.c */ @@ -1279,7 +1343,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The scope context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The swap context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The timer context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1459,7 +1525,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The timer runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1633,6 +1701,11 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_SwapContext, &vl_type_SwapResult, &vl_type_SwapRuntime, + &vl_type_TimerBase, + &vl_type_TimerSpec, + &vl_type_TimerContext, + &vl_type_TimerResult, + &vl_type_TimerRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 230fc5c43eeab..128170eaf4079 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -34,6 +34,8 @@ extern const sd_varlink_symbol vl_type_PathType; extern const sd_varlink_symbol vl_type_PathResult; extern const sd_varlink_symbol vl_type_ScopeResult; extern const sd_varlink_symbol vl_type_SwapResult; +extern const sd_varlink_symbol vl_type_TimerBase; +extern const sd_varlink_symbol vl_type_TimerResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; extern const sd_varlink_symbol vl_type_ServiceType; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index eba3a7d96a520..e03f5edff5aa2 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -13,6 +13,7 @@ #include "swap.h" #include "tests.h" #include "test-varlink-idl-util.h" +#include "timer.h" #include "unit.h" #include "varlink-idl-common.h" #include "varlink-io.systemd.Unit.h" @@ -78,6 +79,16 @@ TEST(unit_enums_idl) { /* SwapRuntime enums */ TEST_IDL_ENUM(SwapResult, swap_result, vl_type_SwapResult); + /* TimerContext enums */ + for (TimerBase b = 0; b < _TIMER_BASE_MAX; b++) { + _cleanup_free_ char *s = timer_base_to_usec_string(b); + assert_se(s); + test_enum_to_string_name(s, &vl_type_TimerBase); + } + + /* TimerRuntime enums */ + TEST_IDL_ENUM(TimerResult, timer_result, vl_type_TimerResult); + /* 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 4d9649f2d702d..ee5d762b5aa77 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -265,6 +265,12 @@ if test -n "$swap_id"; then varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' fi +# test for TimerContext/Runtime +timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$timer_id" +timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From cb3a1a1b2fcac82918aa2902f870f6aed6b966f1 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 7 May 2026 10:55:32 -0700 Subject: [PATCH 1551/2155] test: use jq // empty instead of grep -v null in Unit.List tests Replace `grep -v null` with jq's `// empty` alternative operator when filtering unit IDs. With `set -o pipefail`, `grep` returns 1 when no lines match, which aborts the script before conditional guards can run. The `// empty` operator suppresses null output directly in jq without risking a pipeline failure. --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index ee5d762b5aa77..3e78cf34e9e48 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -233,7 +233,7 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocat # 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) +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 // empty' | tail -n 1) test -n "$automount_id" # Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that # are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. @@ -241,19 +241,19 @@ automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | 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) +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 // empty' | tail -n 1) test -n "$mount_id" mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' # test for PathContext/Runtime -path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) test -n "$path_id" path_params=$(jq -cn --arg name "$path_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' # test for ScopeContext/Runtime -scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) test -n "$scope_id" scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' @@ -266,7 +266,7 @@ if test -n "$swap_id"; then varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' fi # test for TimerContext/Runtime -timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) test -n "$timer_id" timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' From 263335518b29d90cc3745414c02c3a9de20b2070 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 10 May 2026 21:24:26 +0200 Subject: [PATCH 1552/2155] clang-tidy: Drop unknown gcc compiler args clang-tidy recently gained support to allow dropping compiler args from the entries parsed from the compilation database. Let's make use of this to drop the two compiler args we use with gcc that clang doesn't support so we can run clang-tidy on meson build trees configured to use gcc without getting tons of false positives. --- .clang-tidy | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-tidy b/.clang-tidy index 82681fda39923..8d69a69cbf971 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -70,6 +70,7 @@ CheckOptions: ^getmntent$,mnt_table_next_fs(),libmount parser should be used instead ' misc-header-include-cycle.IgnoredFilesList: 'glib-2.0' +RemovedArgs: ['-fwide-exec-charset=UCS2', '-maccumulate-outgoing-args'] WarningsAsErrors: '*' ExcludeHeaderFilterRegex: 'blkid\.h|gmessages\.h|gstring\.h' HeaderFileExtensions: From f68fa99a0748383eb10339f22ad0be0ed825722a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 11 May 2026 12:58:13 +0100 Subject: [PATCH 1553/2155] TEST-67-INTEGRITY: pre-load crypto modules and skip unsupported algorithms The test occasionally fails on GHA CI when formatting with xxhash64 because dm-integrity's crypto_alloc_shash() -> request_module() path flakily fails to load the algorithm: [ 29.172664] TEST-67-INTEGRITY.sh[447]: + for a in crc32c crc32 xxhash64 sha1 sha256 [ 29.172664] TEST-67-INTEGRITY.sh[447]: + [[ xxhash64 == crc32 ]] [ 29.172664] TEST-67-INTEGRITY.sh[447]: + test_one xxhash64 0 [ 29.172664] TEST-67-INTEGRITY.sh[447]: + integritysetup format /dev/loop0 --batch-mode -I xxhash64 '' [ 29.223383] TEST-67-INTEGRITY.sh[1220]: device-mapper: reload ioctl on temporary-cryptsetup-fa8bebe3-1d87-4796-91e8-abc02c487bb5 (254:0) failed: No such file or directory [ 29.226916] kernel: device-mapper: table: 254:0: integrity: Invalid internal hash (-ENOENT) [ 29.227415] kernel: device-mapper: ioctl: error adding target to table [ 29.231586] TEST-67-INTEGRITY.sh[1220]: Cannot format integrity for device /dev/loop0. Preload each algorithm's crypto module before use, and skip algorithms that are not registered in /proc/crypto. Co-developed-by: Claude Opus 4.7 --- test/units/TEST-67-INTEGRITY.sh | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test/units/TEST-67-INTEGRITY.sh b/test/units/TEST-67-INTEGRITY.sh index 667fa56343401..a57d7d9db279f 100755 --- a/test/units/TEST-67-INTEGRITY.sh +++ b/test/units/TEST-67-INTEGRITY.sh @@ -2,8 +2,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later set -euxo pipefail -. /etc/os-release - DM_NAME="integrity_test" DM_NODE="/dev/mapper/${DM_NAME}" DM_SERVICE="systemd-integritysetup@${DM_NAME}.service" @@ -119,16 +117,23 @@ EOF } for a in crc32c crc32 xxhash64 sha1 sha256; do - if [[ "$a" == crc32 && "${ID_LIKE:-}" == alpine ]]; then - # crc32 is not supported on alpine/postmarketos ?? - # -------- - # [ 22.419458] TEST-67-INTEGRITY.sh[3085]: + integritysetup format /dev/loop0 --batch-mode -I crc32 '' - # [ 22.433168] kernel: device-mapper: table: 253:0: integrity: Invalid internal hash (-ENOENT) - # [ 22.433220] TEST-67-INTEGRITY.sh[3475]: device-mapper: reload ioctl on temporary-cryptsetup-6b3b80ef-6854-4102-8239-6360f15af0c3 (253:0) failed: No such file or directory - # [ 22.433220] TEST-67-INTEGRITY.sh[3475]: Cannot format integrity for device /dev/loop0. - # [ 22.433835] kernel: device-mapper: ioctl: error adding target to table - # -------- - continue; + # dm-integrity uses crypto_alloc_shash() which triggers request_module() + # for the underlying hash algorithm when needed. That auto-load has been + # observed to fail flakily in some test environments, leading to errors + # like: + # kernel: device-mapper: table: NNN:N: integrity: Invalid internal hash (-ENOENT) + # integritysetup: Cannot format integrity for device /dev/loopN. + # Try to load the kernel module ahead of time to avoid that. Failure is + # acceptable here: the algorithm might be built-in (no module to load) or + # genuinely unsupported, in which case the next check will skip it. + modprobe -q "crypto-$a" || : + + # Some algorithms are not supported on certain platforms (e.g. crc32 is + # missing on Alpine/postmarketOS). Skip them at runtime to avoid spurious + # failures. + if ! grep -q -E "^name\s+: $a\$" /proc/crypto; then + echo "Algorithm '$a' is not supported on this system, skipping." + continue fi test_one "$a" 0 From 6e7f59884ebc8c8e2205fafd4d789fb73b389029 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 11 May 2026 15:54:12 +0200 Subject: [PATCH 1554/2155] repart: make definitions varlink parameter actually optional The Varlink iterface said the definitions directory was mandatory, and so did the dispatch table. But that's nonsense, the code is completely fine to operate without (same as cmdline repart invocations): it will just use the standard definitions dir. Fix that. --- src/repart/repart.c | 14 +++++++------- src/shared/varlink-io.systemd.Repart.c | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 84aaf60b5f790..168092d43a8eb 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -10937,13 +10937,13 @@ static int vl_method_run( void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RunParameters, node), SD_JSON_NULLABLE }, - { "empty", SD_JSON_VARIANT_STRING, json_dispatch_empty_mode, offsetof(RunParameters, empty), SD_JSON_MANDATORY }, - { "seed", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(RunParameters, seed), SD_JSON_NULLABLE }, - { "dryRun", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, dry_run), SD_JSON_MANDATORY }, - { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_MANDATORY|SD_JSON_STRICT }, - { "deferPartitionsEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_empty), SD_JSON_NULLABLE }, - { "deferPartitionsFactoryReset", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_factory_reset), SD_JSON_NULLABLE }, + { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RunParameters, node), SD_JSON_NULLABLE }, + { "empty", SD_JSON_VARIANT_STRING, json_dispatch_empty_mode, offsetof(RunParameters, empty), SD_JSON_MANDATORY }, + { "seed", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(RunParameters, seed), SD_JSON_NULLABLE }, + { "dryRun", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, dry_run), SD_JSON_MANDATORY }, + { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_STRICT }, + { "deferPartitionsEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_empty), SD_JSON_NULLABLE }, + { "deferPartitionsFactoryReset", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_factory_reset), SD_JSON_NULLABLE }, {} }; diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index dbfb8d0360d2f..287af1f15024d 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -41,7 +41,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("The seed value to derive partition and file system UUIDs from"), SD_VARLINK_DEFINE_INPUT(seed, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files."), - SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY), + SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, automatically defer creation of all partitions whose label is \"empty\"."), SD_VARLINK_DEFINE_INPUT(deferPartitionsEmpty, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, automatically defer creation of all partitions which are marked for factory reset."), From 4409e52494d803426a365b6636a66fd2dfc70b62 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 8 May 2026 16:37:52 +0200 Subject: [PATCH 1555/2155] units: enable systemd-report-basic.socket by default In https://github.com/systemd/systemd/pull/41688 we merged metrics and facts for systemd-report. However while some metric sources are enabled by default (like `io.systemd.{Manager,Network}`) the `io.systemd.Basic` service is not enabled by default. This commit changes this and enables it by default. We could also enable the systemd-report-cgroup.socket but that sends a lot more data not sure that is a good default. --- presets/90-systemd.preset | 1 + test/units/TEST-74-AUX-UTILS.report.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index cd7afb5df2523..4a668fb8ce93b 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -31,6 +31,7 @@ enable systemd-networkd.service enable systemd-networkd-wait-online.service enable systemd-nsresourced.socket enable systemd-pstore.service +enable systemd-report-basic.socket enable systemd-resolved.service enable systemd-sysext.service enable systemd-timesyncd.service diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 61d2b10d0b7c6..7475978336f14 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -51,7 +51,7 @@ varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} # test io.systemd.Basic Metrics -systemctl start systemd-report-basic.socket +[[ "$(systemctl is-enabled systemd-report-basic.socket)" == enabled ]] varlinkctl info /run/systemd/report/io.systemd.Basic varlinkctl list-methods /run/systemd/report/io.systemd.Basic varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} From 6f3fa7177598cc586273ff4e3c753a5bf5bb104a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 15:00:19 +0200 Subject: [PATCH 1556/2155] vmspawn: Attach a USB keyboard in GUI mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EDK2's UsbKbDxe is the only driver that registers a default HII keyboard layout via the HII database protocol; the PS/2 driver does not. Adding a USB xHCI controller and usb-kbd in CONSOLE_GUI mode gives us a layout to query, which systemd-boot exports through the LoaderKeyboardLayout EFI variable — useful for exercising that codepath end-to-end. --- src/vmspawn/vmspawn.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 57b7697079ee4..f49b36af1e866 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2978,6 +2978,23 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + /* Attach a USB xHCI controller and a USB keyboard. We prefer USB over the implicit PS/2 + * keyboard so that EDK2's UsbKbDxe driver runs, which registers the default HII keyboard + * layout package — the PS/2 driver does not. That makes + * EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout() return a usable layout, which systemd-boot + * then exports via the LoaderKeyboardLayout EFI variable, which is useful for testing that + * codepath actually works. */ + r = qemu_config_section(config_file, "device", "xhci0", + "driver", "qemu-xhci"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "usb-kbd0", + "driver", "usb-kbd", + "bus", "xhci0.0"); + if (r < 0) + return r; + break; case CONSOLE_HEADLESS: From 033be1a41b5f75a3f2c8f4fe212512062bc4d5f3 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Mon, 11 May 2026 10:12:47 +0200 Subject: [PATCH 1557/2155] hwdb/keyboard: use vendor/product specific match for X+ Piccolo The controller is used in other devices, and some of these do have a separate keypad with enter key. Fixes: 7ae0a588154ad279deaa98f82c15470684189856 --- 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 23023740901ac..f965d2c6ec6fb 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2228,7 +2228,7 @@ evdev:atkbd:dmi:bvnTIMI*:bvr*:bd*:svnTIMI*:pnMiNoteBookPro*:* ########################################################### # X+ piccolo series 81X (Intel N305, possibly more) -evdev:input:b0011v0001p0001eAB83* +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnX-Plus.tech:pnX+ Piccolo:* KEYBOARD_KEY_9c=enter # KP_enter in the main area is wrong ########################################################### From 96a61b5483ef5650b41ccbfce9d5b229eece1adf Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Mon, 11 May 2026 10:10:06 +0200 Subject: [PATCH 1558/2155] Revert "hwdb/keyboard: fix KP_Enter on Clevo PA70ES" Fixed in previous commit. This reverts commit b7be9ccc8f4299269f72bde49e426a7a9d484da9. --- hwdb.d/60-keyboard.hwdb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index f965d2c6ec6fb..c9c0ea1db1afd 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -344,15 +344,6 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*BenQ*:pn*Joybook*R22*:* # Clevo ########################################################### -# Clevo PA70ES (Avell C73) -# The ITE keyboard controller firmware (version 0xAB83) is shared with -# the X+ piccolo. The piccolo rule (below) matches by input device ID -# and remaps KP_Enter to Enter since the piccolo has no numpad and its -# main Enter sends the wrong scan code. The PA70ES has a real numpad, -# so the remap breaks KP_Enter. This restores the correct mapping. -evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnPA70ES:* - KEYBOARD_KEY_9c=kpenter - evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnW65_67SZ:* KEYBOARD_KEY_a0=!mute KEYBOARD_KEY_a2=!playpause From 5173116fdb1198b40b4f0386d20ada5c4729e0a4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 19:17:53 +0200 Subject: [PATCH 1559/2155] curl-util: Fix clang-tidy warnings (#42030) Use the ref/unref macros to make sure the parameter names match Follow-up for 87cec65cae656f6ac2e702bd60dad6dd4fdae636 --- src/shared/curl-util.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 3436188952fbc..c35de1cb7b254 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -33,9 +33,7 @@ extern DLSYM_PROTOTYPE(curl_slist_free_all); typedef int (*curl_finished_t)(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata); int curl_glue_new(CurlGlue **glue, sd_event *event); -CurlGlue* curl_glue_ref(CurlGlue *glue); -CurlGlue* curl_glue_unref(CurlGlue *glue); - +DECLARE_TRIVIAL_REF_UNREF_FUNC(CurlGlue, curl_glue); DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); /* Build a CURL easy handle with sane defaults. The caller configures any @@ -62,9 +60,7 @@ int curl_glue_perform_async( CURL* curl_slot_get_easy(CurlSlot *slot); CurlGlue* curl_slot_get_glue(CurlSlot *slot); -CurlSlot* curl_slot_ref(CurlSlot *slot); -CurlSlot* curl_slot_unref(CurlSlot *slot); - +DECLARE_TRIVIAL_REF_UNREF_FUNC(CurlSlot, curl_slot); DEFINE_TRIVIAL_CLEANUP_FUNC(CurlSlot*, curl_slot_unref); struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; From 7b9d76cba72680ffad2672b7143d0dc1cea70231 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 13:03:49 +0000 Subject: [PATCH 1560/2155] boot,vconsole: Propagate UEFI HII keyboard layout to the OS UEFI firmware can report the currently-active keyboard layout via EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout(). The layout descriptor includes an RFC 4646 / BCP 47 language tag (e.g. "en-US"). Query this from sd-boot/sd-stub and write it to a new LoaderKeyboardLayout EFI variable, advertised through a new EFI_LOADER_FEATURE_KEYBOARD_LAYOUT feature bit. On the OS side, systemd-vconsole-setup reads the variable as a lowest-priority fallback for the console keymap. To map the BCP 47 tag to a vconsole keymap we extend /usr/share/systemd/kbd-model-map with an optional sixth column listing the comma-separated BCP 47 tags each row covers; a new find_vconsole_keymap_for_bcp47() helper walks the file, preferring an exact tag match and otherwise falling back to the row whose tag matches the input's primary subtag. Credentials, /etc/vconsole.conf, and vconsole.keymap= on the kernel command line continue to take precedence. bootctl status surfaces the new variable, printing the language tag or "n/a (not reported by firmware)" when sd-boot advertises the feature but the firmware HII database didn't expose a layout (common on QEMU without a USB keyboard, since EDK2's PS/2 driver does not register an HII keyboard layout). --- docs/BOOT_LOADER_INTERFACE.md | 13 +++ man/systemd-vconsole-setup.service.xml | 14 +++ man/vconsole.conf.xml | 7 ++ src/boot/boot.c | 1 + src/boot/export-vars.c | 10 ++ src/boot/hii.c | 80 ++++++++++++++ src/boot/hii.h | 9 ++ src/boot/meson.build | 1 + src/boot/proto/hii-database.h | 126 +++++++++++++++++++++ src/bootctl/bootctl-status.c | 80 +++++++------- src/fundamental/efivars-fundamental.h | 1 + src/locale/kbd-model-map | 147 +++++++++++++------------ src/shared/vconsole-util.c | 77 +++++++++++++ src/shared/vconsole-util.h | 2 + src/vconsole/vconsole-setup.c | 37 ++++++- 15 files changed, 494 insertions(+), 111 deletions(-) create mode 100644 src/boot/hii.c create mode 100644 src/boot/hii.h create mode 100644 src/boot/proto/hii-database.h diff --git a/docs/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md index 5c2e74f29011d..36380f38f3328 100644 --- a/docs/BOOT_LOADER_INTERFACE.md +++ b/docs/BOOT_LOADER_INTERFACE.md @@ -143,6 +143,8 @@ Variables will be listed below using the Linux efivarfs naming, * `1 << 18` → The boot loader reports active TPM2 PCR banks in the EFI variable `LoaderTpm2ActivePcrBanks-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * `1 << 19` → The boot loader supports the `LoaderEntryPreferred` variable when set. + * `1 << 20` → The boot loader reports the firmware-configured keyboard layout in the + EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * The EFI variable `LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` contains binary random data, @@ -171,6 +173,17 @@ Variables will be listed below using the Linux efivarfs naming, the TCG EFI ProtocolSpecification for TPM 2.0 as `EFI_TCG2_BOOT_HASH_ALG_*`. If no TPM2 support or no active banks were detected, will be set to `0`. +* The EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` + contains the RFC 4646 (BCP 47) language tag of the currently-active keyboard + layout as reported by the UEFI HII database (e.g. `en-US`, `de-DE`). + It is formatted as a NUL-terminated UTF-16 string. + The boot loader sets this variable from the layout returned by + `EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout()`, + if that protocol is implemented by the firmware. + Userspace (notably `systemd-vconsole-setup`) + uses this as a lowest-priority fallback keyboard layout + when no explicit configuration is provided. + If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze` will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set, systemd will mount the ESP that was used for the boot to `/boot`, but only if diff --git a/man/systemd-vconsole-setup.service.xml b/man/systemd-vconsole-setup.service.xml index 87cb9e4777bb4..e6656eb578545 100644 --- a/man/systemd-vconsole-setup.service.xml +++ b/man/systemd-vconsole-setup.service.xml @@ -95,6 +95,20 @@ + + Firmware-provided keyboard layout + + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + systemd-vconsole-setup uses it as the lowest-priority fallback for the + keymap. The RFC 4646 / BCP 47 language tag reported by the firmware (e.g. de-DE) is + matched against the optional sixth column of /usr/share/systemd/kbd-model-map, + which lists the language tags each virtual-console keymap covers. Credentials, + /etc/vconsole.conf, and kernel command line options all override this + firmware-provided default. + + See Also diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml index e5e160cf3d55d..20b30d39948f4 100644 --- a/man/vconsole.conf.xml +++ b/man/vconsole.conf.xml @@ -56,6 +56,13 @@ might be checked for configuration of the virtual console as well, however only as fallback. + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + it is used as the lowest-priority fallback for KEYMAP=. + Any setting from credentials, /etc/vconsole.conf, or the kernel + command line overrides it. + /etc/vconsole.conf is usually created and updated using systemd-localed.service8. diff --git a/src/boot/boot.c b/src/boot/boot.c index a2a1becc9aaa0..8660814aaebd6 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -3266,6 +3266,7 @@ static void export_loader_variables( EFI_LOADER_FEATURE_TYPE1_UKI | EFI_LOADER_FEATURE_TYPE1_UKI_URL | EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS | + EFI_LOADER_FEATURE_KEYBOARD_LAYOUT | 0; assert(loaded_image); diff --git a/src/boot/export-vars.c b/src/boot/export-vars.c index 5c037bdd25235..aa49666f57ce4 100644 --- a/src/boot/export-vars.c +++ b/src/boot/export-vars.c @@ -3,6 +3,7 @@ #include "device-path-util.h" #include "efi-efivars.h" #include "export-vars.h" +#include "hii.h" #include "measure.h" #include "part-discovery.h" #include "url-discovery.h" @@ -60,4 +61,13 @@ void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { s = xasprintf("0x%08x", active_pcr_banks); efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderTpm2ActivePcrBanks", s, 0); } + + /* Report the firmware's currently-active HII keyboard layout (as an RFC 4646 language tag, e.g. + * "de-DE"), so the OS can pick a matching console keymap. Best-effort: many firmwares do not + * implement the HII database protocol or expose no keyboard layout. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", /* ret_data= */ NULL, /* ret_size= */ NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *lang = hii_query_keyboard_layout_language(); + if (lang) + efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", lang, /* flags= */ 0); + } } diff --git a/src/boot/hii.c b/src/boot/hii.c new file mode 100644 index 0000000000000..84bf299c4247b --- /dev/null +++ b/src/boot/hii.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-log.h" +#include "hii.h" +#include "proto/hii-database.h" +#include "util.h" + +char16_t *hii_query_keyboard_layout_language(void) { + EFI_HII_DATABASE_PROTOCOL *hii_db = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_HII_DATABASE_PROTOCOL), /* Registration= */ NULL, (void **) &hii_db); + if (err != EFI_SUCCESS) { + log_debug_status(err, "HII database protocol not available, ignoring: %m"); + return NULL; + } + + /* First call sizes the layout. We pass length=0 / buffer=NULL and expect EFI_BUFFER_TOO_SMALL. */ + uint16_t length = 0; + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, /* KeyboardLayout= */ NULL); + if (err != EFI_BUFFER_TOO_SMALL) { + log_debug_status(err, "Initial GetKeyboardLayout did not report required buffer size, ignoring: %m"); + return NULL; + } + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u is smaller than the header, ignoring.", length); + return NULL; + } + + _cleanup_free_ EFI_HII_KEYBOARD_LAYOUT *layout = xmalloc(length); + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, layout); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to retrieve current keyboard layout, ignoring: %m"); + return NULL; + } + + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u shrank below the header, ignoring.", length); + return NULL; + } + + if (layout->LayoutLength != length) { + log_debug("Keyboard layout reports inconsistent LayoutLength %u vs %u, ignoring.", + layout->LayoutLength, length); + return NULL; + } + + uint32_t off = layout->LayoutDescriptorStringOffset; + if (off > length || length - off < sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) { + log_debug("Keyboard layout descriptor string offset %u out of bounds (length %u), ignoring.", + off, length); + return NULL; + } + + const EFI_DESCRIPTION_STRING_BUNDLE *bundle = (const EFI_DESCRIPTION_STRING_BUNDLE *) ((const uint8_t *) layout + off); + if (bundle->DescriptionCount == 0) { + log_debug("Keyboard layout has no description strings, ignoring."); + return NULL; + } + + /* Walk Strings[] looking for the U+0020 that terminates the first language tag. */ + size_t max_chars = (length - off - sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) / sizeof(char16_t); + size_t n; + for (n = 0; n < max_chars; n++) + if (bundle->Strings[n] == u' ') + break; + if (n == max_chars) { + log_debug("Keyboard layout language tag is not terminated by a space, ignoring."); + return NULL; + } + if (n == 0) { + log_debug("Keyboard layout language tag is empty, ignoring."); + return NULL; + } + + char16_t *s = xnew(char16_t, n + 1); + memcpy(s, bundle->Strings, n * sizeof(char16_t)); + s[n] = u'\0'; + return s; +} diff --git a/src/boot/hii.h b/src/boot/hii.h new file mode 100644 index 0000000000000..23ebcd303208f --- /dev/null +++ b/src/boot/hii.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +/* Queries the firmware's HII database for the currently-active keyboard layout and returns the RFC 4646 + * language tag (e.g. u"de-DE") embedded in the layout description bundle. Returns NULL if the protocol + * is not provided, the table is malformed, or no language tag is present. */ +char16_t *hii_query_keyboard_layout_language(void); diff --git a/src/boot/meson.build b/src/boot/meson.build index 29fb64efbee1b..1b8f94e58a247 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -320,6 +320,7 @@ libefi_sources = files( 'efi-string.c', 'export-vars.c', 'graphics.c', + 'hii.c', 'initrd.c', 'measure.c', 'part-discovery.c', diff --git a/src/boot/proto/hii-database.h b/src/boot/proto/hii-database.h new file mode 100644 index 0000000000000..7284704f00e23 --- /dev/null +++ b/src/boot/proto/hii-database.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_HII_DATABASE_PROTOCOL_GUID \ + GUID_DEF(0xef9fc172, 0xa1b2, 0x4693, 0xb3, 0x27, 0x6d, 0x32, 0xfc, 0x41, 0x60, 0x42) + +typedef void *EFI_HII_HANDLE; + +typedef struct { + EFI_GUID PackageListGuid; + uint32_t PackageLength; +} EFI_HII_PACKAGE_LIST_HEADER; + +typedef struct _packed_ { + uint32_t LengthAndType; /* Length:24 | Type:8 (little-endian) */ +} EFI_HII_PACKAGE_HEADER; + +typedef size_t EFI_HII_DATABASE_NOTIFY_TYPE; + +typedef EFI_STATUS (EFIAPI *EFI_HII_DATABASE_NOTIFY)( + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_PACKAGE_HEADER *Package, + EFI_HII_HANDLE Handle, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType); + +typedef struct EFI_HII_DATABASE_PROTOCOL EFI_HII_DATABASE_PROTOCOL; + +struct EFI_HII_DATABASE_PROTOCOL { + EFI_STATUS (EFIAPI *NewPackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_PACKAGE_LIST_HEADER *PackageList, + EFI_HANDLE DriverHandle, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *RemovePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle); + + EFI_STATUS (EFIAPI *UpdatePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + EFI_HII_PACKAGE_LIST_HEADER *PackageList); + + EFI_STATUS (EFIAPI *ListPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + size_t *HandleBufferLength, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *ExportPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + size_t *BufferSize, + EFI_HII_PACKAGE_LIST_HEADER *Buffer); + + EFI_STATUS (EFIAPI *RegisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_DATABASE_NOTIFY PackageNotifyFn, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType, + EFI_HANDLE *NotifyHandle); + + EFI_STATUS (EFIAPI *UnregisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HANDLE NotificationHandle); + + EFI_STATUS (EFIAPI *FindKeyboardLayouts)( + EFI_HII_DATABASE_PROTOCOL *This, + uint16_t *KeyGuidBufferLength, + EFI_GUID *KeyGuidBuffer); + + EFI_STATUS (EFIAPI *GetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid, + uint16_t *KeyboardLayoutLength, + void *KeyboardLayout); + + EFI_STATUS (EFIAPI *SetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid); + + EFI_STATUS (EFIAPI *GetPackageListHandle)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE PackageListHandle, + EFI_HANDLE *DriverHandle); +}; + +/* EFI_HII_KEYBOARD_LAYOUT and EFI_KEY_DESCRIPTOR are packed: LayoutDescriptorStringOffset follows + * a 16-byte EFI_GUID at offset 2, so it is at offset 18 — *not* a natural 4-byte alignment. */ +typedef struct _packed_ { + uint16_t LayoutLength; + EFI_GUID Guid; + uint32_t LayoutDescriptorStringOffset; + uint8_t DescriptorCount; + /* EFI_KEY_DESCRIPTOR Descriptors[DescriptorCount] follows here, then at + * LayoutDescriptorStringOffset (from the start of this struct) the description-string bundle. */ +} EFI_HII_KEYBOARD_LAYOUT; + +typedef struct _packed_ { + uint32_t Key; + char16_t Unicode; + char16_t ShiftedUnicode; + char16_t AltGrUnicode; + char16_t ShiftedAltGrUnicode; + uint16_t Modifier; + uint16_t AffectedAttribute; +} EFI_KEY_DESCRIPTOR; + +/* The description-string bundle that LayoutDescriptorStringOffset points to. After DescriptionCount, + * each of the DescriptionCount entries is laid out as: + * + * CHAR16 Language[]; // RFC 4646 tag, terminated by the Space below (no NUL) + * CHAR16 Space; // U+0020 + * CHAR16 DescriptionString[]; // NUL-terminated UTF-16 description + * + * Despite what the UEFI spec text says, Language is encoded as UTF-16 (CHAR16) in practice — see EDK2 + * MdeModulePkg/Bus/Usb/UsbKbDxe/KeyBoard.h USB_KEYBOARD_LAYOUT_PACK_BIN. */ +typedef struct _packed_ { + uint16_t DescriptionCount; + char16_t Strings[]; +} EFI_DESCRIPTION_STRING_BUNDLE; diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 2c0eb4d1d00d4..4cd0e3aca0e75 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -320,7 +320,7 @@ static int efi_get_variable_path_and_warn(const char *variable, char **ret) { static void print_yes_no_line(bool first, bool good, const char *name) { printf("%s%s %s\n", - first ? " Features: " : " ", + first ? " Features: " : " ", COLOR_MARK_BOOL(good), name); } @@ -387,25 +387,26 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { uint64_t flag; const char *name; } loader_flags[] = { - { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, - { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, - { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, - { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, - { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, - { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, - { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, - { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, - { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, - { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, - { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, - { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, - { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, - { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, - { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, - { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, - { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, + { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, + { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, + { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, + { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, + { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, + { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, + { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, + { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, + { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, + { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, + { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, + { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, + { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, + { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, + { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, + { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_KEYBOARD_LAYOUT, "Loader reports firmware keyboard layout" }, }; static const struct { uint64_t flag; @@ -426,7 +427,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, *current_entry = NULL, *oneshot_entry = NULL, *preferred_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, - *sysfail_reason = NULL; + *sysfail_reason = NULL, *keyboard_layout = NULL; uint64_t loader_features = 0, stub_features = 0; int have; @@ -444,6 +445,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &sysfail_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderSysFailReason"), &sysfail_reason); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &keyboard_layout); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -503,7 +505,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { if (loader) { printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); @@ -521,38 +523,42 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_partition_uuid)); } else if (loader_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (loader_path) - printf(" Loader: %s%s%s/%s%s\n", + printf(" Loader: %s%s%s/%s%s\n", glyph(GLYPH_TREE_RIGHT), ansi_grey(), arg_esp_path, ansi_normal(), loader_path); if (loader_url) - printf(" Net Boot URL: %s\n", loader_url); + printf(" Net Boot URL: %s\n", loader_url); + + if (FLAGS_SET(loader_features, EFI_LOADER_FEATURE_KEYBOARD_LAYOUT)) + printf("Keyboard Layout: %s\n", + keyboard_layout ?: "n/a (not reported by firmware)"); if (sysfail_entry) - printf("SysFail Reason: %s\n", sysfail_reason); + printf(" SysFail Reason: %s\n", sysfail_reason); if (current_entry) - printf(" Current Entry: %s\n", current_entry); + printf(" Current Entry: %s\n", current_entry); if (preferred_entry) - printf(" Preferred Entry: %s\n", preferred_entry); + printf("Preferred Entry: %s\n", preferred_entry); if (default_entry) - printf(" Default Entry: %s\n", default_entry); + printf(" Default Entry: %s\n", default_entry); if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry)) - printf(" OneShot Entry: %s\n", oneshot_entry); + printf(" OneShot Entry: %s\n", oneshot_entry); if (sysfail_entry) - printf(" SysFail Entry: %s\n", sysfail_entry); + printf(" SysFail Entry: %s\n", sysfail_entry); printf("\n"); } if (stub) { printf("%sCurrent Stub:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); @@ -573,16 +579,16 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { SD_ID128_FORMAT_VAL(esp_uuid), SD_ID128_FORMAT_VAL(xbootldr_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(stub_partition_uuid)); } else if (stub_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (stub_path) - printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); + printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); if (stub_url) - printf(" Net Boot URL: %s\n", stub_url); + printf(" Net Boot URL: %s\n", stub_url); printf("\n"); } diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 15be52119a0a2..fea23fa29c182 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -29,6 +29,7 @@ #define EFI_LOADER_FEATURE_TYPE1_UKI_URL (UINT64_C(1) << 17) #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS (UINT64_C(1) << 18) #define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19) +#define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) diff --git a/src/locale/kbd-model-map b/src/locale/kbd-model-map index 612f6d749a76f..c0ef480530ac5 100644 --- a/src/locale/kbd-model-map +++ b/src/locale/kbd-model-map @@ -1,73 +1,76 @@ # Originally generated from system-config-keyboard's model list. -# consolelayout xlayout xmodel xvariant xoptions -sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -nl nl pc105 - terminate:ctrl_alt_bksp -mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -trq tr pc105 - terminate:ctrl_alt_bksp -uk gb pc105 - terminate:ctrl_alt_bksp -is-latin1 is pc105 - terminate:ctrl_alt_bksp -de de pc105 - terminate:ctrl_alt_bksp -la-latin1 latam pc105 - terminate:ctrl_alt_bksp -us us pc105+inet - terminate:ctrl_alt_bksp -ko kr pc105 - terminate:ctrl_alt_bksp -ro-std ro pc105 std terminate:ctrl_alt_bksp -de-latin1 de pc105 - terminate:ctrl_alt_bksp -slovene si pc105 - terminate:ctrl_alt_bksp -hu hu pc105 - terminate:ctrl_alt_bksp -jp106 jp jp106 - terminate:ctrl_alt_bksp -croat hr pc105 - terminate:ctrl_alt_bksp -it2 it pc105 - terminate:ctrl_alt_bksp -hu101 hu pc105 qwerty terminate:ctrl_alt_bksp -sr-latin rs pc105 latin terminate:ctrl_alt_bksp -fi fi pc105 - terminate:ctrl_alt_bksp -fr_CH ch pc105 fr terminate:ctrl_alt_bksp -dk-latin1 dk pc105 - terminate:ctrl_alt_bksp -fr fr pc105 - terminate:ctrl_alt_bksp -it it pc105 - terminate:ctrl_alt_bksp -ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -fr-latin1 fr pc105 - terminate:ctrl_alt_bksp -sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -be-latin1 be pc105 - terminate:ctrl_alt_bksp -dk dk pc105 - terminate:ctrl_alt_bksp -fr-pc fr pc105 - terminate:ctrl_alt_bksp -bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -it-ibm it pc105 - terminate:ctrl_alt_bksp -cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -br-abnt2 br abnt2 - terminate:ctrl_alt_bksp -ro ro pc105 - terminate:ctrl_alt_bksp -us-acentos us pc105 intl terminate:ctrl_alt_bksp -pt-latin1 pt pc105 - terminate:ctrl_alt_bksp -ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp -tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp -de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp -no no pc105 - terminate:ctrl_alt_bksp -bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -dvorak us pc105 dvorak terminate:ctrl_alt_bksp -dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp -ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp -pl2 pl pc105 - terminate:ctrl_alt_bksp -es es pc105 - terminate:ctrl_alt_bksp -ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp -ie ie pc105 - terminate:ctrl_alt_bksp -et ee pc105 - terminate:ctrl_alt_bksp -sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp -sk-qwertz sk pc105 - terminate:ctrl_alt_bksp -fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp -fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp -cf ca pc105 - terminate:ctrl_alt_bksp -sv-latin1 se pc105 - terminate:ctrl_alt_bksp -sr-cy rs pc105 - terminate:ctrl_alt_bksp -gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -il il pc105 - terminate:ctrl_alt_bksp -kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -lt.baltic lt pc105 - terminate:ctrl_alt_bksp -lt.l4 lt pc105 - terminate:ctrl_alt_bksp -lt lt pc105 - terminate:ctrl_alt_bksp -khmer kh,us pc105 - terminate:ctrl_alt_bksp -es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp -lv lv pc105 apostrophe terminate:ctrl_alt_bksp -lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp -ge ge,us pc105 - terminate:ctrl_alt_bksp +# The sixth column is an optional comma-separated list of RFC 4646 / BCP 47 +# language tags the row matches; used to map a firmware-reported keyboard +# layout to a vconsole keymap. Use "-" or omit when no tags apply. +# consolelayout xlayout xmodel xvariant xoptions bcp47 +sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp de-CH +nl nl pc105 - terminate:ctrl_alt_bksp nl-NL,nl +mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll mk-MK,mk +trq tr pc105 - terminate:ctrl_alt_bksp tr-TR,tr +uk gb pc105 - terminate:ctrl_alt_bksp en-GB +is-latin1 is pc105 - terminate:ctrl_alt_bksp is-IS,is +de de pc105 - terminate:ctrl_alt_bksp de-DE,de-AT,de +la-latin1 latam pc105 - terminate:ctrl_alt_bksp es-419,es-MX,es-AR,es-CO,es-CL,es-PE,es-VE +us us pc105+inet - terminate:ctrl_alt_bksp en-US,en +ko kr pc105 - terminate:ctrl_alt_bksp ko-KR,ko +ro-std ro pc105 std terminate:ctrl_alt_bksp - +de-latin1 de pc105 - terminate:ctrl_alt_bksp - +slovene si pc105 - terminate:ctrl_alt_bksp sl-SI,sl +hu hu pc105 - terminate:ctrl_alt_bksp hu-HU,hu +jp106 jp jp106 - terminate:ctrl_alt_bksp ja-JP,ja +croat hr pc105 - terminate:ctrl_alt_bksp hr-HR,hr +it2 it pc105 - terminate:ctrl_alt_bksp - +hu101 hu pc105 qwerty terminate:ctrl_alt_bksp - +sr-latin rs pc105 latin terminate:ctrl_alt_bksp sr-Latn-RS,sr-Latn +fi fi pc105 - terminate:ctrl_alt_bksp fi-FI,fi +fr_CH ch pc105 fr terminate:ctrl_alt_bksp fr-CH +dk-latin1 dk pc105 - terminate:ctrl_alt_bksp da-DK,da +fr fr pc105 - terminate:ctrl_alt_bksp fr-FR,fr +it it pc105 - terminate:ctrl_alt_bksp it-IT,it-CH,it +ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll uk-UA,uk +fr-latin1 fr pc105 - terminate:ctrl_alt_bksp - +sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp - +be-latin1 be pc105 - terminate:ctrl_alt_bksp fr-BE,nl-BE +dk dk pc105 - terminate:ctrl_alt_bksp - +fr-pc fr pc105 - terminate:ctrl_alt_bksp - +bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll bg-BG,bg +it-ibm it pc105 - terminate:ctrl_alt_bksp - +cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll cs-CZ,cs +cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +br-abnt2 br abnt2 - terminate:ctrl_alt_bksp pt-BR +ro ro pc105 - terminate:ctrl_alt_bksp ro-RO,ro +us-acentos us pc105 intl terminate:ctrl_alt_bksp - +pt-latin1 pt pc105 - terminate:ctrl_alt_bksp pt-PT,pt +ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp - +tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp tg-TJ,tg +de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp - +no no pc105 - terminate:ctrl_alt_bksp nb-NO,nn-NO,no +bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +dvorak us pc105 dvorak terminate:ctrl_alt_bksp - +dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp - +ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll ru-RU,ru +cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp - +pl2 pl pc105 - terminate:ctrl_alt_bksp pl-PL,pl +es es pc105 - terminate:ctrl_alt_bksp es-ES,es +ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp - +ie ie pc105 - terminate:ctrl_alt_bksp en-IE,ga-IE,ga +et ee pc105 - terminate:ctrl_alt_bksp et-EE,et +sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp - +sk-qwertz sk pc105 - terminate:ctrl_alt_bksp sk-SK,sk +fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp - +fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp - +cf ca pc105 - terminate:ctrl_alt_bksp fr-CA +sv-latin1 se pc105 - terminate:ctrl_alt_bksp sv-SE,sv +sr-cy rs pc105 - terminate:ctrl_alt_bksp sr-Cyrl-RS,sr-Cyrl,sr-RS,sr +gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll el-GR,el +by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll be-BY,be +il il pc105 - terminate:ctrl_alt_bksp he-IL,he +kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll kk-KZ,kk +lt.baltic lt pc105 - terminate:ctrl_alt_bksp - +lt.l4 lt pc105 - terminate:ctrl_alt_bksp - +lt lt pc105 - terminate:ctrl_alt_bksp lt-LT,lt +khmer kh,us pc105 - terminate:ctrl_alt_bksp km-KH,km +es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp - +lv lv pc105 apostrophe terminate:ctrl_alt_bksp lv-LV,lv +lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp - +ge ge,us pc105 - terminate:ctrl_alt_bksp ka-GE,ka diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index 6e8c17561e8a7..aa156f4736fc6 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -578,6 +578,83 @@ int find_language_fallback(const char *lang, char **ret) { } } +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *fallback = NULL; + const char *map; + int r; + + /* Look up a vconsole keymap by RFC 4646 / BCP 47 language tag (e.g. "de-DE") using the optional + * sixth column of /usr/share/systemd/kbd-model-map. That column lists comma-separated tags the + * row matches. An exact (case-insensitive) tag match returns immediately; if no exact match + * exists, the first row whose tag matches the input's primary subtag wins. Returns 1 on match, + * 0 otherwise. */ + + assert(tag); + assert(ret); + + if (isempty(tag)) { + *ret = NULL; + return 0; + } + + size_t primary_len = strcspn(tag, "-"); + if (primary_len == 0) { + *ret = NULL; + return 0; + } + + map = systemd_kbd_model_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (unsigned n = 0;;) { + _cleanup_strv_free_ char **a = NULL, **tags = NULL; + + r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + /* The BCP 47 tag list is the optional 6th column. "-" / empty means "no tags". */ + if (strv_length(a) < 6 || isempty(a[5]) || streq(a[5], "-")) + continue; + + r = strv_split_full(&tags, a[5], ",", /* flags= */ 0); + if (r < 0) + return r; + + STRV_FOREACH(t, tags) { + if (strcaseeq(*t, tag)) { + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (exact match).", + a[0], tag); + + r = strdup_to(ret, a[0]); + if (r < 0) + return r; + + return 1; + } + if (!fallback && strlen(*t) == primary_len && !strchr(*t, '-') && strncaseeq(*t, tag, primary_len)) { + fallback = strdup(a[0]); + if (!fallback) + return -ENOMEM; + } + } + } + + if (!fallback) { + *ret = NULL; + return 0; + } + + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (primary subtag match).", fallback, tag); + *ret = TAKE_PTR(fallback); + return 1; +} + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { int r; diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h index 494bc6ea489e6..3d52ec9866ce8 100644 --- a/src/shared/vconsole-util.h +++ b/src/shared/vconsole-util.h @@ -39,4 +39,6 @@ typedef int (*X11VerifyCallback)(const X11Context *xc); int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret); int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret); + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 73bf240cf5130..c1bf2b0f4f5d9 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -15,6 +15,7 @@ #include "alloc-util.h" #include "creds-util.h" +#include "efivars.h" #include "env-file.h" #include "errno-util.h" #include "fd-util.h" @@ -30,6 +31,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "vconsole-util.h" typedef struct Context { char *keymap; @@ -72,6 +74,34 @@ static void context_merge_config( context_merge(dst, src, src_compat, font_unimap); } +static int context_read_efi(Context *c) { + _cleanup_(context_done) Context v = {}; + _cleanup_free_ char *tag = NULL; + int r; + + assert(c); + + if (!is_efi_boot()) + return 0; + + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable, ignoring: %m"); + + r = find_vconsole_keymap_for_bcp47(tag, &v.keymap); + if (r < 0) + return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s', ignoring: %m", tag); + if (r == 0) { + log_debug("No vconsole keymap matches firmware-provided keyboard layout '%s', ignoring.", tag); + return 0; + } + + context_merge_config(c, &v, /* src_compat= */ NULL); + return 0; +} + static int context_read_creds(Context *c) { _cleanup_(context_done) Context v = {}; int r; @@ -144,10 +174,13 @@ static int context_read_proc_cmdline(Context *c) { static void context_load_config(Context *c) { assert(c); - /* Load data from credentials (lowest priority) */ + /* Pick up the firmware-provided keyboard layout if any (lowest priority) */ + (void) context_read_efi(c); + + /* Load data from credentials */ (void) context_read_creds(c); - /* Load data from configuration file (middle priority) */ + /* Load data from configuration file */ (void) context_read_env(c); /* Let the kernel command line override /etc/vconsole.conf (highest priority) */ From 54776b5fcee5dc50f214fc7bba5faa8c29a74f00 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 13:42:53 +0200 Subject: [PATCH 1561/2155] vmspawn: Use builtin vdagent instead of spicevmc The builtin one also makes the clipboard and such work. spicevmc is only required for remote desktop use cases, so let's use the builtin one instead. --- src/vmspawn/vmspawn.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f49b36af1e866..27fa3501ee5f3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2965,9 +2965,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; r = qemu_config_section(config_file, "chardev", "vdagent", - "backend", "spicevmc", - "debug", "0", - "name", "vdagent"); + "backend", "qemu-vdagent", + "clipboard", "on", + "debug", "0"); if (r < 0) return r; From f0064f1b05886e47d947cac8b18efd89c45ec47f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 14:59:39 +0200 Subject: [PATCH 1562/2155] Add liburing to build image packages --- mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf | 1 + mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf | 1 + mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf | 1 + mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf | 1 + 4 files changed, 4 insertions(+) diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf index d7a79d11c1051..c199288d94bd9 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf @@ -10,3 +10,4 @@ Packages= diffutils erofs-utils git + liburing diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf index 4d0ca8917d83f..472e6b66927b1 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf @@ -11,6 +11,7 @@ Packages= gdb git-core libasan + liburing-devel libubsan rpm-build which diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf index b3fd0836597cf..d762bf861a193 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -11,5 +11,6 @@ Packages= erofs-utils git-core ?exact-name(libclang-rt-dev) + liburing-dev dpkg-dev mount diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf index 70a1b31b64196..6604b3bf2f45c 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf @@ -12,6 +12,7 @@ Packages= git-core grep gzip + liburing-devel patterns-base-minimal_base rpm-build sed From a19d92003d5703fce35bdd81ed7dce978fc814d1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 11 May 2026 17:17:35 +0200 Subject: [PATCH 1563/2155] units: pull in basic.target rather than sysinit.target from system-install.target Many of our services are nowadays implemented via socket activation, and hence require sockets.target to be active to be accessible. One of them is mute-console.socket, which we typically want to use from systemd-firstboot.service, systemd-sysinstall.service and other related services. Hence let's pull in basic.target rather than sysinit.target from system-install.target since it pulls sockets.target in too. Effectively, this doesn't change much except for pulling in a bunch more sockets, and frankly going for sysinit.target was really a bug to begin width. --- units/system-install.target | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/units/system-install.target b/units/system-install.target index 660110dcea36a..d60f9d8137ee7 100644 --- a/units/system-install.target +++ b/units/system-install.target @@ -10,6 +10,6 @@ [Unit] Description=System Installer Documentation=man:systemd-sysinstall(8) -Requires=sysinit.target -After=sysinit.target +Requires=basic.target +After=basic.target AllowIsolate=yes From d0168f4db613610b674dcf41afababde57b8d56b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 21:58:24 +0200 Subject: [PATCH 1564/2155] mkosi: Drop CPUs= limit Limiting VMs to 2 cpus was cargo culting without any actual data that this benefits performance. The host OS has a scheduler, let's make use of it and give the VM access to all the CPUs. This doesn't mean they become inaccessible to the host, it just means the VM gets as many virtual CPUs as the host has CPU cores (threads). How they get scheduled is still up to the host OS. --- mkosi/mkosi.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 2fc087cb73f40..0fbb81eeed23e 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -141,7 +141,6 @@ Credentials= tty.virtual.tty1.agetty.autologin=root tty.virtual.tty1.login.noauth=yes RuntimeBuildSources=yes -CPUs=2 TPM=yes VSock=yes KVM=yes From 324fab7ed665df0a2e3395b75c7b88f33805ccb8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 6 May 2026 16:13:12 +0200 Subject: [PATCH 1565/2155] firstboot,sysinstall,hostnamed: always show FANCY_NAME= This makes sure that whenever we want to show the OS name we can show the fancy name. Thus this moves the escaping/validation of the fancy name out of hostnamed into generic code, and then makes use of it in sysinstall,firstboot,prompt-util. --- src/basic/os-util.c | 45 +++++++++++++++++++++++++++++++++++++ src/basic/os-util.h | 3 +++ src/firstboot/firstboot.c | 7 ++++-- src/hostname/hostnamectl.c | 4 ++-- src/hostname/hostnamed.c | 16 +------------ src/shared/prompt-util.c | 10 +++++++-- src/sysinstall/sysinstall.c | 7 ++++-- 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 06b476f1344a8..bb87fe371c216 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -3,10 +3,12 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "chase.h" #include "dirent-util.h" #include "env-file.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fs-util.h" #include "glyph-util.h" @@ -512,3 +514,46 @@ const char* os_release_pretty_name(const char *pretty_name, const char *name) { return empty_to_null(pretty_name) ?: empty_to_null(name) ?: "Linux"; } + +char *unescape_fancy_name(char **fancy_name) { + assert(fancy_name); + + /* Checks if the fancy name is valid, unescapes if it is, nullifies it if not */ + + _cleanup_free_ char *unescaped_fancy_name = NULL; + + if (isempty(*fancy_name)) + goto clear; + + /* We undo one level of C escapes on this */ + ssize_t n = cunescape(*fancy_name, /* flags= */ 0, &unescaped_fancy_name); + if (n < 0) { + log_debug_errno((int) n, "Failed to unescape FANCY_NAME= string, suppressing: %m"); + goto clear; + } + + if (!utf8_is_valid(unescaped_fancy_name)) { + log_debug("Unescaped FANCY_NAME= string is not valid UTF-8, suppressing."); + goto clear; + } + + free_and_replace(*fancy_name, unescaped_fancy_name); + return *fancy_name; + +clear: + *fancy_name = mfree(*fancy_name); + return NULL; +} + +bool use_fancy_name(const char *fancy_name) { + + /* Decides whether to show the specified fancy name */ + + if (isempty(fancy_name)) + return false; + + if (!colors_enabled()) + return false; + + return emoji_enabled() || ascii_is_valid(fancy_name); +} diff --git a/src/basic/os-util.h b/src/basic/os-util.h index 02d2c9540f2de..336c17ec21aa0 100644 --- a/src/basic/os-util.h +++ b/src/basic/os-util.h @@ -55,3 +55,6 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol); const char* os_release_pretty_name(const char *pretty_name, const char *name); + +bool use_fancy_name(const char *fancy_name); +char *unescape_fancy_name(char **fancy_name); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 3d768b491f83a..8048ec0e810ca 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -103,7 +103,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static void print_welcome(int rfd, sd_varlink **mute_console_link) { - _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL; static bool done = false; const char *pn, *ac; int r; @@ -133,6 +133,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { r = parse_os_release_at(rfd, "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, "NAME", &os_name, "ANSI_COLOR", &ansi_color); if (r < 0) @@ -142,7 +143,9 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - if (colors_enabled()) + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + printf(ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", fancy_name); + else if (colors_enabled()) printf(ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", ac, pn); else printf("Welcome to %s!\n", pn); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 2989840b364d7..257b31fb88dda 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -23,13 +23,13 @@ #include "log.h" #include "main-func.h" #include "options.h" +#include "os-util.h" #include "parse-argument.h" #include "polkit-agent.h" #include "pretty-print.h" #include "runtime-scope.h" #include "string-util.h" #include "time-util.h" -#include "utf8.h" #include "verbs.h" static bool arg_ask_password = true; @@ -236,7 +236,7 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - if (!isempty(i->os_fancy_name) && (emoji_enabled() || ascii_is_valid(i->os_fancy_name)) && colors_enabled()) { + if (use_fancy_name(i->os_fancy_name)) { r = table_add_many(table, TABLE_FIELD, "Operating System", TABLE_STRING_WITH_ANSI, i->os_fancy_name, diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 60b48112449cf..5ec7b2fea992b 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -22,7 +22,6 @@ #include "device-private.h" #include "env-file.h" #include "env-util.h" -#include "escape.h" #include "extract-word.h" #include "fileio.h" #include "hashmap.h" @@ -230,20 +229,7 @@ static void context_read_os_release(Context *c) { if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0) log_oom(); - if (!isempty(os_fancy_name)) { - _cleanup_free_ char *unescaped = NULL; - - /* We undo one level of C escapes on this */ - ssize_t l = cunescape(os_fancy_name, /* flags= */ 0, &unescaped); - if (l < 0) { - log_warning_errno(l, "Failed to unescape fancy OS name, ignoring: %m"); - os_fancy_name = mfree(os_fancy_name); - } else if (!utf8_is_valid(unescaped)) { - log_warning("Unescaped fancy OS name contains invalid UTF-8, ignoring."); - os_fancy_name = mfree(os_fancy_name); - } else - free_and_replace(os_fancy_name, unescaped); - } + unescape_fancy_name(&os_fancy_name); if (isempty(os_fancy_name)) { free(os_fancy_name); /* free if empty string */ diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 7cead706fd95f..2f334f9b52832 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -12,6 +12,7 @@ #include "parse-util.h" #include "pretty-print.h" #include "prompt-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -234,11 +235,12 @@ int chrome_show( _cleanup_free_ char *b = NULL, *ansi_color_reverse = NULL; if (!bottom) { - _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *documentation_url = NULL; + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *documentation_url = NULL, *fancy_name = NULL; r = parse_os_release( /* root= */ NULL, "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, "NAME", &os_name, "ANSI_COLOR", &ansi_color, "ANSI_COLOR_REVERSE", &ansi_color_reverse, @@ -258,7 +260,11 @@ int chrome_show( free_and_replace(ansi_color_reverse, j); } - if (asprintf(&b, "\x1B[0;%sm %s %s", c, m, ansi_color_reverse ?: ANSI_COLOR_CHROME) < 0) + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + b = asprintf_safe("\x1B[0;%sm \x1B[0m%s\x1B[0;%sm %s", c, fancy_name, c, ansi_color_reverse ?: ANSI_COLOR_CHROME); + else + b = asprintf_safe("\x1B[0;%sm %s %s", c, m, ansi_color_reverse ?: ANSI_COLOR_CHROME); + if (!b) return log_oom_debug(); if (documentation_url) { diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index d8f5cbee3c93f..7d08b4866b272 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -214,7 +214,7 @@ static int parse_argv(int argc, char *argv[]) { } static int print_welcome(sd_varlink **mute_console_link) { - _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL; const char *pn, *ac; int r; @@ -229,6 +229,7 @@ static int print_welcome(sd_varlink **mute_console_link) { r = parse_os_release( /* root= */ NULL, "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, "NAME", &os_name, "ANSI_COLOR", &ansi_color); if (r < 0) @@ -238,7 +239,9 @@ static int print_welcome(sd_varlink **mute_console_link) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - if (colors_enabled()) + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", fancy_name); + else if (colors_enabled()) printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", ac, pn); else printf("Welcome to the %s Installer!\n", pn); From 2fcf96327370defdd8bd7dca71f7b34832c32d40 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 21:58:24 +0200 Subject: [PATCH 1566/2155] vmspawn: Add missing error logging --- 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 27fa3501ee5f3..97920aecc0efc 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3103,7 +3103,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { &tree_local_lock, &snapshot_directory); if (r < 0) - return r; + return log_error_errno(r, "Failed to create ephemeral snapshot of '%s': %m", arg_directory); arg_directory = strdup(snapshot_directory); if (!arg_directory) From 58863bdaec9ba4f6be5a171bdc5ffa42f22580a7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Apr 2026 03:27:36 +0900 Subject: [PATCH 1567/2155] iovec-wrapper: make iovw_size() take NULL again This partially reverts 267b16f33c5636617927f15d7ae6b945c862a587. We usually make xyz_size() take NULL, e.g. hashmap_size(). --- src/basic/iovec-wrapper.c | 3 ++- src/test/test-iovec-wrapper.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 59b1addaaf266..c5d1a878ff3cb 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -237,7 +237,8 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new) { } size_t iovw_size(const struct iovec_wrapper *iovw) { - assert(iovw); + if (iovw_isempty(iovw)) + return 0; return iovec_total_size(iovw->iovec, iovw->count); } diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 791a041ab8d6f..d38806e75e543 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -393,6 +393,8 @@ TEST(iovw_size) { ASSERT_OK(iovw_put(&iovw, (char*) "efghij", 6)); ASSERT_OK(iovw_put(&iovw, (char*) "kl", 2)); ASSERT_EQ(iovw_size(&iovw), 12U); + + ASSERT_EQ(iovw_size(NULL), 0U); } TEST(iovw_concat) { From fe6fed5d9730de40bd328ee11678ab235d067b94 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 25 Apr 2026 09:57:41 +0900 Subject: [PATCH 1568/2155] iovec-wrapper: introduce iovw_put_full() and friends to make them accept zero length entry These will be used later. Preparation for later commits. --- src/basic/iovec-wrapper.c | 65 ++++++++++++++++++---------- src/basic/iovec-wrapper.h | 40 ++++++++++++++---- src/test/test-iovec-wrapper.c | 80 +++++++++++++++++++++++++++++++++-- 3 files changed, 151 insertions(+), 34 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index c5d1a878ff3cb..375ecbdaaa23d 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -45,14 +45,13 @@ int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { return CMP(a->count, b->count); } -int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { +int iovw_put_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len) { assert(iovw); + assert(data || len == 0); - if (len == 0) + if (len == 0 && !accept_zero) return 0; - assert(data); - if (iovw->count >= IOV_MAX) return -E2BIG; @@ -63,16 +62,18 @@ 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) { +int iovw_put_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov) { assert(iovw); if (!iov) return 0; - return iovw_put(iovw, iov->iov_base, iov->iov_len); + return iovw_put_full(iovw, accept_zero, iov->iov_base, iov->iov_len); } -int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { +int iovw_put_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source) { + int r; + assert(iovw); if (iovw_isempty(source)) @@ -87,24 +88,41 @@ int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source if (iovw->count + source->count > IOV_MAX) return -E2BIG; - if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count)) - return -ENOMEM; + if (accept_zero) { + if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count)) + return -ENOMEM; + + return 0; + } + + /* When accept_zero is false, we need to filter zero length iovec in source. */ + size_t original_count = iovw->count; + + FOREACH_ARRAY(iovec, source->iovec, source->count) { + r = iovw_put_iov_full(iovw, accept_zero, iovec); + if (r < 0) + goto rollback; + } return 0; + +rollback: + iovw->count = original_count; + return r; } -int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { +int iovw_consume_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len) { /* Move data into iovw or free on error */ int r; - r = iovw_put(iovw, data, len); + r = iovw_put_full(iovw, accept_zero, data, len); if (r <= 0) free(data); return r; } -int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { +int iovw_consume_iov_full(struct iovec_wrapper *iovw, bool accept_zero, struct iovec *iov) { int r; assert(iovw); @@ -112,7 +130,7 @@ int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { if (!iov) return 0; - r = iovw_put_iov(iovw, iov); + r = iovw_put_iov_full(iovw, accept_zero, iov); if (r <= 0) iovec_done(iov); else @@ -123,27 +141,30 @@ int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { return r; } -int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { +int iovw_extend_full(struct iovec_wrapper *iovw, bool accept_zero, const void *data, size_t len) { + assert(iovw); + assert(data || len == 0); + if (len == 0) - return 0; + return iovw_put_full(iovw, accept_zero, /* data= */ NULL, /* len= */ 0); void *c = memdup(data, len); if (!c) return -ENOMEM; - return iovw_consume(iovw, c, len); + return iovw_consume_full(iovw, accept_zero, c, len); } -int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { +int iovw_extend_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov) { assert(iovw); - if (!iovec_is_set(iov)) + if (!iov) return 0; - return iovw_extend(iovw, iov->iov_base, iov->iov_len); + return iovw_extend_full(iovw, accept_zero, iov->iov_base, iov->iov_len); } -int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { +int iovw_extend_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source) { int r; assert(iovw); @@ -165,7 +186,7 @@ int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *sou size_t original_count = iovw->count; FOREACH_ARRAY(iovec, source->iovec, source->count) { - r = iovw_extend_iov(iovw, iovec); + r = iovw_extend_iov_full(iovw, accept_zero, iovec); if (r < 0) goto rollback; } @@ -260,7 +281,7 @@ int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) { uint8_t *p = buf; FOREACH_ARRAY(i, iovw->iovec, iovw->count) - p = mempcpy(p, i->iov_base, i->iov_len); + p = mempcpy_safe(p, i->iov_base, i->iov_len); *p = 0; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 26b7f5ad4be30..c860eaf3339e7 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -16,14 +16,38 @@ static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_ return iovw_compare(a, b) == 0; } -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); +int iovw_put_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len); +static inline int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { + return iovw_put_full(iovw, false, data, len); +} +int iovw_put_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov); +static inline int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + return iovw_put_iov_full(iovw, false, iov); +} +int iovw_put_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source); +static inline int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + return iovw_put_iovw_full(iovw, false, source); +} +int iovw_consume_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len); +static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { + return iovw_consume_full(iovw, false, data, len); +} +int iovw_consume_iov_full(struct iovec_wrapper *iovw, bool accept_zero, struct iovec *iov); +static inline int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { + return iovw_consume_iov_full(iovw, false, iov); +} +int iovw_extend_full(struct iovec_wrapper *iovw, bool accept_zero, const void *data, size_t len); +static inline int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { + return iovw_extend_full(iovw, false, data, len); +} +int iovw_extend_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov); +static inline int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + return iovw_extend_iov_full(iovw, false, iov); +} +int iovw_extend_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source); +static inline int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + return iovw_extend_iovw_full(iovw, false, source); +} static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { return !iovw || iovw->count == 0; diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index d38806e75e543..3bd2123c3c95a 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -82,6 +82,12 @@ TEST(iovw_put) { 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); + + ASSERT_OK(iovw_put_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_put_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); } TEST(iovw_put_iov) { @@ -100,6 +106,12 @@ TEST(iovw_put_iov) { 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_OK(iovw_put_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_put_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); } TEST(iovw_put_iovw) { @@ -113,7 +125,8 @@ TEST(iovw_put_iovw) { 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); + ASSERT_OK(iovw_put_iov_full(&source, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(source.count, 4U); /* 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"))); @@ -136,10 +149,24 @@ TEST(iovw_put_iovw) { ASSERT_PTR_EQ(target.iovec[5].iov_base, source.iovec[2].iov_base); /* Source is unchanged */ - ASSERT_EQ(source.count, 3U); + ASSERT_EQ(source.count, 4U); 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"))); + ASSERT_TRUE(iovec_equal(&source.iovec[3], &(struct iovec) {})); + + ASSERT_OK(iovw_put_iovw_full(&target, /* accept_zero= */ true, &source)); + ASSERT_EQ(target.count, 10U); + 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"))); + ASSERT_TRUE(iovec_equal(&target.iovec[6], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&target.iovec[7], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&target.iovec[8], &IOVEC_MAKE_STRING("ccc"))); + ASSERT_TRUE(iovec_equal(&target.iovec[9], &(struct iovec) {})); /* Cannot pass the same objects */ ASSERT_ERROR(iovw_put_iovw(&target, &target), EINVAL); @@ -169,6 +196,12 @@ TEST(iovw_extend) { /* 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); + + ASSERT_OK(iovw_extend_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_OK(iovw_extend_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); } TEST(iovw_extend_iov) { @@ -186,6 +219,12 @@ TEST(iovw_extend_iov) { 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_OK(iovw_extend_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_extend_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); } TEST(iovw_extend_iovw) { @@ -199,7 +238,8 @@ TEST(iovw_extend_iovw) { ASSERT_OK(iovw_put(&source, (char*) "one", 3)); ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); - ASSERT_EQ(source.count, 2U); + ASSERT_OK(iovw_put_full(&source, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(source.count, 3U); /* Pre-seed target with one entry to check that append adds on top rather than replacing */ char *seed = strdup("zero"); @@ -218,8 +258,17 @@ TEST(iovw_extend_iovw) { ASSERT_EQ(target.iovec[2].iov_len, 6U); ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); + ASSERT_OK(iovw_extend_iovw_full(&target, /* accept_zero= */ true, &source)); + ASSERT_EQ(target.count, 6U); + ASSERT_TRUE(iovec_equal(&target.iovec[0], &IOVEC_MAKE_STRING("zero"))); + ASSERT_TRUE(iovec_equal(&target.iovec[1], &IOVEC_MAKE_STRING("one"))); + ASSERT_TRUE(iovec_equal(&target.iovec[2], &IOVEC_MAKE_STRING("twotwo"))); + ASSERT_TRUE(iovec_equal(&target.iovec[3], &IOVEC_MAKE_STRING("one"))); + ASSERT_TRUE(iovec_equal(&target.iovec[4], &IOVEC_MAKE_STRING("twotwo"))); + ASSERT_TRUE(iovec_equal(&target.iovec[5], &(struct iovec) {})); + /* Source is unchanged */ - ASSERT_EQ(source.count, 2U); + ASSERT_EQ(source.count, 3U); /* Cannot pass the same objects */ ASSERT_ERROR(iovw_extend_iovw(&target, &target), EINVAL); @@ -240,6 +289,16 @@ TEST(iovw_consume) { char *q = ASSERT_NOT_NULL(strdup("")); ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0)); ASSERT_EQ(iovw.count, 1U); + + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &(struct iovec) {})); + q = ASSERT_NOT_NULL(strdup("")); + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ true, q, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); } TEST(iovw_consume_iov) { @@ -270,6 +329,19 @@ TEST(iovw_consume_iov) { /* zero length iovec is also freed */ ASSERT_NULL(iov.iov_base); ASSERT_EQ(iov.iov_len, 0U); + + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 1U); + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 2U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &(struct iovec) {})); + iov = (struct iovec) { + .iov_base = ASSERT_NOT_NULL(strdup("")), + .iov_len = 0, + }; + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ true, &iov)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); } TEST(iovw_isempty) { From 32729ae92f53f78f5ffb94d22c56b3781e8e6154 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Apr 2026 03:33:36 +0900 Subject: [PATCH 1569/2155] iovec-wrapper: introduce iovec_split() and iovw_merge() In many network protocols, the length-prefixed data format is often used. Let's add a simple parser and builder for the format. --- src/basic/iovec-wrapper.c | 119 +++++++++++++++++++++ src/basic/iovec-wrapper.h | 3 + src/test/test-iovec-wrapper.c | 193 ++++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 375ecbdaaa23d..6b4b006c059dc 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -6,6 +6,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "string-util.h" +#include "unaligned.h" void iovw_done(struct iovec_wrapper *iovw) { assert(iovw); @@ -302,3 +303,121 @@ char* iovw_to_cstring(const struct iovec_wrapper *iovw) { assert(!memchr(iov.iov_base, 0, iov.iov_len)); return TAKE_PTR(iov.iov_base); } + +int iovec_split(const struct iovec *iov, size_t length_size, struct iovec_wrapper *ret) { + int r; + + assert(IN_SET(length_size, 1, 2, 4)); + assert(ret); + + /* This parses the input iovec as length-prefixed data, and stores the result as iovec_wrapper. + * Note, zero-length entries are silently dropped. */ + + if (!iovec_is_set(iov)) { + *ret = (struct iovec_wrapper) {}; + return 0; + } + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + for (struct iovec i = *iov; iovec_is_set(&i); ) { + if (i.iov_len < length_size) + return -EBADMSG; + + size_t len; + switch (length_size) { + case 1: + len = *(uint8_t*) i.iov_base; + break; + case 2: + len = unaligned_read_be16(i.iov_base); + break; + case 4: + len = unaligned_read_be32(i.iov_base); + break; + default: + assert_not_reached(); + } + + iovec_inc(&i, length_size); + + if (len == 0) + continue; + + if (i.iov_len < len) + return -EBADMSG; + + r = iovw_extend(&iovw, i.iov_base, len); + if (r < 0) + return r; + + iovec_inc(&i, len); + } + + *ret = TAKE_STRUCT(iovw); + return 0; +} + +int iovw_merge(const struct iovec_wrapper *iovw, size_t length_size, struct iovec *ret) { + assert(IN_SET(length_size, 1, 2, 4)); + assert(ret); + + /* This is the inverse of iovec_split(), and builds a length-prefixed data from iovec_wrapper. + * Note, zero-length entries are silently dropped. */ + + size_t sz = iovw_size(iovw); + if (sz == 0) { + *ret = (struct iovec) {}; + return 0; + } + if (sz == SIZE_MAX) + return -E2BIG; + + if (size_multiply_overflow(length_size, iovw->count)) + return -E2BIG; + + sz = size_add(sz, iovw->count * length_size); + if (sz == SIZE_MAX) + return -E2BIG; + + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + if (iov->iov_len == 0) + continue; + + switch (length_size) { + case 1: + if (iov->iov_len > UINT8_MAX) + return -ERANGE; + + *p = iov->iov_len; + break; + case 2: + if (iov->iov_len > UINT16_MAX) + return -ERANGE; + + unaligned_write_be16(p, iov->iov_len); + break; + case 4: + if (iov->iov_len > UINT32_MAX) + return -ERANGE; + + unaligned_write_be32(p, iov->iov_len); + break; + default: + assert_not_reached(); + } + p += length_size; + + p = mempcpy(p, iov->iov_base, iov->iov_len); + } + + assert(sz >= (size_t) (p - buf)); + sz = p - buf; + + *ret = IOVEC_MAKE(TAKE_PTR(buf), sz); + return 0; +} diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index c860eaf3339e7..c2a0cff1aeed6 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -68,3 +68,6 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new); size_t iovw_size(const struct iovec_wrapper *iovw); int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret); char* iovw_to_cstring(const struct iovec_wrapper *iovw); + +int iovec_split(const struct iovec *iov, size_t length_size, struct iovec_wrapper *ret); +int iovw_merge(const struct iovec_wrapper *iovw, size_t length_size, struct iovec *ret); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 3bd2123c3c95a..523089058707c 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" +#include "random-util.h" #include "tests.h" TEST(iovw_compare) { @@ -506,4 +507,196 @@ TEST(iovw_to_cstring) { ASSERT_STREQ(s, "foo/bar"); } +TEST(iovw_merge_and_iovec_split) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + _cleanup_(iovec_done) struct iovec v = {}, v2 = {}; + uint8_t *p; + + struct iovec + a = IOVEC_MAKE_STRING("aaa"), + b = IOVEC_MAKE_STRING("bbbb"), + c = IOVEC_MAKE_STRING("ccccc"); + + /* single entry */ + ASSERT_OK(iovw_extend_iov(&iovw, &a)); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_EQ(v.iov_len, 1 + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint16_t), &v)); + ASSERT_EQ(v.iov_len, sizeof(uint16_t) + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint32_t), &v)); + ASSERT_EQ(v.iov_len, sizeof(uint32_t) + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint32_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* multiple entries */ + ASSERT_OK(iovw_extend_iov(&iovw, &b)); + ASSERT_OK(iovw_extend_iov(&iovw, &c)); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_EQ(v.iov_len, 3 + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint16_t), &v)); + ASSERT_EQ(v.iov_len, 3 * sizeof(uint16_t) + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint32_t), &v)); + ASSERT_EQ(v.iov_len, 3 * sizeof(uint32_t) + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint32_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* with empty entries */ + _cleanup_(iovw_done) struct iovec_wrapper with_empty = { + .iovec = ASSERT_PTR(new0(struct iovec, 6)), + .count = 6, + }; + with_empty.iovec[0] = a; + with_empty.iovec[2] = b; + with_empty.iovec[4] = c; + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_OK(iovw_merge(&with_empty, sizeof(uint8_t), &v2)); + ASSERT_TRUE(iovec_equal(&v, &v2)); + + iovec_done(&v); + iovec_done(&v2); + + size_t sz = 6 + a.iov_len + b.iov_len + c.iov_len; + _cleanup_free_ uint8_t *buf = ASSERT_PTR(new(uint8_t, sz)); + p = buf; + *p++ = a.iov_len; + p = mempcpy(p, a.iov_base, a.iov_len); + *p++ = 0; + *p++ = b.iov_len; + p = mempcpy(p, b.iov_base, b.iov_len); + *p++ = 0; + *p++ = c.iov_len; + p = mempcpy(p, c.iov_base, c.iov_len); + *p++ = 0; + ASSERT_OK(iovec_split(&IOVEC_MAKE(buf, sz), sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovw_done_free(&iovw2); + + /* truncated */ + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_ERROR(iovec_split(&IOVEC_MAKE(v.iov_base, v.iov_len - 1), sizeof(uint8_t), &iovw2), EBADMSG); + + iovec_done(&v); + + /* too long */ + _cleanup_(iovec_done) struct iovec large = {}; + ASSERT_OK(random_bytes_allocate_iovec(256, &large)); + ASSERT_ERROR(iovw_merge(&(struct iovec_wrapper) { .iovec = &large, .count = 1, }, sizeof(uint8_t), &v), ERANGE); + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) { .iovec = &large, .count = 1, }, sizeof(uint16_t), &v)); + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_EQ(iovw2.count, 1u); + ASSERT_TRUE(iovec_equal(&iovw2.iovec[0], &large)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* No entry */ + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) {}, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovw_merge(NULL, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovec_split(&(struct iovec) {}, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + + ASSERT_OK(iovec_split(NULL, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + + /* empty entry only */ + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) { .iovec = &(struct iovec) {}, .count = 1, }, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovec_split(&IOVEC_MAKE("", 1), sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + +} + DEFINE_TEST_MAIN(LOG_INFO); From 4fc58bf62111ae164bbbbf2f276af24f1c4b0efd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Apr 2026 03:10:16 +0900 Subject: [PATCH 1570/2155] iovec-wrapper: reintroduce iovw_free() and iovw_free_free() They were dropped by the commit 267b16f33c5636617927f15d7ae6b945c862a587, but will be used later. Hence, let's reintroduce them. --- src/basic/iovec-wrapper.c | 16 ++++++++++++++++ src/basic/iovec-wrapper.h | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 6b4b006c059dc..da217170c573c 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -24,6 +24,22 @@ void iovw_done_free(struct iovec_wrapper *iovw) { iovw_done(iovw); } +struct iovec_wrapper* iovw_free(struct iovec_wrapper *iovw) { + if (!iovw) + return NULL; + + iovw_done(iovw); + return mfree(iovw); +} + +struct iovec_wrapper* iovw_free_free(struct iovec_wrapper *iovw) { + if (!iovw) + return NULL; + + iovw_done_free(iovw); + return mfree(iovw); +} + int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { int r; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index c2a0cff1aeed6..a4f93f1fdb94b 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -11,6 +11,12 @@ struct iovec_wrapper { void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); +struct iovec_wrapper* iovw_free(struct iovec_wrapper *iovw); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free); + +struct iovec_wrapper* iovw_free_free(struct iovec_wrapper *iovw); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free); + 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; From 320ae462b1315564e0ab97f1d1b13e6e05b489af Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 21 Apr 2026 05:05:32 +0900 Subject: [PATCH 1571/2155] tlv-util: introduce tlv-util that handles Tag-Length-Value data format In many network protocols e.g. DHCP, the TLV format is used. Let's introduce a simple parser and builder of the data format. --- src/libsystemd-network/meson.build | 4 + src/libsystemd-network/test-tlv-util.c | 207 ++++++++++ src/libsystemd-network/tlv-util.c | 505 +++++++++++++++++++++++++ src/libsystemd-network/tlv-util.h | 82 ++++ 4 files changed, 798 insertions(+) create mode 100644 src/libsystemd-network/test-tlv-util.c create mode 100644 src/libsystemd-network/tlv-util.c create mode 100644 src/libsystemd-network/tlv-util.h diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index b0443c3695206..6239056e3b4b1 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -37,6 +37,7 @@ libsystemd_network_sources = files( 'sd-ndisc-router.c', 'sd-ndisc-router-solicit.c', 'sd-radv.c', + 'tlv-util.c', ) sources += libsystemd_network_sources @@ -113,6 +114,9 @@ executables += [ network_test_template + { 'sources' : files('test-sd-dhcp-lease.c'), }, + network_test_template + { + 'sources' : files('test-tlv-util.c'), + }, network_fuzz_template + { 'sources' : files('fuzz-dhcp-client.c'), }, diff --git a/src/libsystemd-network/test-tlv-util.c b/src/libsystemd-network/test-tlv-util.c new file mode 100644 index 0000000000000..4747afa98dfe5 --- /dev/null +++ b/src/libsystemd-network/test-tlv-util.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-protocol.h" + +#include "hashmap.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "random-util.h" +#include "tests.h" +#include "tlv-util.h" + +TEST(tlv_constant) { + ASSERT_EQ(TLV_TAG_PAD, (uint32_t) SD_DHCP_OPTION_PAD); + ASSERT_EQ(TLV_TAG_END, (uint32_t) SD_DHCP_OPTION_END); +} + +TEST(tlv) { + _cleanup_(tlv_done) TLV tlv = TLV_INIT(TLV_DHCP4); + + _cleanup_(iovec_done) struct iovec data0 = {}, data1 = {}, data2a = {}, data2b = {}, data3 = {}, data4 = {}; + ASSERT_OK(random_bytes_allocate_iovec(0, &data0)); + ASSERT_OK(random_bytes_allocate_iovec(111, &data1)); + ASSERT_OK(random_bytes_allocate_iovec(123, &data2a)); + ASSERT_OK(random_bytes_allocate_iovec(321, &data2b)); + ASSERT_OK(random_bytes_allocate_iovec(333, &data3)); + ASSERT_OK(random_bytes_allocate_iovec(444, &data4)); + + /* tlv_append() */ + ASSERT_OK(tlv_append(&tlv, 10, data0.iov_len, data0.iov_base)); + ASSERT_OK(tlv_append(&tlv, 11, data1.iov_len, data1.iov_base)); + ASSERT_OK(tlv_append(&tlv, 22, data2a.iov_len, data2a.iov_base)); + ASSERT_OK(tlv_append(&tlv, 22, data2b.iov_len, data2b.iov_base)); + ASSERT_OK(tlv_append(&tlv, 33, data3.iov_len, data3.iov_base)); + ASSERT_OK(tlv_append(&tlv, 44, data4.iov_len, data4.iov_base)); + ASSERT_ERROR(tlv_append(&tlv, 0x00, data4.iov_len, data4.iov_base), EINVAL); + ASSERT_ERROR(tlv_append(&tlv, 0xFF, data4.iov_len, data4.iov_base), EINVAL); + ASSERT_EQ(hashmap_size(tlv.entries), 5u); + + /* tlv_remove() */ + tlv_remove(&tlv, 44); + ASSERT_EQ(hashmap_size(tlv.entries), 4u); + tlv_remove(&tlv, 55); + ASSERT_EQ(hashmap_size(tlv.entries), 4u); + + /* tlv_append_tlv() */ + _cleanup_(tlv_done) TLV tlv_copy = TLV_INIT(TLV_DHCP4); + ASSERT_ERROR(tlv_append_tlv(&tlv_copy, &tlv_copy), EINVAL); + ASSERT_OK(tlv_append_tlv(&tlv_copy, NULL)); + ASSERT_OK(tlv_append_tlv(&tlv_copy, &tlv)); + ASSERT_EQ(hashmap_size(tlv_copy.entries), hashmap_size(tlv.entries)); + + /* tlv_isempty() */ + ASSERT_TRUE(tlv_isempty(NULL)); + ASSERT_TRUE(tlv_isempty(&TLV_INIT(TLV_DHCP4))); + ASSERT_FALSE(tlv_isempty(&tlv)); + + /* tlv_contains() */ + ASSERT_TRUE(tlv_contains(&tlv, 10)); + ASSERT_TRUE(tlv_contains(&tlv, 11)); + ASSERT_TRUE(tlv_contains(&tlv, 22)); + ASSERT_TRUE(tlv_contains(&tlv, 33)); + ASSERT_FALSE(tlv_contains(&tlv, 44)); + + /* tlv_get_all() */ + struct iovec_wrapper *iovw; + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 10)); + ASSERT_EQ(iovw->count, 1u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data0)); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 11)); + ASSERT_EQ(iovw->count, 1u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data1)); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 22)); + ASSERT_EQ(iovw->count, 3u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data2a)); + ASSERT_TRUE(iovec_equal(&iovw->iovec[1], &IOVEC_MAKE(data2b.iov_base, UINT8_MAX))); + ASSERT_TRUE(iovec_equal(&iovw->iovec[2], &IOVEC_SHIFT(&data2b, UINT8_MAX))); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 33)); + ASSERT_EQ(iovw->count, 2u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_TRUE(iovec_equal(&iovw->iovec[1], &IOVEC_SHIFT(&data3, UINT8_MAX))); + + ASSERT_NULL(tlv_get_all(&tlv, 44)); + + /* tlv_get_full() */ + struct iovec iov; + + ASSERT_OK(tlv_get(&tlv, 10, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data0)); + ASSERT_OK(tlv_get_full(&tlv, 10, data0.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data0)); + ASSERT_ERROR(tlv_get_full(&tlv, 10, 123, &iov), ENODATA); + + ASSERT_OK(tlv_get(&tlv, 11, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data1)); + ASSERT_OK(tlv_get_full(&tlv, 11, data1.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data1)); + ASSERT_ERROR(tlv_get_full(&tlv, 11, 123, &iov), ENODATA); + + ASSERT_OK(tlv_get(&tlv, 22, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data2a)); + ASSERT_OK(tlv_get_full(&tlv, 22, data2a.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data2a)); + ASSERT_ERROR(tlv_get_full(&tlv, 22, data2b.iov_len, &iov), ENODATA); + ASSERT_OK(tlv_get_full(&tlv, 22, UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data2b.iov_base, UINT8_MAX))); + ASSERT_OK(tlv_get_full(&tlv, 22, data2b.iov_len - UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_SHIFT(&data2b, UINT8_MAX))); + + ASSERT_OK(tlv_get(&tlv, 33, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_ERROR(tlv_get_full(&tlv, 33, data3.iov_len, &iov), ENODATA); + ASSERT_OK(tlv_get_full(&tlv, 33, UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_OK(tlv_get_full(&tlv, 33, data3.iov_len - UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_SHIFT(&data3, UINT8_MAX))); + + ASSERT_ERROR(tlv_get(&tlv, 44, NULL), ENODATA); + + /* tlv_get_alloc() */ + _cleanup_(iovec_done) struct iovec v = {}; + + ASSERT_OK(tlv_get_alloc(&tlv, 10, &v)); + ASSERT_TRUE(iovec_equal(&v, &data0)); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 11, &v)); + ASSERT_TRUE(iovec_equal(&v, &data1)); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 22, &v)); + ASSERT_EQ(v.iov_len, data2a.iov_len + data2b.iov_len); + ASSERT_EQ(memcmp(v.iov_base, data2a.iov_base, data2a.iov_len), 0); + ASSERT_EQ(memcmp((uint8_t*) v.iov_base + data2a.iov_len, data2b.iov_base, data2b.iov_len), 0); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 33, &v)); + ASSERT_TRUE(iovec_equal(&v, &data3)); + iovec_done(&v); + + ASSERT_ERROR(tlv_get_alloc(&tlv, 44, NULL), ENODATA); + + /* tlv_size() */ + size_t sz = tlv_size(&tlv); + /* The tlv contains the 7 entries with a 2-byte header: + * tag 10: 1 entry, tag 11: 1 entry, tag 22: 3 entries, tag 33: 2 entries = 7 entries total. */ + ASSERT_EQ(sz, 7 * 2 + data0.iov_len + data1.iov_len + data2a.iov_len + data2b.iov_len + data3.iov_len + 1); + + /* tlv_build() */ + ASSERT_OK(tlv_build(&tlv, &v)); + ASSERT_EQ(v.iov_len, sz); + uint8_t *p = v.iov_base; + ASSERT_EQ(*p++, 10u); + ASSERT_EQ(*p++, data0.iov_len); + + ASSERT_EQ(*p++, 11u); + ASSERT_EQ(*p++, data1.iov_len); + ASSERT_EQ(memcmp(p, data1.iov_base, data1.iov_len), 0); + p += data1.iov_len; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, data2a.iov_len); + ASSERT_EQ(memcmp(p, data2a.iov_base, data2a.iov_len), 0); + p += data2a.iov_len; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, UINT8_MAX); + ASSERT_EQ(memcmp(p, data2b.iov_base, UINT8_MAX), 0); + p += UINT8_MAX; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, data2b.iov_len - UINT8_MAX); + ASSERT_EQ(memcmp(p, (uint8_t*) data2b.iov_base + UINT8_MAX, data2b.iov_len - UINT8_MAX), 0); + p += data2b.iov_len - UINT8_MAX; + + ASSERT_EQ(*p++, 33u); + ASSERT_EQ(*p++, UINT8_MAX); + ASSERT_EQ(memcmp(p, data3.iov_base, UINT8_MAX), 0); + p += UINT8_MAX; + + ASSERT_EQ(*p++, 33u); + ASSERT_EQ(*p++, data3.iov_len - UINT8_MAX); + ASSERT_EQ(memcmp(p, (uint8_t*) data3.iov_base + UINT8_MAX, data3.iov_len - UINT8_MAX), 0); + p += data3.iov_len - UINT8_MAX; + + ASSERT_EQ(*p, 255u); + + /* tlv_new() and tlv_parse() */ + _cleanup_(tlv_unrefp) TLV *tlv2 = ASSERT_NOT_NULL(tlv_new(TLV_DHCP4 | TLV_TEMPORARY)); + ASSERT_OK(tlv_parse(tlv2, &v)); + ASSERT_EQ(hashmap_size(tlv.entries), hashmap_size(tlv2->entries)); + void *tagp; + HASHMAP_FOREACH_KEY(iovw, tagp, tlv.entries) { + struct iovec_wrapper *iovw2 = ASSERT_PTR(hashmap_get(tlv2->entries, tagp)); + ASSERT_TRUE(iovw_equal(iovw, iovw2)); + } + + /* tlv_build() again, and check the reproducibility. */ + _cleanup_(iovec_done) struct iovec v2 = {}; + ASSERT_OK(tlv_build(tlv2, &v2)); + ASSERT_TRUE(iovec_equal(&v, &v2)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/tlv-util.c b/src/libsystemd-network/tlv-util.c new file mode 100644 index 0000000000000..68574e718c741 --- /dev/null +++ b/src/libsystemd-network/tlv-util.c @@ -0,0 +1,505 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "tlv-util.h" +#include "unaligned.h" + +#define TLV_MAX_ENTRIES 4096u + +TLVFlag tlv_flags_verify(TLVFlag flags) { + assert(IN_SET(flags & _TLV_TAG_MASK, TLV_TAG_U8, TLV_TAG_U16, TLV_TAG_U32)); + assert(IN_SET(flags & _TLV_LENGTH_MASK, TLV_LENGTH_U8, TLV_LENGTH_U16, TLV_LENGTH_U32)); + + /* TLV_PAD and TLV_END are for DHCPv4 options, hence here we assume TLV_TAG_U8 is set. */ + assert(!FLAGS_SET(flags, TLV_PAD) || FLAGS_SET(flags, TLV_TAG_U8)); + assert(!FLAGS_SET(flags, TLV_END) || FLAGS_SET(flags, TLV_TAG_U8)); + + /* When we requested to append the END tag, then we should understand the END tag on parse. */ + assert(!FLAGS_SET(flags, TLV_APPEND_END) || FLAGS_SET(flags, TLV_END)); + + return flags; +} + +void tlv_done(TLV *tlv) { + assert(tlv); + + tlv->entries = hashmap_free(tlv->entries); + tlv->n_entries = 0; +} + +static TLV* tlv_free(TLV *tlv) { + if (!tlv) + return NULL; + + tlv_done(tlv); + return mfree(tlv); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(TLV, tlv, tlv_free); + +TLV* tlv_new(TLVFlag flags) { + TLV *tlv = new(TLV, 1); + if (!tlv) + return NULL; + + *tlv = TLV_INIT(flags); + return tlv; +} + +bool tlv_isempty(const TLV *tlv) { + return !tlv || hashmap_isempty(tlv->entries); +} + +struct iovec_wrapper* tlv_get_all(const TLV *tlv, uint32_t tag) { + assert(tlv); + return hashmap_get(tlv->entries, UINT32_TO_PTR(tag)); +} + +int tlv_get_full(const TLV *tlv, uint32_t tag, size_t length, struct iovec *ret) { + assert(tlv); + + /* Do not free the result iovec, the data is still owned by TLV (or the original input data when + * TLV_TEMPORARY is set). */ + + struct iovec_wrapper *iovw = tlv_get_all(tlv, tag); + if (iovw_isempty(iovw)) + return -ENODATA; + + /* When multiple entries exist, use the first one matching the length. */ + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + if (length != SIZE_MAX && iov->iov_len != length) + continue; + + if (ret) + *ret = *iov; + return 0; + } + + return -ENODATA; +} + +int tlv_get_alloc(const TLV *tlv, uint32_t tag, struct iovec *ret) { + assert(tlv); + + /* Free the result iovec. */ + + struct iovec_wrapper *iovw = tlv_get_all(tlv, tag); + if (iovw_isempty(iovw)) + return -ENODATA; + + if (!ret) + return 0; + + if (FLAGS_SET(tlv->flags, TLV_MERGE)) + return iovw_concat(iovw, ret); + + /* When TLV_MERGE is unset, provides the first entry. */ + if (!iovec_memdup(&iovw->iovec[0], ret)) + return -ENOMEM; + + return 0; +} + +void tlv_remove(TLV *tlv, uint32_t tag) { + assert(tlv); + + struct iovec_wrapper *iovw = hashmap_remove(tlv->entries, UINT32_TO_PTR(tag)); + if (!iovw) + return; + + assert(tlv->n_entries >= iovw->count); + tlv->n_entries -= iovw->count; + + if (FLAGS_SET(tlv->flags, TLV_TEMPORARY)) + iovw_free(iovw); + else + iovw_free_free(iovw); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + tlv_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + struct iovec_wrapper, + iovw_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + tlv_hash_ops_free, + void, + trivial_hash_func, + trivial_compare_func, + struct iovec_wrapper, + iovw_free_free); + +static int tlv_append_impl(TLV *tlv, uint32_t tag, size_t length, const void *data) { + int r; + + assert(tlv); + assert(length == 0 || data); + + if (tlv->n_entries >= TLV_MAX_ENTRIES) + return -E2BIG; + + if (FLAGS_SET(tlv->flags, TLV_TEMPORARY)) { + struct iovec_wrapper *e = tlv_get_all(tlv, tag); + if (e) { + r = iovw_put_full(e, /* accept_zero= */ true, (void*) data, length); + if (r < 0) + return r; + } else { + _cleanup_(iovw_freep) struct iovec_wrapper *v = new0(struct iovec_wrapper, 1); + if (!v) + return -ENOMEM; + + r = iovw_put_full(v, /* accept_zero= */ true, (void*) data, length); + if (r < 0) + return r; + + r = hashmap_ensure_put(&tlv->entries, &tlv_hash_ops, UINT32_TO_PTR(tag), v); + if (r < 0) + return r; + + TAKE_PTR(v); + } + } else { + struct iovec_wrapper *e = tlv_get_all(tlv, tag); + if (e) { + r = iovw_extend_full(e, /* accept_zero= */ true, data, length); + if (r < 0) + return r; + } else { + _cleanup_(iovw_free_freep) struct iovec_wrapper *v = new0(struct iovec_wrapper, 1); + if (!v) + return -ENOMEM; + + r = iovw_extend_full(v, /* accept_zero= */ true, data, length); + if (r < 0) + return r; + + r = hashmap_ensure_put(&tlv->entries, &tlv_hash_ops_free, UINT32_TO_PTR(tag), v); + if (r < 0) + return r; + + TAKE_PTR(v); + } + } + + tlv->n_entries++; + return 0; +} + +int tlv_append(TLV *tlv, uint32_t tag, size_t length, const void *data) { + int r; + + assert(tlv); + assert(length == 0 || data); + + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (tag > UINT8_MAX) + return -EINVAL; + break; + case TLV_TAG_U16: + if (tag > UINT16_MAX) + return -EINVAL; + break; + case TLV_TAG_U32: + break; + default: + assert_not_reached(); + } + + if ((FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) || + (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END)) + return -EINVAL; + + size_t max_length; + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + max_length = UINT8_MAX; + break; + case TLV_LENGTH_U16: + max_length = UINT16_MAX; + break; + case TLV_LENGTH_U32: + max_length = UINT32_MAX; + break; + default: + assert_not_reached(); + } + + if (FLAGS_SET(tlv->flags, TLV_MERGE)) { + /* If TLV_MERGE is set and the length is larger than the allowed maximum, then split the data + * and store them in multiple entries. + * + * Note, if tlv_append_impl() fails below, we do not rollback the entries, hence the caller + * of this function needs to discard the entire data in that case. */ + const uint8_t *p = data; + while (length > max_length) { + r = tlv_append_impl(tlv, tag, max_length, p); + if (r < 0) + return r; + + p += max_length; + length -= max_length; + } + + return tlv_append_impl(tlv, tag, length, p); + } + + /* Otherwise, refuse too long data. */ + if (length > max_length) + return -EINVAL; + + return tlv_append_impl(tlv, tag, length, data); +} + +int tlv_append_iov(TLV *tlv, uint32_t tag, const struct iovec *iov) { + assert(tlv); + assert(iovec_is_valid(iov)); + + return tlv_append(tlv, tag, iov ? iov->iov_len : 0, iov ? iov->iov_base : NULL); +} + +int tlv_append_tlv(TLV *tlv, const TLV *source) { + int r; + + assert(tlv); + + /* Note, this does not rollback entries on failure, hence the caller of this function needs to + * discard the entire data in that case. */ + + if (!source) + return 0; + + if (source == tlv) + return -EINVAL; + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, source->entries) { + uint32_t tag = PTR_TO_UINT32(tagp); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = tlv_append(tlv, tag, iov->iov_len, iov->iov_base); + if (r < 0) + return r; + } + } + + return 0; +} + +int tlv_parse(TLV *tlv, const struct iovec *iov) { + int r; + + assert(tlv); + assert(iovec_is_valid(iov)); + + /* Note, this does not rollback entries on failure, hence the caller of this function needs to + * discard the entire data in that case. */ + + if (!iovec_is_set(iov)) + return 0; + + for (struct iovec i = *iov; iovec_is_set(&i); ) { + uint32_t tag; + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (i.iov_len < sizeof(uint8_t)) + return -EBADMSG; + tag = *(uint8_t*) i.iov_base; + iovec_inc(&i, sizeof(uint8_t)); + break; + case TLV_TAG_U16: + if (i.iov_len < sizeof(uint16_t)) + return -EBADMSG; + tag = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(uint16_t)); + break; + case TLV_TAG_U32: + if (i.iov_len < sizeof(uint32_t)) + return -EBADMSG; + tag = unaligned_read_be32(i.iov_base); + iovec_inc(&i, sizeof(uint32_t)); + break; + default: + assert_not_reached(); + } + + if (FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) + continue; + if (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END) + break; + + size_t len; + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + if (i.iov_len < sizeof(uint8_t)) + return -EBADMSG; + len = *(uint8_t*) i.iov_base; + iovec_inc(&i, sizeof(uint8_t)); + break; + case TLV_LENGTH_U16: + if (i.iov_len < sizeof(uint16_t)) + return -EBADMSG; + len = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(uint16_t)); + break; + case TLV_LENGTH_U32: + if (i.iov_len < sizeof(uint32_t)) + return -EBADMSG; + len = unaligned_read_be32(i.iov_base); + iovec_inc(&i, sizeof(uint32_t)); + break; + default: + assert_not_reached(); + } + + if (i.iov_len < len) + return -EBADMSG; + + r = tlv_append_impl(tlv, tag, len, i.iov_base); + if (r < 0) + return r; + + iovec_inc(&i, len); + } + + return 0; +} + +size_t tlv_size(const TLV *tlv) { + assert(tlv); + + size_t header_sz; + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + header_sz = sizeof(uint8_t); + break; + case TLV_TAG_U16: + header_sz = sizeof(uint16_t); + break; + case TLV_TAG_U32: + header_sz = sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + header_sz += sizeof(uint8_t); + break; + case TLV_LENGTH_U16: + header_sz += sizeof(uint16_t); + break; + case TLV_LENGTH_U32: + header_sz += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + size_t sz = FLAGS_SET(tlv->flags, TLV_APPEND_END); + + struct iovec_wrapper *iovw; + HASHMAP_FOREACH(iovw, tlv->entries) { + if (size_multiply_overflow(header_sz, iovw->count)) + return SIZE_MAX; + + sz = size_add(sz, size_add(header_sz * iovw->count, iovw_size(iovw))); + } + + return sz; +} + +int tlv_build(const TLV *tlv, struct iovec *ret) { + int r; + + assert(tlv); + assert(ret); + + size_t sz = tlv_size(tlv); + if (sz == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + /* Sort by tags, for reproducibility. */ + _cleanup_free_ void **sorted = NULL; + size_t n; + r = hashmap_dump_keys_sorted(tlv->entries, &sorted, &n); + if (r < 0) + return r; + + uint8_t *p = buf; + FOREACH_ARRAY(tagp, sorted, n) { + uint32_t tag = PTR_TO_UINT32(*tagp); + struct iovec_wrapper *iovw = ASSERT_PTR(tlv_get_all(tlv, tag)); + + if ((FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) || + (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END)) + return -EINVAL; + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (tag > UINT8_MAX) + return -EINVAL; + *p++ = tag; + break; + case TLV_TAG_U16: + if (tag > UINT16_MAX) + return -EINVAL; + unaligned_write_be16(p, tag); + p += sizeof(uint16_t); + break; + case TLV_TAG_U32: + unaligned_write_be32(p, tag); + p += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + if (iov->iov_len > UINT8_MAX) + return -EINVAL; + *p++ = iov->iov_len; + break; + case TLV_LENGTH_U16: + if (iov->iov_len > UINT16_MAX) + return -EINVAL; + unaligned_write_be16(p, iov->iov_len); + p += sizeof(uint16_t); + break; + case TLV_LENGTH_U32: + if (iov->iov_len > UINT32_MAX) + return -EINVAL; + unaligned_write_be32(p, iov->iov_len); + p += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + p = mempcpy_safe(p, iov->iov_base, iov->iov_len); + } + } + + if (FLAGS_SET(tlv->flags, TLV_APPEND_END)) + *p++ = TLV_TAG_END; + + assert(sz == (size_t) (p - buf)); + + *ret = IOVEC_MAKE(TAKE_PTR(buf), sz); + return 0; +} diff --git a/src/libsystemd-network/tlv-util.h b/src/libsystemd-network/tlv-util.h new file mode 100644 index 0000000000000..5344c28703244 --- /dev/null +++ b/src/libsystemd-network/tlv-util.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#define TLV_TAG_PAD UINT32_C(0) +#define TLV_TAG_END UINT32_C(0xFF) + +typedef enum TLVFlag { + TLV_TAG_U8 = 1 << 0, + TLV_TAG_U16 = 1 << 1, + TLV_TAG_U32 = 1 << 2, + _TLV_TAG_MASK = TLV_TAG_U8 | TLV_TAG_U16 | TLV_TAG_U32, + TLV_LENGTH_U8 = 1 << 3, + TLV_LENGTH_U16 = 1 << 4, + TLV_LENGTH_U32 = 1 << 5, + _TLV_LENGTH_MASK = TLV_LENGTH_U8 | TLV_LENGTH_U16 | TLV_LENGTH_U32, + TLV_PAD = 1 << 6, /* If set, tag == 0 is a pad, and does not have the length field. */ + TLV_END = 1 << 7, /* If set, tag == 0xFF is a sign of the end of the sequence. */ + TLV_APPEND_END = 1 << 8, /* If set, append the END tag at the end of the sequence on build. */ + TLV_MERGE = 1 << 9, /* If set, tlv_get_alloc() merges them, and tlv_append() split long data. */ + TLV_TEMPORARY = 1 << 10, /* If set, tlv_append() and tlv_parse() do not copy the data. */ + + /* DHCPv4 options. */ + TLV_DHCP4 = TLV_TAG_U8 | TLV_LENGTH_U8 | TLV_PAD | TLV_END | TLV_APPEND_END | TLV_MERGE, + /* Used for DHCPv4 sub-options, e.g. + * DHCPv4 Vendor Specific Information sub-option (43), + * DHCPv4 Relay Agent Information sub-option (82), or + * DHCPv4 Vendor-Identifying Vendor Specific Information sub-sub-option (125). + * Note that the PAD is not mentioned in RFC, but some implementations use it, hence let's gracefully + * handle it. Also note that the END tag is prohibited in most options, but we also gracefully handle + * it on parse, but of course do not append it on build. */ + TLV_DHCP4_SUBOPTION + = TLV_TAG_U8 | TLV_LENGTH_U8 | TLV_PAD | TLV_END, + /* DHCPv4 Vendor-Identifying Vendor Class sub-option (124) and + * DHCPv4 Vendor-Identifying Vendor Specific Information sub-option (125). + * The tag is called 'enterprise-number', and in uint32. */ + TLV_DHCP4_VENDOR_IDENTIFYING_OPTION + = TLV_TAG_U32 | TLV_LENGTH_U8 | TLV_MERGE, +} TLVFlag; + +typedef struct TLV { + unsigned n_ref; + TLVFlag flags; + unsigned n_entries; + Hashmap *entries; +} TLV; + +#define TLV_INIT(f) \ + (TLV) { \ + .n_ref = 1, \ + .flags = tlv_flags_verify(f), \ + } + +TLVFlag tlv_flags_verify(TLVFlag flags); + +void tlv_done(TLV *tlv); +TLV* tlv_ref(TLV *p); +TLV* tlv_unref(TLV *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(TLV*, tlv_unref); +TLV* tlv_new(TLVFlag flags); + +bool tlv_isempty(const TLV *tlv); + +struct iovec_wrapper* tlv_get_all(const TLV *tlv, uint32_t tag); +static inline bool tlv_contains(const TLV *tlv, uint32_t tag) { + return tlv_get_all(tlv, tag); +} +int tlv_get_full(const TLV *tlv, uint32_t tag, size_t length, struct iovec *ret); +static inline int tlv_get(const TLV *tlv, uint32_t tag, struct iovec *ret) { + return tlv_get_full(tlv, tag, SIZE_MAX, ret); +} +int tlv_get_alloc(const TLV *tlv, uint32_t tag, struct iovec *ret); + +void tlv_remove(TLV *tlv, uint32_t tag); +int tlv_append(TLV *tlv, uint32_t tag, size_t length, const void *data); +int tlv_append_iov(TLV *tlv, uint32_t tag, const struct iovec *iov); +int tlv_append_tlv(TLV *tlv, const TLV *source); + +int tlv_parse(TLV *tlv, const struct iovec *iov); +size_t tlv_size(const TLV *tlv); +int tlv_build(const TLV *tlv, struct iovec *ret); From 53b16b68e14275b9e0ea2eb2df51461f648d47ea Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 11 May 2026 22:36:29 +0100 Subject: [PATCH 1572/2155] test: start systemd-report-basic.socket again SUSE uses a different preset, so don't just assert in the test, instead just start the socket in case it is not enabled TEST-74-AUX-UTILS.sh[1594]: ++ systemctl is-enabled systemd-report-basic.socket TEST-74-AUX-UTILS.sh[1540]: + [[ disabled == enabled ]] TEST-74-AUX-UTILS.sh[120]: + echo 'Subtest /usr/lib/systemd/tests/testdata/units/TEST-74-AUX-UTILS.report.sh failed' Follow-up for 4409e52494d803426a365b6636a66fd2dfc70b62 --- test/units/TEST-74-AUX-UTILS.report.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 7475978336f14..456132c04df92 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -51,7 +51,8 @@ varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} # test io.systemd.Basic Metrics -[[ "$(systemctl is-enabled systemd-report-basic.socket)" == enabled ]] +# ensure the socket is running, as some distros don't enable it by default +systemctl start systemd-report-basic.socket varlinkctl info /run/systemd/report/io.systemd.Basic varlinkctl list-methods /run/systemd/report/io.systemd.Basic varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} From dc6b6e9d649f0738fa20862bd9daee76cc336783 Mon Sep 17 00:00:00 2001 From: Artem Proskurnev Date: Tue, 12 May 2026 11:07:39 +0300 Subject: [PATCH 1573/2155] hwdb/keyboard: Map f21 key on Wareus B15 Addition to PR https://github.com/systemd/systemd/pull/41181 Plasma-workspace OSD notifications about turning the touchpad on and off are guided by f21. When this match is specified, KDE notifies on this laptop that the on/off switch of the atchpad state is pressed. Fix dmesg: atkbd serio0: Unknown key pressed (translated set 2, code 0xc1 on isa0060/serio0). --- hwdb.d/60-keyboard.hwdb | 1 + 1 file changed, 1 insertion(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index c9c0ea1db1afd..fa3e4fb154eac 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2186,6 +2186,7 @@ evdev:name:SIPODEV USB Composite Device:dmi:bvn*:bvr*:bd*:svnVIOS:pnLTH17:* # Wareus B15 (8AD5A) evdev:atkbd:dmi:bvn*:bvr*:bd*:svnWareus*:pnB15*:* KEYBOARD_KEY_55=fn + KEYBOARD_KEY_c1=f21 ########################################################### # WeiHeng From 60d6f70ca4ced0a426800c55eaefa17438b89c3e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 21:58:24 +0200 Subject: [PATCH 1574/2155] btrfs-util: Make nested subvolume operations work unpriv BTRFS_IOC_SEARCH is only available to root in the initial userns. This means we fail to recursively snapshot even if a subvolume has no nested subvolumes at the moment. Let's fix this by using the newer btrfs ioctls which do work even if we don't have CAP_SYS_ADMIN in the initial userns. --- src/shared/btrfs-util.c | 150 ++++++++++++---------------------------- 1 file changed, 46 insertions(+), 104 deletions(-) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index bb3e28f6bbba4..9c86e49f0f86c 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -863,19 +863,6 @@ int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { } static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = UINT64_MAX, - }; - struct btrfs_ioctl_vol_args vol_args = {}; _cleanup_close_ int subvol_fd = -EBADF; bool made_writable = false; @@ -923,43 +910,29 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) return -errno; - /* OK, the subvolume is not empty, let's look for child - * subvolumes, and remove them, first */ - - args.key.min_offset = args.key.max_offset = subvol_id; + /* OK, the subvolume is not empty, let's look for child subvolumes, and remove them, first. + * BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER (kernel 4.18+) enumerate child + * subvolumes without requiring CAP_SYS_ADMIN in the initial user namespace, unlike the older + * BTRFS_IOC_TREE_SEARCH ioctl. */ - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - struct btrfs_ioctl_search_header sh; - const void *body; + struct btrfs_ioctl_get_subvol_rootref_args rootref_args = {}; - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + for (;;) { + /* BTRFS_IOC_GET_SUBVOL_ROOTREF returns up to BTRFS_MAX_ROOTREF_BUFFER_NUM entries per + * call. If more are available, it returns -EOVERFLOW with num_items filled in and + * min_treeid advanced so we can resume on the next iteration. */ + int ioctl_ret = ioctl(subvol_fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootref_args); + if (ioctl_ret < 0 && errno != EOVERFLOW) return -errno; - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) { - _cleanup_free_ char *p = NULL; - - btrfs_ioctl_search_args_set(&args, &sh); - - if (sh.type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh.offset != subvol_id) - continue; - - const struct btrfs_root_ref *ref = body; - p = memdup_suffix0((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; + for (uint8_t i = 0; i < rootref_args.num_items; i++) { + uint64_t child_subvol_id = rootref_args.rootref[i].treeid; - struct btrfs_ioctl_ino_lookup_args ino_args = { - .treeid = subvol_id, - .objectid = htole64(ref->dirid), + struct btrfs_ioctl_ino_lookup_user_args lookup_args = { + .dirid = rootref_args.rootref[i].dirid, + .treeid = child_subvol_id, }; - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + if (ioctl(subvol_fd, BTRFS_IOC_INO_LOOKUP_USER, &lookup_args) < 0) return -errno; if (!made_writable) { @@ -970,29 +943,26 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol made_writable = true; } - if (isempty(ino_args.name)) - /* Subvolume is in the top-level - * directory of the subvolume. */ - r = subvol_remove_children(subvol_fd, p, sh.objectid, flags); + if (isempty(lookup_args.path)) + /* Subvolume is in the top-level directory of the subvolume. */ + r = subvol_remove_children(subvol_fd, lookup_args.name, child_subvol_id, flags); else { _cleanup_close_ int child_fd = -EBADF; - /* Subvolume is somewhere further down, - * hence we need to open the + /* Subvolume is somewhere further down, hence we need to open the * containing directory first */ - child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + child_fd = openat(subvol_fd, lookup_args.path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (child_fd < 0) return -errno; - r = subvol_remove_children(child_fd, p, sh.objectid, flags); + r = subvol_remove_children(child_fd, lookup_args.name, child_subvol_id, flags); } if (r < 0) return r; } - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) + if (ioctl_ret >= 0) break; } @@ -1223,19 +1193,6 @@ static int subvol_snapshot_children( uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = UINT64_MAX, - }; - struct btrfs_ioctl_vol_args_v2 vol_args = { .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, .fd = old_fd, @@ -1293,50 +1250,36 @@ static int subvol_snapshot_children( return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; } - args.key.min_offset = args.key.max_offset = old_subvol_id; + /* Enumerate child subvolumes via BTRFS_IOC_GET_SUBVOL_ROOTREF + BTRFS_IOC_INO_LOOKUP_USER + * (kernel 4.18+), neither of which requires CAP_SYS_ADMIN, unlike BTRFS_IOC_TREE_SEARCH. */ - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - struct btrfs_ioctl_search_header sh; - const void *body; + struct btrfs_ioctl_get_subvol_rootref_args rootref_args = {}; - args.key.nr_items = 256; - if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + for (;;) { + /* BTRFS_IOC_GET_SUBVOL_ROOTREF returns up to BTRFS_MAX_ROOTREF_BUFFER_NUM entries per + * call. If more are available, it returns -EOVERFLOW with num_items filled in and + * min_treeid advanced so we can resume on the next iteration. */ + int ioctl_ret = ioctl(old_fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootref_args); + if (ioctl_ret < 0 && errno != EOVERFLOW) return -errno; - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) { - _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; + for (uint8_t i = 0; i < rootref_args.num_items; i++) { + _cleanup_free_ char *c = NULL, *np = NULL; _cleanup_close_ int old_child_fd = -EBADF, new_child_fd = -EBADF; - - btrfs_ioctl_search_args_set(&args, &sh); - - if (sh.type != BTRFS_ROOT_BACKREF_KEY) - continue; - - /* Avoid finding the source subvolume a second time */ - if (sh.offset != old_subvol_id) - continue; + uint64_t child_subvol_id = rootref_args.rootref[i].treeid; /* Avoid running into loops if the new subvolume is below the old one. */ - if (sh.objectid == new_subvol_id) + if (child_subvol_id == new_subvol_id) continue; - const struct btrfs_root_ref *ref = body; - p = memdup_suffix0((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - struct btrfs_ioctl_ino_lookup_args ino_args = { - .treeid = old_subvol_id, - .objectid = htole64(ref->dirid), + struct btrfs_ioctl_ino_lookup_user_args lookup_args = { + .dirid = rootref_args.rootref[i].dirid, + .treeid = child_subvol_id, }; - - if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP_USER, &lookup_args) < 0) return -errno; - c = path_join(ino_args.name, p); + c = path_join(lookup_args.path, lookup_args.name); if (!c) return -ENOMEM; @@ -1344,7 +1287,7 @@ static int subvol_snapshot_children( if (old_child_fd < 0) return -errno; - np = path_join(subvolume, ino_args.name); + np = path_join(subvolume, lookup_args.path); if (!np) return -ENOMEM; @@ -1369,7 +1312,7 @@ static int subvol_snapshot_children( /* When btrfs clones the subvolumes, child subvolumes appear as empty * directories. Remove them, so that we can create a new snapshot in their place */ - if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { + if (unlinkat(new_child_fd, lookup_args.name, AT_REMOVEDIR) < 0) { int k = -errno; if (flags & BTRFS_SNAPSHOT_READ_ONLY) @@ -1378,7 +1321,7 @@ static int subvol_snapshot_children( return k; } - r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh.objectid, + r = subvol_snapshot_children(old_child_fd, new_child_fd, lookup_args.name, child_subvol_id, flags & ~(BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_LOCK_BSD)); /* Restore the readonly flag */ @@ -1394,8 +1337,7 @@ static int subvol_snapshot_children( return r; } - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) + if (ioctl_ret >= 0) break; } From 47747cd2129b750ae519c0803d01cc96ce9e3100 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 11:38:25 +0900 Subject: [PATCH 1575/2155] sd-dhcp-protocol: rename DHCP option 43, 124, and 125 There are four DHCP options with confusing names: Option 43: Vendor-Specific Information Option 60: Vendor Class Identifier Option 124: Vendor-Identifying Vendor Class Option 125: Vendor-Identifying Vendor-Specific Information Let's use their full names for their corresponding enums. --- src/libsystemd-network/dhcp-option.c | 2 +- src/libsystemd-network/dhcp-protocol.c | 302 +++++++++++------------ src/libsystemd-network/sd-dhcp-client.c | 4 +- src/libsystemd-network/sd-dhcp-lease.c | 2 +- src/libsystemd-network/sd-dhcp-server.c | 2 +- src/systemd/sd-dhcp-protocol.h | 306 ++++++++++++------------ 6 files changed, 309 insertions(+), 309 deletions(-) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index a195a4b098353..74bc21e95ba23 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -107,7 +107,7 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 3 + optlen; break; - case SD_DHCP_OPTION_VENDOR_SPECIFIC: { + case SD_DHCP_OPTION_VENDOR_SPECIFIC_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 OrderedSet*. */ if (optlen > 0) diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c index 3a22e0f225aaa..caf92c494ef34 100644 --- a/src/libsystemd-network/dhcp-protocol.c +++ b/src/libsystemd-network/dhcp-protocol.c @@ -34,157 +34,157 @@ 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", + [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_INFORMATION] = "vendor-specific information", + [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_IDENTIFYING_VENDOR_CLASS] = "vendor-identifying vendor class", + [SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION] = "vendor-identifying 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/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index c6a4d02f965a4..45a0eb8674a90 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -65,7 +65,7 @@ static const uint8_t default_req_opts_anonymize[] = { SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ - SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* 43 */ SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ @@ -972,7 +972,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, if (!ordered_hashmap_isempty(client->vendor_options)) { r = dhcp_option_append( &packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* optlen= */ 0, client->vendor_options); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index fa41d293da2f3..1eec439d81065 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -1092,7 +1092,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; - case SD_DHCP_OPTION_VENDOR_SPECIFIC: + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: if (len <= 0) lease->vendor_specific = mfree(lease->vendor_specific); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index ba2b035821918..7519189229ca0 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -728,7 +728,7 @@ static int server_send_offer_or_ack( if (!ordered_set_isempty(server->vendor_options)) { r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* optlen= */ 0, server->vendor_options); if (r < 0) return r; diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index 1d3b635c31eff..55ce189cf4979 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -23,174 +23,174 @@ _SD_BEGIN_DECLARATIONS; /* https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options */ enum { - SD_DHCP_OPTION_PAD = 0, /* [RFC2132] */ - SD_DHCP_OPTION_SUBNET_MASK = 1, /* [RFC2132] */ - SD_DHCP_OPTION_TIME_OFFSET = 2, /* [RFC2132], deprecated by 100 and 101 */ - SD_DHCP_OPTION_ROUTER = 3, /* [RFC2132] */ - SD_DHCP_OPTION_TIME_SERVER = 4, /* [RFC2132] */ - SD_DHCP_OPTION_NAME_SERVER = 5, /* [RFC2132] */ - SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, /* [RFC2132] */ - SD_DHCP_OPTION_LOG_SERVER = 7, /* [RFC2132] */ - SD_DHCP_OPTION_QUOTES_SERVER = 8, /* [RFC2132] */ - SD_DHCP_OPTION_LPR_SERVER = 9, /* [RFC2132] */ - SD_DHCP_OPTION_IMPRESS_SERVER = 10, /* [RFC2132] */ - SD_DHCP_OPTION_RLP_SERVER = 11, /* [RFC2132] */ - SD_DHCP_OPTION_HOST_NAME = 12, /* [RFC2132] */ - SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, /* [RFC2132] */ - SD_DHCP_OPTION_MERIT_DUMP_FILE = 14, /* [RFC2132] */ - SD_DHCP_OPTION_DOMAIN_NAME = 15, /* [RFC2132] */ - SD_DHCP_OPTION_SWAP_SERVER = 16, /* [RFC2132] */ - SD_DHCP_OPTION_ROOT_PATH = 17, /* [RFC2132] */ - SD_DHCP_OPTION_EXTENSION_FILE = 18, /* [RFC2132] */ - SD_DHCP_OPTION_FORWARD = 19, /* [RFC2132] */ - SD_DHCP_OPTION_SOURCE_ROUTE = 20, /* [RFC2132] */ - SD_DHCP_OPTION_POLICY_FILTER = 21, /* [RFC2132] */ - SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY = 22, /* [RFC2132] */ - SD_DHCP_OPTION_DEFAULT_IP_TTL = 23, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_TIMEOUT = 24, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_PLATEAU = 25, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_INTERFACE = 26, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_SUBNET = 27, /* [RFC2132] */ - SD_DHCP_OPTION_BROADCAST = 28, /* [RFC2132] */ - SD_DHCP_OPTION_MASK_DISCOVERY = 29, /* [RFC2132] */ - SD_DHCP_OPTION_MASK_SUPPLIER = 30, /* [RFC2132] */ - SD_DHCP_OPTION_ROUTER_DISCOVERY = 31, /* [RFC2132] */ - SD_DHCP_OPTION_ROUTER_REQUEST = 32, /* [RFC2132] */ - SD_DHCP_OPTION_STATIC_ROUTE = 33, /* [RFC2132] */ - SD_DHCP_OPTION_TRAILERS = 34, /* [RFC2132] */ - SD_DHCP_OPTION_ARP_TIMEOUT = 35, /* [RFC2132] */ - SD_DHCP_OPTION_ETHERNET = 36, /* [RFC2132] */ - SD_DHCP_OPTION_DEFAULT_TCP_TTL = 37, /* [RFC2132] */ - SD_DHCP_OPTION_KEEPALIVE_TIME = 38, /* [RFC2132] */ - SD_DHCP_OPTION_KEEPALIVE_DATA = 39, /* [RFC2132] */ - SD_DHCP_OPTION_NIS_DOMAIN = 40, /* [RFC2132] */ - SD_DHCP_OPTION_NIS_SERVER = 41, /* [RFC2132] */ - SD_DHCP_OPTION_NTP_SERVER = 42, /* [RFC2132] */ - SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_NAME_SERVER = 44, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_DIST_SERVER = 45, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_NODE_TYPE = 46, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_SCOPE = 47, /* [RFC2132] */ - SD_DHCP_OPTION_X_WINDOW_FONT = 48, /* [RFC2132] */ - SD_DHCP_OPTION_X_WINDOW_MANAGER = 49, /* [RFC2132] */ - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, /* [RFC2132] */ - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, /* [RFC2132] */ - SD_DHCP_OPTION_OVERLOAD = 52, /* [RFC2132] */ - SD_DHCP_OPTION_MESSAGE_TYPE = 53, /* [RFC2132] */ - SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, /* [RFC2132] */ - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, /* [RFC2132] */ - SD_DHCP_OPTION_ERROR_MESSAGE = 56, /* [RFC2132] */ - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, /* [RFC2132] */ - SD_DHCP_OPTION_RENEWAL_TIME = 58, /* [RFC2132] */ - SD_DHCP_OPTION_REBINDING_TIME = 59, /* [RFC2132] */ - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, /* [RFC2132] */ - SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, /* [RFC2132] */ - SD_DHCP_OPTION_NETWARE_IP_DOMAIN = 62, /* [RFC2242] */ - SD_DHCP_OPTION_NETWARE_IP_OPTION = 63, /* [RFC2242] */ - SD_DHCP_OPTION_NIS_DOMAIN_NAME = 64, /* [RFC2132] */ - 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_ADDRESS = 68, /* [RFC2132] */ - SD_DHCP_OPTION_SMTP_SERVER = 69, /* [RFC2132] */ - SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ - SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ - SD_DHCP_OPTION_WWW_SERVER = 72, /* [RFC2132] */ - SD_DHCP_OPTION_FINGER_SERVER = 73, /* [RFC2132] */ - SD_DHCP_OPTION_IRC_SERVER = 74, /* [RFC2132] */ - SD_DHCP_OPTION_STREETTALK_SERVER = 75, /* [RFC2132] */ - SD_DHCP_OPTION_STDA_SERVER = 76, /* [RFC2132] */ - SD_DHCP_OPTION_USER_CLASS = 77, /* [RFC3004] */ - SD_DHCP_OPTION_DIRECTORY_AGENT = 78, /* [RFC2610] */ - SD_DHCP_OPTION_SERVICE_SCOPE = 79, /* [RFC2610] */ - SD_DHCP_OPTION_RAPID_COMMIT = 80, /* [RFC4039] */ - SD_DHCP_OPTION_FQDN = 81, /* [RFC4702] */ - SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82, /* [RFC3046] */ - SD_DHCP_OPTION_ISNS = 83, /* [RFC4174] */ + SD_DHCP_OPTION_PAD = 0, /* [RFC2132] */ + SD_DHCP_OPTION_SUBNET_MASK = 1, /* [RFC2132] */ + SD_DHCP_OPTION_TIME_OFFSET = 2, /* [RFC2132], deprecated by 100 and 101 */ + SD_DHCP_OPTION_ROUTER = 3, /* [RFC2132] */ + SD_DHCP_OPTION_TIME_SERVER = 4, /* [RFC2132] */ + SD_DHCP_OPTION_NAME_SERVER = 5, /* [RFC2132] */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, /* [RFC2132] */ + SD_DHCP_OPTION_LOG_SERVER = 7, /* [RFC2132] */ + SD_DHCP_OPTION_QUOTES_SERVER = 8, /* [RFC2132] */ + SD_DHCP_OPTION_LPR_SERVER = 9, /* [RFC2132] */ + SD_DHCP_OPTION_IMPRESS_SERVER = 10, /* [RFC2132] */ + SD_DHCP_OPTION_RLP_SERVER = 11, /* [RFC2132] */ + SD_DHCP_OPTION_HOST_NAME = 12, /* [RFC2132] */ + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, /* [RFC2132] */ + SD_DHCP_OPTION_MERIT_DUMP_FILE = 14, /* [RFC2132] */ + SD_DHCP_OPTION_DOMAIN_NAME = 15, /* [RFC2132] */ + SD_DHCP_OPTION_SWAP_SERVER = 16, /* [RFC2132] */ + SD_DHCP_OPTION_ROOT_PATH = 17, /* [RFC2132] */ + SD_DHCP_OPTION_EXTENSION_FILE = 18, /* [RFC2132] */ + SD_DHCP_OPTION_FORWARD = 19, /* [RFC2132] */ + SD_DHCP_OPTION_SOURCE_ROUTE = 20, /* [RFC2132] */ + SD_DHCP_OPTION_POLICY_FILTER = 21, /* [RFC2132] */ + SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY = 22, /* [RFC2132] */ + SD_DHCP_OPTION_DEFAULT_IP_TTL = 23, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_TIMEOUT = 24, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_PLATEAU = 25, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_INTERFACE = 26, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_SUBNET = 27, /* [RFC2132] */ + SD_DHCP_OPTION_BROADCAST = 28, /* [RFC2132] */ + SD_DHCP_OPTION_MASK_DISCOVERY = 29, /* [RFC2132] */ + SD_DHCP_OPTION_MASK_SUPPLIER = 30, /* [RFC2132] */ + SD_DHCP_OPTION_ROUTER_DISCOVERY = 31, /* [RFC2132] */ + SD_DHCP_OPTION_ROUTER_REQUEST = 32, /* [RFC2132] */ + SD_DHCP_OPTION_STATIC_ROUTE = 33, /* [RFC2132] */ + SD_DHCP_OPTION_TRAILERS = 34, /* [RFC2132] */ + SD_DHCP_OPTION_ARP_TIMEOUT = 35, /* [RFC2132] */ + SD_DHCP_OPTION_ETHERNET = 36, /* [RFC2132] */ + SD_DHCP_OPTION_DEFAULT_TCP_TTL = 37, /* [RFC2132] */ + SD_DHCP_OPTION_KEEPALIVE_TIME = 38, /* [RFC2132] */ + SD_DHCP_OPTION_KEEPALIVE_DATA = 39, /* [RFC2132] */ + SD_DHCP_OPTION_NIS_DOMAIN = 40, /* [RFC2132] */ + SD_DHCP_OPTION_NIS_SERVER = 41, /* [RFC2132] */ + SD_DHCP_OPTION_NTP_SERVER = 42, /* [RFC2132] */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION = 43, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_NAME_SERVER = 44, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_DIST_SERVER = 45, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_NODE_TYPE = 46, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_SCOPE = 47, /* [RFC2132] */ + SD_DHCP_OPTION_X_WINDOW_FONT = 48, /* [RFC2132] */ + SD_DHCP_OPTION_X_WINDOW_MANAGER = 49, /* [RFC2132] */ + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, /* [RFC2132] */ + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, /* [RFC2132] */ + SD_DHCP_OPTION_OVERLOAD = 52, /* [RFC2132] */ + SD_DHCP_OPTION_MESSAGE_TYPE = 53, /* [RFC2132] */ + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, /* [RFC2132] */ + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, /* [RFC2132] */ + SD_DHCP_OPTION_ERROR_MESSAGE = 56, /* [RFC2132] */ + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, /* [RFC2132] */ + SD_DHCP_OPTION_RENEWAL_TIME = 58, /* [RFC2132] */ + SD_DHCP_OPTION_REBINDING_TIME = 59, /* [RFC2132] */ + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, /* [RFC2132] */ + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, /* [RFC2132] */ + SD_DHCP_OPTION_NETWARE_IP_DOMAIN = 62, /* [RFC2242] */ + SD_DHCP_OPTION_NETWARE_IP_OPTION = 63, /* [RFC2242] */ + SD_DHCP_OPTION_NIS_DOMAIN_NAME = 64, /* [RFC2132] */ + 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_ADDRESS = 68, /* [RFC2132] */ + SD_DHCP_OPTION_SMTP_SERVER = 69, /* [RFC2132] */ + SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ + SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ + SD_DHCP_OPTION_WWW_SERVER = 72, /* [RFC2132] */ + SD_DHCP_OPTION_FINGER_SERVER = 73, /* [RFC2132] */ + SD_DHCP_OPTION_IRC_SERVER = 74, /* [RFC2132] */ + SD_DHCP_OPTION_STREETTALK_SERVER = 75, /* [RFC2132] */ + SD_DHCP_OPTION_STDA_SERVER = 76, /* [RFC2132] */ + SD_DHCP_OPTION_USER_CLASS = 77, /* [RFC3004] */ + SD_DHCP_OPTION_DIRECTORY_AGENT = 78, /* [RFC2610] */ + SD_DHCP_OPTION_SERVICE_SCOPE = 79, /* [RFC2610] */ + SD_DHCP_OPTION_RAPID_COMMIT = 80, /* [RFC4039] */ + SD_DHCP_OPTION_FQDN = 81, /* [RFC4702] */ + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82, /* [RFC3046] */ + SD_DHCP_OPTION_ISNS = 83, /* [RFC4174] */ /* option code 84 is unassigned [RFC3679] */ - SD_DHCP_OPTION_NDS_SERVER = 85, /* [RFC2241] */ - SD_DHCP_OPTION_NDS_TREE_NAME = 86, /* [RFC2241] */ - SD_DHCP_OPTION_NDS_CONTEXT = 87, /* [RFC2241] */ - SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME = 88, /* [RFC4280] */ - SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS = 89, /* [RFC4280] */ - SD_DHCP_OPTION_AUTHENTICATION = 90, /* [RFC3118] */ - SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME = 91, /* [RFC4388] */ - SD_DHCP_OPTION_ASSOCIATED_IP = 92, /* [RFC4388] */ - SD_DHCP_OPTION_CLIENT_SYSTEM = 93, /* [RFC4578] */ - SD_DHCP_OPTION_CLIENT_NDI = 94, /* [RFC4578] */ - SD_DHCP_OPTION_LDAP = 95, /* [RFC3679] */ + SD_DHCP_OPTION_NDS_SERVER = 85, /* [RFC2241] */ + SD_DHCP_OPTION_NDS_TREE_NAME = 86, /* [RFC2241] */ + SD_DHCP_OPTION_NDS_CONTEXT = 87, /* [RFC2241] */ + SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME = 88, /* [RFC4280] */ + SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS = 89, /* [RFC4280] */ + SD_DHCP_OPTION_AUTHENTICATION = 90, /* [RFC3118] */ + SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME = 91, /* [RFC4388] */ + SD_DHCP_OPTION_ASSOCIATED_IP = 92, /* [RFC4388] */ + SD_DHCP_OPTION_CLIENT_SYSTEM = 93, /* [RFC4578] */ + SD_DHCP_OPTION_CLIENT_NDI = 94, /* [RFC4578] */ + SD_DHCP_OPTION_LDAP = 95, /* [RFC3679] */ /* option code 96 is unassigned [RFC3679] */ - SD_DHCP_OPTION_UUID = 97, /* [RFC4578] */ - SD_DHCP_OPTION_USER_AUTHENTICATION = 98, /* [RFC2485] */ - SD_DHCP_OPTION_GEOCONF_CIVIC = 99, /* [RFC4776] */ - SD_DHCP_OPTION_POSIX_TIMEZONE = 100, /* [RFC4833] */ - SD_DHCP_OPTION_TZDB_TIMEZONE = 101, /* [RFC4833] */ + SD_DHCP_OPTION_UUID = 97, /* [RFC4578] */ + SD_DHCP_OPTION_USER_AUTHENTICATION = 98, /* [RFC2485] */ + SD_DHCP_OPTION_GEOCONF_CIVIC = 99, /* [RFC4776] */ + SD_DHCP_OPTION_POSIX_TIMEZONE = 100, /* [RFC4833] */ + SD_DHCP_OPTION_TZDB_TIMEZONE = 101, /* [RFC4833] */ /* option codes 102-107 are unassigned [RFC3679] */ - SD_DHCP_OPTION_IPV6_ONLY_PREFERRED = 108, /* [RFC8925] */ - SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS = 109, /* [RFC8539] */ + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED = 108, /* [RFC8925] */ + SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS = 109, /* [RFC8539] */ /* option codes 110-111 are unassigned [RFC3679] */ - SD_DHCP_OPTION_NETINFO_ADDRESS = 112, /* [RFC3679] */ - SD_DHCP_OPTION_NETINFO_TAG = 113, /* [RFC3679] */ - SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL = 114, /* [RFC8910] */ + SD_DHCP_OPTION_NETINFO_ADDRESS = 112, /* [RFC3679] */ + SD_DHCP_OPTION_NETINFO_TAG = 113, /* [RFC3679] */ + SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL = 114, /* [RFC8910] */ /* option code 115 is unassigned [RFC3679] */ - SD_DHCP_OPTION_AUTO_CONFIG = 116, /* [RFC2563] */ - SD_DHCP_OPTION_NAME_SERVICE_SEARCH = 117, /* [RFC2937] */ - SD_DHCP_OPTION_SUBNET_SELECTION = 118, /* [RFC3011] */ - SD_DHCP_OPTION_DOMAIN_SEARCH = 119, /* [RFC3397] */ - SD_DHCP_OPTION_SIP_SERVER = 120, /* [RFC3361] */ - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, /* [RFC3442] */ - SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION = 122, /* [RFC3495] */ - SD_DHCP_OPTION_GEOCONF = 123, /* [RFC6225] */ - SD_DHCP_OPTION_VENDOR_CLASS = 124, /* [RFC3925] */ - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION = 125, /* [RFC3925] */ + SD_DHCP_OPTION_AUTO_CONFIG = 116, /* [RFC2563] */ + SD_DHCP_OPTION_NAME_SERVICE_SEARCH = 117, /* [RFC2937] */ + SD_DHCP_OPTION_SUBNET_SELECTION = 118, /* [RFC3011] */ + SD_DHCP_OPTION_DOMAIN_SEARCH = 119, /* [RFC3397] */ + SD_DHCP_OPTION_SIP_SERVER = 120, /* [RFC3361] */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, /* [RFC3442] */ + SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION = 122, /* [RFC3495] */ + SD_DHCP_OPTION_GEOCONF = 123, /* [RFC6225] */ + SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS = 124, /* [RFC3925] */ + SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION = 125, /* [RFC3925] */ /* option codes 126-127 are unassigned [RFC3679] */ /* option codes 128-135 are assigned to use by PXE, but they are vendor specific [RFC4578] */ - SD_DHCP_OPTION_PANA_AGENT = 136, /* [RFC5192] */ - SD_DHCP_OPTION_LOST_SERVER_FQDN = 137, /* [RFC5223] */ - SD_DHCP_OPTION_CAPWAP_AC_ADDRESS = 138, /* [RFC5417] */ - SD_DHCP_OPTION_MOS_ADDRESS = 139, /* [RFC5678] */ - SD_DHCP_OPTION_MOS_FQDN = 140, /* [RFC5678] */ - SD_DHCP_OPTION_SIP_SERVICE_DOMAIN = 141, /* [RFC6011] */ - SD_DHCP_OPTION_ANDSF_ADDRESS = 142, /* [RFC6153] */ - SD_DHCP_OPTION_SZTP_REDIRECT = 143, /* [RFC8572] */ - SD_DHCP_OPTION_GEOLOC = 144, /* [RFC6225] */ - SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE = 145, /* [RFC6704] */ - SD_DHCP_OPTION_RDNSS_SELECTION = 146, /* [RFC6731] */ - SD_DHCP_OPTION_DOTS_RI = 147, /* [RFC8973] */ - SD_DHCP_OPTION_DOTS_ADDRESS = 148, /* [RFC8973] */ + SD_DHCP_OPTION_PANA_AGENT = 136, /* [RFC5192] */ + SD_DHCP_OPTION_LOST_SERVER_FQDN = 137, /* [RFC5223] */ + SD_DHCP_OPTION_CAPWAP_AC_ADDRESS = 138, /* [RFC5417] */ + SD_DHCP_OPTION_MOS_ADDRESS = 139, /* [RFC5678] */ + SD_DHCP_OPTION_MOS_FQDN = 140, /* [RFC5678] */ + SD_DHCP_OPTION_SIP_SERVICE_DOMAIN = 141, /* [RFC6011] */ + SD_DHCP_OPTION_ANDSF_ADDRESS = 142, /* [RFC6153] */ + SD_DHCP_OPTION_SZTP_REDIRECT = 143, /* [RFC8572] */ + SD_DHCP_OPTION_GEOLOC = 144, /* [RFC6225] */ + SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE = 145, /* [RFC6704] */ + SD_DHCP_OPTION_RDNSS_SELECTION = 146, /* [RFC6731] */ + SD_DHCP_OPTION_DOTS_RI = 147, /* [RFC8973] */ + SD_DHCP_OPTION_DOTS_ADDRESS = 148, /* [RFC8973] */ /* option code 149 is unassigned [RFC3942] */ - SD_DHCP_OPTION_TFTP_SERVER_ADDRESS = 150, /* [RFC5859] */ - SD_DHCP_OPTION_STATUS_CODE = 151, /* [RFC6926] */ - SD_DHCP_OPTION_BASE_TIME = 152, /* [RFC6926] */ - SD_DHCP_OPTION_START_TIME_OF_STATE = 153, /* [RFC6926] */ - SD_DHCP_OPTION_QUERY_START_TIME = 154, /* [RFC6926] */ - SD_DHCP_OPTION_QUERY_END_TIME = 155, /* [RFC6926] */ - SD_DHCP_OPTION_DHCP_STATE = 156, /* [RFC6926] */ - SD_DHCP_OPTION_DATA_SOURCE = 157, /* [RFC6926] */ - SD_DHCP_OPTION_PCP_SERVER = 158, /* [RFC7291] */ - SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */ + SD_DHCP_OPTION_TFTP_SERVER_ADDRESS = 150, /* [RFC5859] */ + SD_DHCP_OPTION_STATUS_CODE = 151, /* [RFC6926] */ + SD_DHCP_OPTION_BASE_TIME = 152, /* [RFC6926] */ + SD_DHCP_OPTION_START_TIME_OF_STATE = 153, /* [RFC6926] */ + SD_DHCP_OPTION_QUERY_START_TIME = 154, /* [RFC6926] */ + SD_DHCP_OPTION_QUERY_END_TIME = 155, /* [RFC6926] */ + SD_DHCP_OPTION_DHCP_STATE = 156, /* [RFC6926] */ + SD_DHCP_OPTION_DATA_SOURCE = 157, /* [RFC6926] */ + SD_DHCP_OPTION_PCP_SERVER = 158, /* [RFC7291] */ + SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */ /* option code 160 is unassigned [RFC7710][RFC8910] */ - SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */ - SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */ + SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */ + SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */ /* option codes 163-174 are unassigned [RFC3942] */ /* option codes 175-177 are temporary assigned. */ /* option codes 178-207 are unassigned [RFC3942] */ - SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */ - SD_DHCP_OPTION_CONFIGURATION_FILE = 209, /* [RFC5071] */ - SD_DHCP_OPTION_PATH_PREFIX = 210, /* [RFC5071] */ - SD_DHCP_OPTION_REBOOT_TIME = 211, /* [RFC5071] */ - SD_DHCP_OPTION_6RD = 212, /* [RFC5969] */ - SD_DHCP_OPTION_ACCESS_DOMAIN = 213, /* [RFC5986] */ + SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */ + SD_DHCP_OPTION_CONFIGURATION_FILE = 209, /* [RFC5071] */ + SD_DHCP_OPTION_PATH_PREFIX = 210, /* [RFC5071] */ + SD_DHCP_OPTION_REBOOT_TIME = 211, /* [RFC5071] */ + SD_DHCP_OPTION_6RD = 212, /* [RFC5969] */ + SD_DHCP_OPTION_ACCESS_DOMAIN = 213, /* [RFC5986] */ /* option codes 214-219 are unassigned */ - SD_DHCP_OPTION_SUBNET_ALLOCATION = 220, /* [RFC6656] */ - SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION = 221, /* [RFC6607] */ + SD_DHCP_OPTION_SUBNET_ALLOCATION = 220, /* [RFC6656] */ + SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION = 221, /* [RFC6607] */ /* option codes 222-223 are unassigned [RFC3942] */ /* option codes 224-254 are reserved for private use */ - SD_DHCP_OPTION_PRIVATE_BASE = 224, - SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, /* [RFC7844] */ - SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, /* [RFC7844] */ - SD_DHCP_OPTION_PRIVATE_LAST = 254, - SD_DHCP_OPTION_END = 255 /* [RFC2132] */ + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, /* [RFC7844] */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, /* [RFC7844] */ + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255 /* [RFC2132] */ }; /* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */ From 431ec1b1451b3e6d0ddc17170a8bcc7bea3a2166 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 17:00:33 +0900 Subject: [PATCH 1576/2155] dhcp: use TLV object to manage extra and vendor options Note, previously we replaced the previous option with the same option code with new one. But, DHCP message can have multiple options with same option code. Hence, this make the conf parser not replace, but append new one. --- src/libsystemd-network/dhcp-client-internal.h | 8 +- src/libsystemd-network/dhcp-option.c | 28 -- src/libsystemd-network/dhcp-server-internal.h | 8 +- src/libsystemd-network/sd-dhcp-client.c | 68 ++--- src/libsystemd-network/sd-dhcp-server.c | 64 ++--- src/network/networkd-dhcp-common.c | 260 +++++++++++++----- src/network/networkd-dhcp-common.h | 4 +- src/network/networkd-dhcp-server.c | 22 +- src/network/networkd-dhcp4.c | 21 +- src/network/networkd-network-gperf.gperf | 12 +- src/network/networkd-network.c | 12 +- src/network/networkd-network.h | 9 +- src/systemd/sd-dhcp-client.h | 3 - src/systemd/sd-dhcp-server.h | 2 - 14 files changed, 300 insertions(+), 221 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index fab4ff24aaf99..8e23f7d931439 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -8,6 +8,7 @@ #include "network-common.h" #include "sd-forward.h" #include "socket-util.h" +#include "tlv-util.h" typedef enum DHCPState { DHCP_STATE_STOPPED, @@ -65,8 +66,8 @@ struct sd_dhcp_client { uint64_t discover_attempt; uint64_t request_attempt; uint64_t max_discover_attempts; - OrderedHashmap *extra_options; - OrderedHashmap *vendor_options; + TLV *extra_options; + TLV *vendor_options; sd_event_source *timeout_t1; sd_event_source *timeout_t2; sd_event_source *timeout_expire; @@ -90,6 +91,9 @@ int dhcp_client_set_state_callback( void *userdata); int dhcp_client_get_state(sd_dhcp_client *client); +int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options); +int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options); + int client_receive_message_raw( sd_event_source *s, int fd, diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 74bc21e95ba23..75470cb3d6eae 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -11,7 +11,6 @@ #include "dns-domain.h" #include "hostname-util.h" #include "memory-util.h" -#include "ordered-set.h" #include "string-util.h" #include "strv.h" #include "utf8.h" @@ -107,33 +106,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 3 + optlen; break; - case SD_DHCP_OPTION_VENDOR_SPECIFIC_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 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; - - ORDERED_SET_FOREACH(p, s) - l += p->length + 2; - - if (*offset + l + 2 > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = l; - *offset += 2; - - ORDERED_SET_FOREACH(p, s) { - r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data); - if (r < 0) - return r; - } - 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*. */ diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 8b7c856d218af..a8e2cbf5c706d 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -13,6 +13,7 @@ #include "sd-forward.h" #include "network-common.h" #include "sparse-endian.h" +#include "tlv-util.h" typedef enum DHCPRawOption { DHCP_RAW_OPTION_DATA_UINT8, @@ -53,8 +54,8 @@ typedef struct sd_dhcp_server { char *boot_server_name; char *boot_filename; - OrderedSet *extra_options; - OrderedSet *vendor_options; + TLV *extra_options; + TLV *vendor_options; bool emit_router; struct in_addr router_address; @@ -99,6 +100,9 @@ typedef struct DHCPRequest { triple_timestamp timestamp; } DHCPRequest; +int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options); +int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options); + int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp); int dhcp_server_send_packet(sd_dhcp_server *server, diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 45a0eb8674a90..0b13e925f2669 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -19,6 +19,7 @@ #include "event-util.h" #include "hostname-util.h" #include "iovec-util.h" +#include "iovec-wrapper.h" #include "memory-util.h" #include "network-common.h" #include "random-util.h" @@ -502,39 +503,18 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt return 0; } -int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) { - int r; - - assert_return(client, -EINVAL); - assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(v, -EINVAL); - - r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp_option_hash_ops, UINT_TO_PTR(v->option), v); - if (r < 0) - return r; +int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options) { + assert(client); + assert(!sd_dhcp_client_is_running(client)); - sd_dhcp_option_ref(v); - return 0; + return unref_and_replace_new_ref(client->extra_options, options, tlv_ref, tlv_unref); } -int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) { - int r; - - assert_return(client, -EINVAL); - assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(v, -EINVAL); - - r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops); - if (r < 0) - return -ENOMEM; - - r = ordered_hashmap_put(client->vendor_options, v, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); +int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options) { + assert(client); + assert(!sd_dhcp_client_is_running(client)); - return 1; + return unref_and_replace_new_ref(client->vendor_options, options, tlv_ref, tlv_unref); } int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { @@ -912,7 +892,6 @@ static int client_append_fqdn_option( } 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; assert(client); @@ -962,18 +941,31 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, return r; } - ORDERED_HASHMAP_FOREACH(j, client->extra_options) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - j->option, j->length, j->data); + if (client->extra_options) { + void *key; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, key, client->extra_options->entries) { + uint32_t tag = PTR_TO_UINT32(key); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + tag, iov->iov_len, iov->iov_base); + if (r < 0) + return r; + } + } + } + + if (!tlv_isempty(client->vendor_options)) { + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(client->vendor_options, &iov); if (r < 0) return r; - } - if (!ordered_hashmap_isempty(client->vendor_options)) { r = dhcp_option_append( &packet->dhcp, optlen, optoffset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - /* optlen= */ 0, client->vendor_options); + iov.iov_len, iov.iov_base); if (r < 0) return r; } @@ -2306,8 +2298,8 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { free(client->vendor_class_identifier); free(client->mudurl); client->user_class = strv_free(client->user_class); - ordered_hashmap_free(client->extra_options); - ordered_hashmap_free(client->vendor_options); + tlv_unref(client->extra_options); + tlv_unref(client->vendor_options); free(client->ifname); return mfree(client); } diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 7519189229ca0..48c481809f4e5 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -16,11 +16,12 @@ #include "dns-domain.h" #include "errno-util.h" #include "fd-util.h" +#include "hashmap.h" #include "in-addr-util.h" #include "iovec-util.h" +#include "iovec-wrapper.h" #include "memory-util.h" #include "network-common.h" -#include "ordered-set.h" #include "path-util.h" #include "siphash24.h" #include "socket-util.h" @@ -138,8 +139,8 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { server->static_leases_by_address = hashmap_free(server->static_leases_by_address); server->static_leases_by_client_id = hashmap_free(server->static_leases_by_client_id); - ordered_set_free(server->extra_options); - ordered_set_free(server->vendor_options); + tlv_unref(server->extra_options); + tlv_unref(server->vendor_options); free(server->agent_circuit_id); free(server->agent_remote_id); @@ -618,7 +619,6 @@ static int server_send_offer_or_ack( }; _cleanup_free_ DHCPPacket *packet = NULL; - sd_dhcp_option *j; be32_t lease_time; size_t offset; int r; @@ -718,18 +718,31 @@ static int server_send_offer_or_ack( return r; } - ORDERED_SET_FOREACH(j, server->extra_options) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - j->option, j->length, j->data); + if (server->extra_options) { + void *key; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) { + uint32_t tag = PTR_TO_UINT32(key); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + tag, iov->iov_len, iov->iov_base); + if (r < 0) + return r; + } + } + } + + if (!tlv_isempty(server->vendor_options)) { + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(server->vendor_options, &iov); if (r < 0) return r; - } - if (!ordered_set_isempty(server->vendor_options)) { r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - /* optlen= */ 0, server->vendor_options); + iov.iov_len, iov.iov_base); if (r < 0) return r; } @@ -1611,33 +1624,14 @@ int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *rout return 0; } -int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v) { - int r; - - assert_return(server, -EINVAL); - assert_return(v, -EINVAL); - - r = ordered_set_ensure_put(&server->extra_options, &dhcp_option_hash_ops, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); - return 0; +int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options) { + assert(server); + return unref_and_replace_new_ref(server->extra_options, options, tlv_ref, tlv_unref); } -int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v) { - int r; - - assert_return(server, -EINVAL); - assert_return(v, -EINVAL); - - r = ordered_set_ensure_put(&server->vendor_options, &dhcp_option_hash_ops, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); - - return 1; +int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options) { + assert(server); + return unref_and_replace_new_ref(server->vendor_options, options, tlv_ref, tlv_unref); } int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_t cb, void *userdata) { diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 3e7cca991b392..d7353d07ed41c 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -11,12 +11,12 @@ #include "alloc-util.h" #include "bus-error.h" #include "bus-locator.h" -#include "dhcp-option.h" #include "dhcp6-option.h" #include "escape.h" #include "extract-word.h" #include "hexdecoct.h" #include "in-addr-prefix-util.h" +#include "iovec-util.h" #include "networkd-dhcp-common.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-link.h" @@ -696,7 +696,7 @@ int config_parse_dhcp_user_or_vendor_class( } } -int config_parse_dhcp_send_option( +int config_parse_dhcp6_send_option( const char *unit, const char *filename, unsigned line, @@ -708,17 +708,15 @@ int config_parse_dhcp_send_option( void *data, void *userdata) { - _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL; _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL; - _unused_ _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *old4 = NULL; _unused_ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *old6 = NULL; uint32_t uint32_data, enterprise_identifier = 0; _cleanup_free_ char *word = NULL, *q = NULL; - OrderedHashmap **options = ASSERT_PTR(data); + OrderedHashmap **dhcp6_options = ASSERT_PTR(data); uint16_t u16, uint16_data; union in_addr_union addr; DHCPOptionDataType type; - uint8_t u8, uint8_data; + uint8_t uint8_data; const void *udata; const char *p; ssize_t sz; @@ -729,12 +727,12 @@ int config_parse_dhcp_send_option( assert(rvalue); if (isempty(rvalue)) { - *options = ordered_hashmap_free(*options); + *dhcp6_options = ordered_hashmap_free(*dhcp6_options); return 0; } p = rvalue; - if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) { + if (streq(lvalue, "SendVendorOption")) { r = extract_first_word(&p, &word, ":", 0); if (r == -ENOMEM) return log_oom(); @@ -762,30 +760,16 @@ int config_parse_dhcp_send_option( return 0; } - if (ltype == AF_INET6) { - r = safe_atou16(word, &u16); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid DHCP option, ignoring assignment: %s", rvalue); - return 0; - } - if (u16 < 1 || u16 >= UINT16_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); - return 0; - } - } else { - r = safe_atou8(word, &u8); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid DHCP option, ignoring assignment: %s", rvalue); - return 0; - } - if (u8 < 1 || u8 >= UINT8_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue); - return 0; - } + r = safe_atou16(word, &u16); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + if (u16 < 1 || u16 >= UINT16_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); + return 0; } word = mfree(word); @@ -887,49 +871,193 @@ int config_parse_dhcp_send_option( return -EINVAL; } - if (ltype == AF_INET6) { - r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } + r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + + r = ordered_hashmap_ensure_allocated(dhcp6_options, &dhcp6_option_hash_ops); + if (r < 0) + return log_oom(); + + /* Overwrite existing option */ + old6 = ordered_hashmap_get(*dhcp6_options, UINT_TO_PTR(u16)); + r = ordered_hashmap_replace(*dhcp6_options, UINT_TO_PTR(u16), opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + TAKE_PTR(opt6); - r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops); + return 0; +} + +int config_parse_dhcp_option( + 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) { + + struct iovec *iov = ASSERT_PTR(data); + bool check_length = ltype; + int r; + + if (isempty(rvalue)) { + iovec_done(iov); + return 0; + } + + _cleanup_free_ char *word = NULL; + const char *p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + DHCPOptionDataType type = dhcp_option_data_type_from_string(word); + if (type < 0) + return log_syntax_parse_error(unit, filename, line, type, lvalue, rvalue); + + switch (type) { + case DHCP_OPTION_DATA_UINT8:{ + uint8_t u; + + r = safe_atou8(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); if (r < 0) return log_oom(); - /* Overwrite existing option */ - old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16)); - r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } - TAKE_PTR(opt6); - } else { - r = sd_dhcp_option_new(u8, udata, sz, &opt4); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } + return 1; + } + case DHCP_OPTION_DATA_UINT16:{ + uint16_t u; - r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops); + r = safe_atou16(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + u = htobe16(u); + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); if (r < 0) return log_oom(); - /* Overwrite existing option */ - old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8)); - r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } - TAKE_PTR(opt4); + return 1; + } + case DHCP_OPTION_DATA_UINT32: { + uint32_t u; + + r = safe_atou32(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + u = htobe32(u); + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_IPV4ADDRESS: { + union in_addr_union a; + + r = in_addr_from_string(AF_INET, p, &a); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in, sizeof(a.in))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_IPV6ADDRESS: { + union in_addr_union a; + + r = in_addr_from_string(AF_INET6, p, &a); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in6, sizeof(a.in6))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_STRING: { + _cleanup_free_ char *s = NULL; + ssize_t sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &s); + if (sz < 0) + return log_syntax_parse_error(unit, filename, line, sz, lvalue, rvalue); + if (check_length && sz > UINT8_MAX) + return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue); + + iovec_done(iov); + *iov = IOVEC_MAKE(TAKE_PTR(s), sz); + return 1; + } + default: + return -EINVAL; } +} + +int config_parse_dhcp_option_tlv( + 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) { + + TLV *options = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + tlv_done(options); + return 0; + } + + _cleanup_free_ char *word = NULL; + const char *p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + uint8_t code; + r = safe_atou8(word, &code); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (code < 1 || code >= UINT8_MAX) + return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = config_parse_dhcp_option(unit, filename, line, section, section_line, lvalue, ltype, p, &iov, userdata); + if (r <= 0) + return r; + + r = tlv_append_iov(options, code, &iov); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store '%s=%s', ignoring assignment: %m", lvalue, rvalue); + return 0; } diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 9eb9331923b9f..0b88d7790c17a 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -82,7 +82,9 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_send_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option_tlv); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options); CONFIG_PARSER_PROTOTYPE(config_parse_duid_type); CONFIG_PARSER_PROTOTYPE(config_parse_manager_duid_type); diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index c88488258f002..9903c6b9d3233 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -7,6 +7,7 @@ #include "conf-parser.h" #include "dhcp-protocol.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dns-domain.h" #include "errno-util.h" @@ -567,7 +568,6 @@ static int dhcp_server_set_domain(Link *link) { static int dhcp4_server_configure(Link *link) { bool acquired_uplink = false; - sd_dhcp_option *p; DHCPStaticLease *static_lease; Link *uplink = NULL; Address *address; @@ -721,21 +721,13 @@ static int dhcp4_server_configure(Link *link) { else if (r < 0) return log_link_error_errno(link, r, "Failed to set domain name for DHCP server: %m"); - ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) { - r = sd_dhcp_server_add_option(link->dhcp_server, p); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); - } + r = dhcp_server_set_extra_options(link->dhcp_server, &link->network->dhcp_server_extra_options); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 extra options: %m"); - ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) { - r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); - } + r = dhcp_server_set_vendor_options(link->dhcp_server, &link->network->dhcp_server_vendor_options); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 vendor options: %m"); HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) { r = sd_dhcp_server_set_static_lease( diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index dc6b78e326c34..7304a049b699d 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1477,7 +1477,6 @@ static bool link_dhcp4_ipv6_only_mode(Link *link) { } static int dhcp4_configure(Link *link) { - sd_dhcp_option *send_option; void *request_options; int r; @@ -1619,21 +1618,13 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for '%u': %m", option); } - ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_options) { - r = sd_dhcp_client_add_option(link->dhcp_client, send_option); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m"); - } + r = dhcp_client_set_extra_options(link->dhcp_client, &link->network->dhcp_extra_options); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set extra options: %m"); - ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_vendor_options) { - r = sd_dhcp_client_add_vendor_option(link->dhcp_client, send_option); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m"); - } + r = dhcp_client_set_vendor_options(link->dhcp_client, &link->network->dhcp_vendor_options); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set vendor options: %m"); r = dhcp4_set_hostname(link); if (r < 0) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index aaf974e312d67..0f1c4e57c46f4 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -293,8 +293,8 @@ DHCPv4.DenyList, config_parse_in_addr_prefixes, DHCPv4.AllowList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_allow_listed_ip) DHCPv4.IPServiceType, config_parse_dhcp_ip_service_type, 0, offsetof(Network, dhcp_ip_service_type) DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, 0 -DHCPv4.SendOption, config_parse_dhcp_send_option, AF_INET, offsetof(Network, dhcp_client_send_options) -DHCPv4.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_client_send_vendor_options) +DHCPv4.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_extra_options) +DHCPv4.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_vendor_options) DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu) DHCPv4.InitialCongestionWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_initial_congestion_window) DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_advertised_receive_window) @@ -320,11 +320,11 @@ DHCPv6.Hostname, config_parse_hostname, DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 DHCPv6.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_user_class) DHCPv6.VendorClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_vendor_class) -DHCPv6.SendVendorOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_vendor_options) +DHCPv6.SendVendorOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_prefix_hint, 0, 0 DHCPv6.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp6_pd_prefix_route_type) DHCPv6.WithoutRA, config_parse_dhcp6_client_start_mode, 0, offsetof(Network, dhcp6_client_start_mode) -DHCPv6.SendOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_options) +DHCPv6.SendOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_options) DHCPv6.IAID, config_parse_iaid, AF_INET6, 0 DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid) @@ -387,8 +387,8 @@ DHCPServer.EmitDomain, config_parse_bool, DHCPServer.Domain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_domain) DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset) DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) -DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options) -DHCPServer.SendOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_options) +DHCPServer.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_extra_options) +DHCPServer.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_vendor_options) DHCPServer.BindToInterface, config_parse_bool, 0, offsetof(Network, dhcp_server_bind_to_interface) DHCPServer.BootServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_boot_server_address) DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3ffe1640e767b..0397e77f81003 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -397,6 +397,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_use_gateway = -1, .dhcp_send_hostname = true, .dhcp_send_release = true, + .dhcp_extra_options = TLV_INIT(TLV_DHCP4), + .dhcp_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_route_metric = DHCP_ROUTE_METRIC, .dhcp_use_rapid_commit = -1, .dhcp_client_identifier = _DHCP_CLIENT_ID_INVALID, @@ -436,6 +438,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_server_emit_router = true, .dhcp_server_emit_timezone = true, .dhcp_server_rapid_commit = true, + .dhcp_server_extra_options = TLV_INIT(TLV_DHCP4), + .dhcp_server_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_server_persist_leases = _DHCP_SERVER_PERSIST_LEASES_INVALID, .router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC, @@ -771,8 +775,8 @@ static Network *network_free(Network *network) { free(network->dhcp_server_uplink_name); for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++) free(network->dhcp_server_emit[t].addresses); - ordered_hashmap_free(network->dhcp_server_send_options); - ordered_hashmap_free(network->dhcp_server_send_vendor_options); + tlv_done(&network->dhcp_server_extra_options); + tlv_done(&network->dhcp_server_vendor_options); free(network->dhcp_server_local_lease_domain); /* DHCP client */ @@ -784,8 +788,8 @@ static Network *network_free(Network *network) { set_free(network->dhcp_allow_listed_ip); strv_free(network->dhcp_user_class); set_free(network->dhcp_request_options); - ordered_hashmap_free(network->dhcp_client_send_options); - ordered_hashmap_free(network->dhcp_client_send_vendor_options); + tlv_done(&network->dhcp_extra_options); + tlv_done(&network->dhcp_vendor_options); free(network->dhcp_netlabel); nft_set_context_clear(&network->dhcp_nft_set_context); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 9a36c312f8920..26012612a15c8 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -23,6 +23,7 @@ #include "networkd-sysctl.h" #include "networkd-wwan-bus.h" #include "resolve-util.h" +#include "tlv-util.h" typedef enum KeepConfiguration { KEEP_CONFIGURATION_NO = 0, @@ -168,8 +169,8 @@ typedef struct Network { Set *dhcp_deny_listed_ip; Set *dhcp_allow_listed_ip; Set *dhcp_request_options; - OrderedHashmap *dhcp_client_send_options; - OrderedHashmap *dhcp_client_send_vendor_options; + TLV dhcp_extra_options; + TLV dhcp_vendor_options; char *dhcp_netlabel; NFTSetContext dhcp_nft_set_context; @@ -226,8 +227,8 @@ typedef struct Network { usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec; uint32_t dhcp_server_pool_offset; uint32_t dhcp_server_pool_size; - OrderedHashmap *dhcp_server_send_options; - OrderedHashmap *dhcp_server_send_vendor_options; + TLV dhcp_server_extra_options; + TLV dhcp_server_vendor_options; struct in_addr dhcp_server_boot_server_address; char *dhcp_server_boot_server_name; char *dhcp_server_boot_filename; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 378271aef885d..f7c5602b6655e 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -150,9 +150,6 @@ int sd_dhcp_client_set_bootp( int bootp); int sd_dhcp_client_set_send_release(sd_dhcp_client *client, int enable); -int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v); -int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v); - int sd_dhcp_client_is_running(sd_dhcp_client *client); int sd_dhcp_client_stop(sd_dhcp_client *client); int sd_dhcp_client_start(sd_dhcp_client *client); diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index ef13776201a70..c17cd6b7b68ea 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -76,8 +76,6 @@ int sd_dhcp_server_set_sip(sd_dhcp_server *server, const struct in_addr sip[], s int sd_dhcp_server_set_pop3(sd_dhcp_server *server, const struct in_addr pop3[], size_t n); int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], size_t n); -int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v); -int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_set_static_lease( sd_dhcp_server *server, const struct in_addr *address, From fa881fda15025dfb643279d8f983a2409253617b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 26 Apr 2026 13:22:07 +0900 Subject: [PATCH 1577/2155] sd-dhcp-option: drop unused sd_dhcp_option --- src/libsystemd-network/dhcp-option.c | 40 ---------------------------- src/libsystemd-network/dhcp-option.h | 13 --------- src/systemd/meson.build | 1 - src/systemd/sd-dhcp-client.h | 1 - src/systemd/sd-dhcp-option.h | 34 ----------------------- src/systemd/sd-dhcp-server.h | 1 - 6 files changed, 90 deletions(-) delete mode 100644 src/systemd/sd-dhcp-option.h diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 75470cb3d6eae..da5fd64af67c2 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -434,43 +434,3 @@ int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret) { *ret = TAKE_PTR(hostname); return 0; } - -static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { - if (!i) - return NULL; - - free(i->data); - return mfree(i); -} - -int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) { - assert_return(ret, -EINVAL); - assert_return(length == 0 || data, -EINVAL); - - _cleanup_free_ void *q = memdup(data, length); - if (!q) - return -ENOMEM; - - sd_dhcp_option *p = new(sd_dhcp_option, 1); - if (!p) - return -ENOMEM; - - *p = (sd_dhcp_option) { - .n_ref = 1, - .option = option, - .length = length, - .data = TAKE_PTR(q), - }; - - *ret = TAKE_PTR(p); - return 0; -} - -DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - dhcp_option_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - sd_dhcp_option, - sd_dhcp_option_unref); diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h index 5ca1cafe388ae..f0afd892e68c0 100644 --- a/src/libsystemd-network/dhcp-option.h +++ b/src/libsystemd-network/dhcp-option.h @@ -1,21 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "sd-dhcp-option.h" /* IWYU pragma: export */ - #include "dhcp-protocol.h" #include "sd-forward.h" -#include "hash-funcs.h" - -struct sd_dhcp_option { - unsigned n_ref; - - uint8_t option; - void *data; - size_t length; -}; - -extern const struct hash_ops dhcp_option_hash_ops; typedef struct DHCPServerData { struct in_addr *addr; diff --git a/src/systemd/meson.build b/src/systemd/meson.build index d7335cee558de..ad455d73b217b 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -28,7 +28,6 @@ _not_installed_headers = [ 'sd-dhcp-client.h', 'sd-dhcp-duid.h', 'sd-dhcp-lease.h', - 'sd-dhcp-option.h', 'sd-dhcp-protocol.h', 'sd-dhcp-server-lease.h', 'sd-dhcp-server.h', diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index f7c5602b6655e..c83aaac2c9d38 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -30,7 +30,6 @@ struct in_addr; typedef struct sd_device sd_device; typedef struct sd_dhcp_client_id sd_dhcp_client_id; typedef struct sd_dhcp_lease sd_dhcp_lease; -typedef struct sd_dhcp_option sd_dhcp_option; typedef struct sd_event sd_event; enum { diff --git a/src/systemd/sd-dhcp-option.h b/src/systemd/sd-dhcp-option.h deleted file mode 100644 index edc671b1b5789..0000000000000 --- a/src/systemd/sd-dhcp-option.h +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#ifndef foosddhcpoptionhfoo -#define foosddhcpoptionhfoo - -/*** - 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 "_sd-common.h" -#include "sd-dhcp-protocol.h" /* IWYU pragma: export */ - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_option sd_dhcp_option; - -int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret); -_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index c17cd6b7b68ea..4f2fc688352cf 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -26,7 +26,6 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_event sd_event; -typedef struct sd_dhcp_option sd_dhcp_option; typedef struct sd_dhcp_server sd_dhcp_server; enum { From 30b14a0886a42fe08b4cafbafc8002bd4d89cd2d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 24 Mar 2026 03:53:51 +0900 Subject: [PATCH 1578/2155] dhcp: use struct iovec_wrapper to manage user class --- src/libsystemd-network/dhcp-client-internal.h | 4 +- src/libsystemd-network/dhcp-option.c | 38 ---------- src/libsystemd-network/sd-dhcp-client.c | 59 ++++++++------ src/network/networkd-dhcp-common.c | 76 +++++++++++++------ src/network/networkd-dhcp-common.h | 3 +- src/network/networkd-dhcp4.c | 8 +- src/network/networkd-network-gperf.gperf | 8 +- src/network/networkd-network.c | 2 +- src/network/networkd-network.h | 3 +- src/systemd/sd-dhcp-client.h | 3 - 10 files changed, 105 insertions(+), 99 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index 8e23f7d931439..7db0a91fa94a1 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -5,6 +5,7 @@ #include "dhcp-client-id-internal.h" #include "ether-addr-util.h" +#include "iovec-wrapper.h" #include "network-common.h" #include "sd-forward.h" #include "socket-util.h" @@ -55,7 +56,7 @@ struct sd_dhcp_client { char *hostname; char *vendor_class_identifier; char *mudurl; - char **user_class; + struct iovec_wrapper user_class; uint32_t mtu; usec_t fallback_lease_lifetime; uint32_t xid; @@ -93,6 +94,7 @@ int dhcp_client_get_state(sd_dhcp_client *client); int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options); int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options); +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class); int client_receive_message_raw( sd_event_source *s, diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index da5fd64af67c2..c5e8b29794433 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -12,7 +12,6 @@ #include "hostname-util.h" #include "memory-util.h" #include "string-util.h" -#include "strv.h" #include "utf8.h" /* Append type-length value structure to the options buffer */ @@ -57,43 +56,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 1; 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)) - return -EINVAL; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - if (len > 255 || len == 0) - return -EINVAL; - - total += 1 + len; - } - - if (*offset + 2 + total > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = total; - *offset += 2; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - options[*offset] = len; - memcpy(&options[*offset + 1], *s, len); - *offset += 1 + len; - } - - break; - } case SD_DHCP_OPTION_SIP_SERVER: if (*offset + 3 + optlen > size) return -ENOBUFS; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 0b13e925f2669..8b517982b65cd 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -27,7 +27,6 @@ #include "sort-util.h" #include "string-table.h" #include "string-util.h" -#include "strv.h" #include "time-util.h" #include "web-util.h" @@ -434,28 +433,30 @@ int sd_dhcp_client_set_mud_url( return free_and_strdup(&client->mudurl, mudurl); } -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class) { - - char **s = NULL; +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class) { + int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(!strv_isempty(user_class), -EINVAL); - STRV_FOREACH(p, user_class) { - size_t n = strlen(*p); + if (iovw_isempty(user_class)) { + iovw_done_free(&client->user_class); + return 0; + } - if (n > 255 || n == 0) + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + FOREACH_ARRAY(iovec, user_class->iovec, user_class->count) { + if (iovec->iov_len == 0 || iovec->iov_len > UINT8_MAX) return -EINVAL; - } - s = strv_copy(user_class); - if (!s) - return -ENOMEM; + r = iovw_extend_iov(&iovw, iovec); + if (r < 0) + return r; + } - return strv_free_and_replace(client->user_class, s); + iovw_done_free(&client->user_class); + client->user_class = TAKE_STRUCT(iovw); + return 0; } int sd_dhcp_client_set_client_port( @@ -933,12 +934,26 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, return r; } - if (client->user_class) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_USER_CLASS, - /* optlen= */ 0, client->user_class); - if (r < 0) - return r; + if (!iovw_isempty(&client->user_class)) { + size_t sz = iovw_size(&client->user_class) + client->user_class.count; + if (sz <= UINT8_MAX) { + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(iovec, client->user_class.iovec, client->user_class.count) { + assert(iovec->iov_len > 0 && iovec->iov_len <= UINT8_MAX); + *p++ = iovec->iov_len; + p = mempcpy(p, iovec->iov_base, iovec->iov_len); + } + + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_USER_CLASS, + sz, buf); + if (r < 0) + return r; + } } if (client->extra_options) { @@ -2297,7 +2312,7 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { free(client->hostname); free(client->vendor_class_identifier); free(client->mudurl); - client->user_class = strv_free(client->user_class); + iovw_done_free(&client->user_class); tlv_unref(client->extra_options); tlv_unref(client->vendor_options); free(client->ifname); diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index d7353d07ed41c..5d83835580971 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -636,7 +636,7 @@ int config_parse_iaid( return 0; } -int config_parse_dhcp_user_or_vendor_class( +int config_parse_dhcp4_user_class( const char *unit, const char *filename, unsigned line, @@ -648,46 +648,76 @@ int config_parse_dhcp_user_or_vendor_class( void *data, void *userdata) { - char ***l = ASSERT_PTR(data); + struct iovec_wrapper *iovw = ASSERT_PTR(data); int r; assert(lvalue); assert(rvalue); - assert(IN_SET(ltype, AF_INET, AF_INET6)); if (isempty(rvalue)) { - *l = strv_free(*l); + iovw_done_free(iovw); return 0; } for (const char *p = rvalue;;) { _cleanup_free_ char *w = NULL; - size_t len; r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to split user classes option, ignoring: %s", rvalue); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) return 0; + + size_t len = strlen(w); + if (len > UINT8_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…255, ignoring.", lvalue, w); + continue; } + + r = iovw_consume(iovw, TAKE_PTR(w), len); + if (r < 0) + return log_oom(); + } +} + +int config_parse_dhcp6_user_or_vendor_class( + 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) { + + char ***l = ASSERT_PTR(data); + int r; + + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); if (r == 0) return 0; - len = strlen(w); - if (ltype == AF_INET) { - if (len > UINT8_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…255, ignoring.", w); - continue; - } - } else { - if (len > UINT16_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…65535, ignoring.", w); - continue; - } + size_t len = strlen(w); + if (len > UINT16_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…65535, ignoring.", lvalue, w); + continue; } r = strv_consume(l, TAKE_PTR(w)); diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 0b88d7790c17a..6be8bcd6dc374 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -81,7 +81,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp4_user_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_user_or_vendor_class); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_send_option); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option_tlv); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 7304a049b699d..b5210e9ddfc91 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1643,11 +1643,9 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set MUD URL: %m"); } - if (link->network->dhcp_user_class) { - r = sd_dhcp_client_set_user_class(link->dhcp_client, link->network->dhcp_user_class); - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); - } + r = dhcp_client_set_user_class(link->dhcp_client, &link->network->dhcp_user_class); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); } if (link->network->dhcp_client_port > 0) { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 0f1c4e57c46f4..b925cd3a2b47d 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -278,7 +278,7 @@ DHCPv4.RequestBroadcast, config_parse_tristate, DHCPv4.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) DHCPv4.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp_mudurl) DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 -DHCPv4.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCPv4.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCPv4.IAID, config_parse_iaid, AF_INET, 0 DHCPv4.DUIDType, config_parse_network_duid_type, 0, 0 DHCPv4.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 @@ -318,8 +318,8 @@ DHCPv6.MUDURL, config_parse_mud_url, DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 DHCPv6.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp6_hostname) DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 -DHCPv6.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_user_class) -DHCPv6.VendorClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_vendor_class) +DHCPv6.UserClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_user_class) +DHCPv6.VendorClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_vendor_class) DHCPv6.SendVendorOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_prefix_hint, 0, 0 DHCPv6.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp6_pd_prefix_route_type) @@ -664,7 +664,7 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_tristate, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) -DHCP.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCP.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCP.IAID, config_parse_iaid, AF_INET, 0 DHCP.DUIDType, config_parse_network_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 0397e77f81003..5cd47ca0a81ee 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -786,7 +786,7 @@ static Network *network_free(Network *network) { free(network->dhcp_label); set_free(network->dhcp_deny_listed_ip); set_free(network->dhcp_allow_listed_ip); - strv_free(network->dhcp_user_class); + iovw_done_free(&network->dhcp_user_class); set_free(network->dhcp_request_options); tlv_done(&network->dhcp_extra_options); tlv_done(&network->dhcp_vendor_options); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 26012612a15c8..c4020a4341af1 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -7,6 +7,7 @@ #include "bridge.h" #include "firewall-util.h" #include "ipoib.h" +#include "iovec-wrapper.h" #include "net-condition.h" #include "network-util.h" #include "networkd-bridge-vlan.h" @@ -124,7 +125,7 @@ typedef struct Network { bool dhcp_iaid_set; char *dhcp_vendor_class_identifier; char *dhcp_mudurl; - char **dhcp_user_class; + struct iovec_wrapper dhcp_user_class; char *dhcp_hostname; char *dhcp_label; uint64_t dhcp_max_attempts; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index c83aaac2c9d38..4e7e79da65667 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -129,9 +129,6 @@ int sd_dhcp_client_set_vendor_class_identifier( int sd_dhcp_client_set_mud_url( sd_dhcp_client *client, const char *mudurl); -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class); int sd_dhcp_client_get_lease( sd_dhcp_client *client, sd_dhcp_lease **ret); From 9bd3f4297dd22dc2268fcdeaa6b3c1da0bbf27c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 11 May 2026 16:25:59 +0200 Subject: [PATCH 1579/2155] journalctl: reorder parse_argv() cases to match --help Pure reordering. ARG_SMART_RELINQUISH_VAR is kept immediately before ARG_RELINQUISH_VAR because of the existing _fallthrough_; that's the only deviation from strict --help order. Co-developed-by: Claude Opus 4.7 --- src/journal/journalctl.c | 540 +++++++++++++++++++-------------------- 1 file changed, 270 insertions(+), 270 deletions(-) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index b25827961f22c..2aeeeb55314f0 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -525,124 +525,6 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case 'e': - arg_pager_flags |= PAGER_JUMP_TO_END; - break; - - case 'f': - arg_follow = true; - break; - - case 'o': - 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); - - if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) - arg_quiet = true; - - if (OUTPUT_MODE_IS_JSON(arg_output)) - arg_json_format_flags = output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO; - else - arg_json_format_flags = SD_JSON_FORMAT_OFF; - - break; - - case 'l': - arg_full = true; - break; - - case ARG_NO_FULL: - arg_full = false; - break; - - case 'a': - arg_all = true; - break; - - case 'n': - r = parse_lines(optarg ?: argv[optind], !optarg); - if (r < 0) - return r; - if (r > 0 && !optarg) - optind++; - - break; - - case ARG_NO_TAIL: - arg_no_tail = true; - break; - - case ARG_TRUNCATE_NEWLINE: - arg_truncate_newline = true; - break; - - case ARG_NEW_ID128: - arg_action = ACTION_NEW_ID128; - break; - - case 'q': - arg_quiet = true; - break; - - case 'm': - arg_merge = true; - break; - - case ARG_THIS_BOOT: - arg_boot = true; - arg_boot_id = SD_ID128_NULL; - arg_boot_offset = 0; - break; - - case 'b': - arg_boot = true; - arg_boot_id = SD_ID128_NULL; - arg_boot_offset = 0; - - if (optarg) { - r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); - if (r < 0) - return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); - - arg_boot = r; - - } else if (optind < argc) { - /* Hmm, no argument? Maybe the next word on the command line is supposed to be the - * argument? Let's see if there is one and is parsable as a boot descriptor... */ - r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); - if (r >= 0) { - arg_boot = r; - optind++; - } - } - break; - - case ARG_LIST_BOOTS: - arg_action = ACTION_LIST_BOOTS; - break; - - case ARG_LIST_INVOCATIONS: - arg_action = ACTION_LIST_INVOCATIONS; - break; - - case 'k': - arg_dmesg = true; - break; - case ARG_SYSTEM: arg_journal_type |= SD_JOURNAL_SYSTEM; break; @@ -657,28 +539,8 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { - arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; - arg_namespace = mfree(arg_namespace); - } else if (startswith(optarg, "+")) { - arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); - if (r < 0) - return r; - } else if (isempty(optarg)) { - arg_namespace_flags = 0; - arg_namespace = mfree(arg_namespace); - } else { - arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); - if (r < 0) - return r; - } - break; - - case ARG_LIST_NAMESPACES: - arg_action = ACTION_LIST_NAMESPACES; + case 'm': + arg_merge = true; break; case 'D': @@ -719,14 +581,44 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case 'c': - r = free_and_strdup_warn(&arg_cursor, optarg); + case ARG_NAMESPACE: + if (streq(optarg, "*")) { + arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; + arg_namespace = mfree(arg_namespace); + } else if (startswith(optarg, "+")) { + arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; + r = free_and_strdup_warn(&arg_namespace, optarg + 1); + if (r < 0) + return r; + } else if (isempty(optarg)) { + arg_namespace_flags = 0; + arg_namespace = mfree(arg_namespace); + } else { + arg_namespace_flags = 0; + r = free_and_strdup_warn(&arg_namespace, optarg); + if (r < 0) + return r; + } + break; + + case 'S': + r = parse_timestamp(optarg, &arg_since); if (r < 0) - return r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", optarg); + arg_since_set = true; break; - case ARG_CURSOR_FILE: - r = free_and_strdup_warn(&arg_cursor_file, optarg); + case 'U': + r = parse_timestamp(optarg, &arg_until); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", optarg); + arg_until_set = true; + break; + + case 'c': + r = free_and_strdup_warn(&arg_cursor, optarg); if (r < 0) return r; break; @@ -737,83 +629,78 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_SHOW_CURSOR: - arg_show_cursor = true; + case ARG_CURSOR_FILE: + r = free_and_strdup_warn(&arg_cursor_file, optarg); + if (r < 0) + return r; break; - case ARG_HEADER: - arg_action = ACTION_PRINT_HEADER; + case ARG_THIS_BOOT: + arg_boot = true; + arg_boot_id = SD_ID128_NULL; + arg_boot_offset = 0; break; - case ARG_VERIFY: - arg_action = ACTION_VERIFY; - break; + case 'b': + arg_boot = true; + arg_boot_id = SD_ID128_NULL; + arg_boot_offset = 0; - case ARG_DISK_USAGE: - arg_action = ACTION_DISK_USAGE; - break; + if (optarg) { + r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + if (r < 0) + return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); - case ARG_VACUUM_SIZE: - r = parse_size(optarg, 1024, &arg_vacuum_size); - if (r < 0) - return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); + arg_boot = r; - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + } else if (optind < argc) { + /* Hmm, no argument? Maybe the next word on the command line is supposed to be the + * argument? Let's see if there is one and is parsable as a boot descriptor... */ + r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); + if (r >= 0) { + arg_boot = r; + optind++; + } + } break; - case ARG_VACUUM_FILES: - r = safe_atou64(optarg, &arg_vacuum_n_files); + case 'u': + r = strv_extend(&arg_system_units, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); - - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + return log_oom(); break; - case ARG_VACUUM_TIME: - r = parse_sec(optarg, &arg_vacuum_time); + case ARG_USER_UNIT: + r = strv_extend(&arg_user_units, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); - - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + return log_oom(); break; -#if HAVE_GCRYPT - case ARG_FORCE: - arg_force = true; + case ARG_INVOCATION: + r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); + if (r < 0) + return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); + arg_invocation = r; break; - case ARG_SETUP_KEYS: - arg_action = ACTION_SETUP_KEYS; + case 'I': + /* Equivalent to --invocation=0 */ + arg_invocation = true; + arg_invocation_id = SD_ID128_NULL; + arg_invocation_offset = 0; break; - case ARG_VERIFY_KEY: - erase_and_free(arg_verify_key); - arg_verify_key = strdup(optarg); - if (!arg_verify_key) + case 't': + r = strv_extend(&arg_syslog_identifier, optarg); + if (r < 0) return log_oom(); - - /* Use memset not explicit_bzero() or similar so this doesn't look confusing - * in ps or htop output. */ - memset(optarg, 'x', strlen(optarg)); - - arg_action = ACTION_VERIFY; - arg_merge = false; - break; - - case ARG_INTERVAL: - r = parse_sec(optarg, &arg_interval); - if (r < 0 || arg_interval <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse sealing key change interval: %s", optarg); - break; -#else - case ARG_SETUP_KEYS: - case ARG_VERIFY_KEY: - case ARG_INTERVAL: - case ARG_FORCE: - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Compiled without forward-secure sealing support."); -#endif + break; + + case 'T': + r = strv_extend(&arg_exclude_identifier, optarg); + if (r < 0) + return log_oom(); + break; case 'p': { const char *dots; @@ -907,58 +794,160 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'S': - r = parse_timestamp(optarg, &arg_since); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); - arg_since_set = true; + case 'k': + arg_dmesg = true; break; - case 'U': - r = parse_timestamp(optarg, &arg_until); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); - arg_until_set = true; + case 'o': + 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); + + if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) + arg_quiet = true; + + if (OUTPUT_MODE_IS_JSON(arg_output)) + arg_json_format_flags = output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO; + else + arg_json_format_flags = SD_JSON_FORMAT_OFF; + break; - case 't': - r = strv_extend(&arg_syslog_identifier, optarg); - if (r < 0) + case ARG_OUTPUT_FIELDS: { + _cleanup_strv_free_ char **v = NULL; + + v = strv_split(optarg, ","); + if (!v) return log_oom(); - break; - case 'T': - r = strv_extend(&arg_exclude_identifier, optarg); + r = set_put_strdupv(&arg_output_fields, v); if (r < 0) return log_oom(); + break; + } - case 'u': - r = strv_extend(&arg_system_units, optarg); + case 'n': + r = parse_lines(optarg ?: argv[optind], !optarg); if (r < 0) - return log_oom(); + return r; + if (r > 0 && !optarg) + optind++; + break; - case ARG_USER_UNIT: - r = strv_extend(&arg_user_units, optarg); + case 'r': + arg_reverse = true; + break; + + case ARG_SHOW_CURSOR: + arg_show_cursor = true; + break; + + case ARG_UTC: + arg_utc = true; + break; + + case 'x': + arg_catalog = true; + break; + + case 'W': + arg_no_hostname = true; + break; + + case 'l': + arg_full = true; + break; + + case ARG_NO_FULL: + arg_full = false; + break; + + case 'a': + arg_all = true; + break; + + case 'f': + arg_follow = true; + break; + + case ARG_NO_TAIL: + arg_no_tail = true; + break; + + case ARG_TRUNCATE_NEWLINE: + arg_truncate_newline = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_SYNCHRONIZE_ON_EXIT: + r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit); if (r < 0) + return r; + + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case 'e': + arg_pager_flags |= PAGER_JUMP_TO_END; + break; + +#if HAVE_GCRYPT + case ARG_INTERVAL: + r = parse_sec(optarg, &arg_interval); + if (r < 0 || arg_interval <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse sealing key change interval: %s", optarg); + break; + + case ARG_VERIFY_KEY: + erase_and_free(arg_verify_key); + arg_verify_key = strdup(optarg); + if (!arg_verify_key) return log_oom(); + + /* Use memset not explicit_bzero() or similar so this doesn't look confusing + * in ps or htop output. */ + memset(optarg, 'x', strlen(optarg)); + + arg_action = ACTION_VERIFY; + arg_merge = false; break; - case ARG_INVOCATION: - r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); - if (r < 0) - return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); - arg_invocation = r; + case ARG_FORCE: + arg_force = true; break; - case 'I': - /* Equivalent to --invocation=0 */ - arg_invocation = true; - arg_invocation_id = SD_ID128_NULL; - arg_invocation_offset = 0; + case ARG_SETUP_KEYS: + arg_action = ACTION_SETUP_KEYS; + break; +#else + case ARG_INTERVAL: + case ARG_VERIFY_KEY: + case ARG_FORCE: + case ARG_SETUP_KEYS: + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'N': + arg_action = ACTION_LIST_FIELD_NAMES; break; case 'F': @@ -968,40 +957,52 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case 'N': - arg_action = ACTION_LIST_FIELD_NAMES; + case ARG_LIST_BOOTS: + arg_action = ACTION_LIST_BOOTS; break; - case 'W': - arg_no_hostname = true; + case ARG_LIST_INVOCATIONS: + arg_action = ACTION_LIST_INVOCATIONS; break; - case 'x': - arg_catalog = true; + case ARG_LIST_NAMESPACES: + arg_action = ACTION_LIST_NAMESPACES; break; - case ARG_LIST_CATALOG: - arg_action = ACTION_LIST_CATALOG; + case ARG_DISK_USAGE: + arg_action = ACTION_DISK_USAGE; break; - case ARG_DUMP_CATALOG: - arg_action = ACTION_DUMP_CATALOG; + case ARG_VACUUM_SIZE: + r = parse_size(optarg, 1024, &arg_vacuum_size); + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); + + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_UPDATE_CATALOG: - arg_action = ACTION_UPDATE_CATALOG; + case ARG_VACUUM_FILES: + r = safe_atou64(optarg, &arg_vacuum_n_files); + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); + + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case 'r': - arg_reverse = true; + case ARG_VACUUM_TIME: + r = parse_sec(optarg, &arg_vacuum_time); + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); + + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_UTC: - arg_utc = true; + case ARG_VERIFY: + arg_action = ACTION_VERIFY; break; - case ARG_FLUSH: - arg_action = ACTION_FLUSH; + case ARG_SYNC: + arg_action = ACTION_SYNC; break; case ARG_SMART_RELINQUISH_VAR: { @@ -1033,33 +1034,32 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_RELINQUISH_VAR; break; + case ARG_FLUSH: + arg_action = ACTION_FLUSH; + break; + case ARG_ROTATE: arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; break; - case ARG_SYNC: - arg_action = ACTION_SYNC; + case ARG_HEADER: + arg_action = ACTION_PRINT_HEADER; break; - case ARG_OUTPUT_FIELDS: { - _cleanup_strv_free_ char **v = NULL; - - v = strv_split(optarg, ","); - if (!v) - return log_oom(); - - r = set_put_strdupv(&arg_output_fields, v); - if (r < 0) - return log_oom(); + case ARG_LIST_CATALOG: + arg_action = ACTION_LIST_CATALOG; + break; + case ARG_DUMP_CATALOG: + arg_action = ACTION_DUMP_CATALOG; break; - } - case ARG_SYNCHRONIZE_ON_EXIT: - r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit); - if (r < 0) - return r; + case ARG_UPDATE_CATALOG: + arg_action = ACTION_UPDATE_CATALOG; + break; + case ARG_NEW_ID128: + arg_action = ACTION_NEW_ID128; break; case '?': From 1ea3359793ffd1079b6f431f859ba1888dc218dd Mon Sep 17 00:00:00 2001 From: hschloss Date: Wed, 6 May 2026 17:07:40 +0200 Subject: [PATCH 1580/2155] pe-binary: fix "systemd-sbsign calculates wrong PE checksum" --- src/shared/pe-binary.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index b4b180d701337..2f7764c1393a8 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -487,7 +487,8 @@ int pe_checksum(int fd, uint32_t *ret) { return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file"); for (size_t i = 0; i < (size_t) n / 2; i++) { - if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum)) + size_t pos = off + i * sizeof(uint16_t); + if (pos >= checksum_offset && pos < checksum_offset + sizeof(pe_header->optional.CheckSum)) continue; uint16_t val = le16toh(buf[i]); From 452214c09ba3dea05dd214a1cea315333903266f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 19:34:01 +0200 Subject: [PATCH 1581/2155] chase: Use ELOOP for CHASE_PROHIBIT_SYMLINKS error Matches the behavior of openat2() with RESOLVE_NO_SYMLINKS which makes introducing support for openat2() easier. --- src/basic/chase.c | 4 ++-- src/core/mount.c | 2 +- src/test/test-chase.c | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 50ab4e3c47495..b8fcbcb4e5fa8 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -139,11 +139,11 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) { assert(fd >= 0); if (!FLAGS_SET(flags, CHASE_WARN)) - return -EREMCHG; + return -ELOOP; (void) fd_get_path(fd, &n1); - return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG), + return log_warning_errno(SYNTHETIC_ERRNO(ELOOP), "Detected symlink where no symlink is allowed at '%s', refusing.", strna(n1)); } diff --git a/src/core/mount.c b/src/core/mount.c index 967274b950b67..c57f6e8a667cf 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -1171,7 +1171,7 @@ static void mount_enter_mounting(Mount *m) { * couldn't support that reasonably: the mounts in /proc/self/mountinfo would not be recognizable to * us anymore. */ fd = chase_and_open_parent(m->where, /* root= */ NULL, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_TRIGGER_AUTOFS, &fn); - if (fd == -EREMCHG) { + if (fd == -ELOOP) { r = unit_log_noncanonical_mount_path(UNIT(m), m->where); goto fail; } diff --git a/src/test/test-chase.c b/src/test/test-chase.c index ed07ac5cef68d..23ceef53285e7 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -403,12 +403,12 @@ TEST(chase) { /* Test CHASE_PROHIBIT_SYMLINKS */ - 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); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); cleanup: ASSERT_OK(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL)); From 8b81e3b751b60bb63fec28f65306e62ffa48cee7 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 19:42:45 +0200 Subject: [PATCH 1582/2155] chase: Use openat2() if available Let's make use of openat2() if we can in chaseat(). --- meson.build | 1 + src/basic/chase.c | 101 +++++++++++++++++++++++++++++++++++ src/include/override/fcntl.h | 9 ++++ src/libc/fcntl.c | 11 ++++ src/libc/meson.build | 1 + 5 files changed, 123 insertions(+) create mode 100644 src/libc/fcntl.c diff --git a/meson.build b/meson.build index d6fbd7c2b7ea6..d5405995db22c 100644 --- a/meson.build +++ b/meson.build @@ -593,6 +593,7 @@ foreach ident : [ ['mount_setattr', '''#include '''], # since glibc-2.36 ['move_mount', '''#include '''], # since glibc-2.36 ['open_tree', '''#include '''], # since glibc-2.36 + ['openat2', '''#include '''], # since glibc-2.42 ['pidfd_open', '''#include '''], # since glibc-2.36 ['pidfd_send_signal', '''#include '''], # since glibc-2.36 ['pidfd_spawn', '''#include '''], # since glibc-2.39 diff --git a/src/basic/chase.c b/src/basic/chase.c index b8fcbcb4e5fa8..66c78fc8ddb56 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -50,6 +50,93 @@ static int chase_statx(int fd, struct statx *ret) { ret); } +static int chase_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) { + /* Open the target of a chase operation via openat2(), translating the relevant ChaseFlags into + * RESOLVE_* and O_* flags and verifying MUST_BE_REGULAR/SOCKET via fstat after the open. Returns + * -EOPNOTSUPP when openat2() is unavailable (older kernels) or blocked by a seccomp filter + * (notably systemd's own filter, which returns ENOSYS to force programs onto the openat() + * fallback path) — the verdict is cached so subsequent calls in the same process skip the syscall + * entirely. */ + + static bool can_openat2 = true; + int r; + + assert(path); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + + if (!can_openat2) + return -EOPNOTSUPP; + + /* openat2() can handle everything the regular shortcut handles, plus a real root boundary (via + * RESOLVE_IN_ROOT) and CHASE_PROHIBIT_SYMLINKS (via RESOLVE_NO_SYMLINKS). It cannot model the other + * CHASE_NO_SHORTCUT flags, cannot trigger automounts on O_PATH fds, and RESOLVE_IN_ROOT requires + * the dirfd to be the root. Bail out so the caller falls back to the regular chase loop. */ + if ((chase_flags & (CHASE_NO_SHORTCUT_MASK & ~CHASE_PROHIBIT_SYMLINKS)) != 0) + return -EOPNOTSUPP; + if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS)) + return -EOPNOTSUPP; + if (root_fd != XAT_FDROOT && root_fd != dir_fd) + return -EOPNOTSUPP; + + _cleanup_close_ int dir_fd_local = -EBADF; + if (dir_fd == XAT_FDROOT) { + if (path_is_absolute(path)) + dir_fd = AT_FDCWD; + else { + dir_fd_local = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); + if (dir_fd_local < 0) + return -errno; + dir_fd = dir_fd_local; + } + } + + struct open_how how = { + .flags = O_PATH|O_CLOEXEC, + }; + if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW)) + how.flags |= O_NOFOLLOW; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) + how.flags |= O_DIRECTORY; + if (root_fd != XAT_FDROOT) + how.resolve |= RESOLVE_IN_ROOT; + if (FLAGS_SET(chase_flags, CHASE_PROHIBIT_SYMLINKS)) + how.resolve |= RESOLVE_NO_SYMLINKS; + + _cleanup_close_ int fd = openat2(dir_fd, path, &how, sizeof(how)); + if (fd < 0) { + /* ENOSYS: kernel too old or seccomp filter (systemd's filter returns ENOSYS). + * EPERM: Some seccomp profiles of container runtimes use EPERM rather than ENOSYS. + * But EPERM might also be returned because we can't access some component of the path. So + * we can't cache the result and skip using openat2() if it is blocked with EPERM. Instead + * we fall back to userspace chase() if we get EPERM. + * EAGAIN: with RESOLVE_IN_ROOT the kernel returns this when a ".." component + * (typically from following a symlink like /etc/os-release → ../usr/lib/os-release) + * is processed and the global mount_lock or rename_lock seqcount changed during + * the walk. Any mount activity anywhere in the system bumps mount_lock, so this + * fires reliably while we're still setting up a mount tree. Fall back to the + * regular chase loop, which handles root boundaries without openat2(). Don't + * cache this — the condition is per-call, not a kernel/sandbox capability. */ + if (errno == ENOSYS) + can_openat2 = false; + if (IN_SET(errno, ENOSYS, EPERM, EAGAIN)) + return -EOPNOTSUPP; + return -errno; + } + + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) { + r = fd_verify_regular(fd); + if (r < 0) + return r; + } + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) { + r = fd_verify_socket(fd); + if (r < 0) + return r; + } + + return TAKE_FD(fd); +} + 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 @@ -262,6 +349,20 @@ int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char ** 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 (!ret_path) { + r = chase_openat2(root_fd, dir_fd, path, flags); + if (r >= 0) { + if (ret_fd) + *ret_fd = r; + else + safe_close(r); + + return 1; + } + if (r != -EOPNOTSUPP) + return r; + } + 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_* diff --git a/src/include/override/fcntl.h b/src/include/override/fcntl.h index b41f364534174..a6f2afe53b810 100644 --- a/src/include/override/fcntl.h +++ b/src/include/override/fcntl.h @@ -2,6 +2,8 @@ #pragma once #include_next /* IWYU pragma: export */ +#include /* IWYU pragma: export */ +#include /* This is defined since glibc-2.41. */ #ifndef F_DUPFD_QUERY @@ -22,3 +24,10 @@ #ifndef AT_HANDLE_MNT_ID_UNIQUE #define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */ #endif + +/* Defined since glibc-2.42. + * Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */ +#if !HAVE_OPENAT2 +int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize); +# define openat2 missing_openat2 +#endif diff --git a/src/libc/fcntl.c b/src/libc/fcntl.c new file mode 100644 index 0000000000000..ca5e6c7977c04 --- /dev/null +++ b/src/libc/fcntl.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#if !HAVE_OPENAT2 +int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize) { + return syscall(__NR_openat2, dfd, filename, how, usize); +} +#endif diff --git a/src/libc/meson.build b/src/libc/meson.build index 3b7b96d07f219..e100a6f4ea06d 100644 --- a/src/libc/meson.build +++ b/src/libc/meson.build @@ -2,6 +2,7 @@ libc_wrapper_sources = files( 'bpf.c', + 'fcntl.c', 'ioprio.c', 'kcmp.c', 'kexec.c', From e7d07c20351cbce5ed179b0bf3ca29765d5d005d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 13:58:47 +0200 Subject: [PATCH 1583/2155] shared/options: add OPTION_COMMON_{SYSTEM,USER} We have different help strings for --user/--system in different places, so this only covers a subset of --system/--user instances. But this particular help seems to be the most widely used. (In a few cases, the help string is fixed: it should be "system mode", not "per-system mode".) --- src/import/export.c | 4 ++-- src/import/import-fs.c | 4 ++-- src/import/import.c | 4 ++-- src/import/pull.c | 4 ++-- src/shared/options.h | 6 ++++++ src/storage/storage-fs.c | 4 ++-- src/storage/storagectl.c | 4 ++-- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/import/export.c b/src/import/export.c index a77333643c6bc..8b64b9cad901e 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -263,11 +263,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; - OPTION_LONG("system", NULL, "Operate in per-system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in per-user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 513a2c62d3960..cc6e14c995a8a 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -370,11 +370,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; - OPTION_LONG("system", NULL, "Operate in per-system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in per-user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } diff --git a/src/import/import.c b/src/import/import.c index 798b6b743a21c..9ddc4469c1fda 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -410,11 +410,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return log_error_errno(arg_class, "Failed to parse --class= argument: %s", opts.arg); break; - OPTION_LONG("system", NULL, "Operate in per-system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in per-user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } diff --git a/src/import/pull.c b/src/import/pull.c index 6a1f913ff8a5c..ac9492f177565 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -537,11 +537,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { auto_keep_download = false; break; - OPTION_LONG("system", NULL, "Operate in per-system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in per-user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } diff --git a/src/shared/options.h b/src/shared/options.h index 1f28dab8ad51f..f88275821a6e8 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -124,6 +124,12 @@ typedef struct Option { #define OPTION_COMMON_MACHINE \ OPTION('M', "machine", "CONTAINER", "Operate on local container") +#define OPTION_COMMON_SYSTEM \ + OPTION_LONG("system", NULL, "Operate in system mode") + +#define OPTION_COMMON_USER \ + OPTION_LONG("user", NULL, "Operate in per-user mode") + #define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") diff --git a/src/storage/storage-fs.c b/src/storage/storage-fs.c index 167b10dd83542..5b2bc415fb650 100644 --- a/src/storage/storage-fs.c +++ b/src/storage/storage-fs.c @@ -777,11 +777,11 @@ static int parse_argv(int argc, char *argv[]) { OPTION_COMMON_VERSION: return version(); - OPTION_LONG("system", NULL, "Operate in system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index bbe09e01b1d70..d3ae3f9145327 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -433,11 +433,11 @@ static int parse_argv(int argc, char *argv[], char ***args) { arg_ask_password = false; break; - OPTION_LONG("system", NULL, "Operate in system mode"): + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - OPTION_LONG("user", NULL, "Operate in user mode"): + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; } From 1f4052a2f227a1e057ff68643098bcc09e34b760 Mon Sep 17 00:00:00 2001 From: drhydroxide Date: Tue, 12 May 2026 20:27:02 +0700 Subject: [PATCH 1584/2155] 60-sensor.hwdb iio/accel fix for Advan Evo-X 13 (#42037) Added fix for Advan Evo-X 13 2-in-1 laptop Relevant output of `udevadm info --export-db` ``` P: /devices/pci0000:00/0000:00:15.2/i2c_designware.3/i2c-2/i2c-NSA2513:00/iio:device0 M: iio:device0 R: 0 J: +iio:iio:device0 U: iio T: iio_device E: DEVPATH=/devices/pci0000:00/0000:00:15.2/i2c_designware.3/i2c-2/i2c-NSA2513:00/iio:device0 E: DEVTYPE=iio_device E: SUBSYSTEM=iio E: USEC_INITIALIZED=5462149 E: ACCEL_MOUNT_MATRIX=-1,0,0;0,-1,0;0,0,1 E: IIO_SENSOR_PROXY_TYPE=iio-poll-accel E: SYSTEMD_WANTS=iio-sensor-proxy.service E: TAGS=:systemd: E: CURRENT_TAGS=:systemd: ``` --- hwdb.d/60-sensor.hwdb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index a3f243a660861..93e4c3b494181 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -109,6 +109,13 @@ sensor:modalias:acpi:KIOX0009:*:dmi:*:svnAcer:*:rnOneS1003:* # One 10 (S1003) sensor:modalias:acpi:BMA250E:*:dmi:*:svnAcer:*:rnAigner:* # Iconia Tab 8W (W1-810) ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1 +######################################### +# Advan +######################################### + +sensor:modalias:acpi:NSA2513:*:dmi*:svnADVAN*:pn1301:* # Evo-X 13 (Intel N150) + ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, 1 + ######################################### # Aquarius ######################################### From c3f4c1b8d6b9bad0d88255ce40956a8f513c098a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 13:28:35 +0200 Subject: [PATCH 1585/2155] journalctl: convert to OPTION macros Two namespaces are used: "journalctl" and "journalctl-varlink". Help for --user/--system in the latter is added, even though it is not used yet. I think it'll be good to have this for introspection. The four FSS-related options (--interval, --verify-key, --force, --setup-keys) unfortunately each gain an inline #if HAVE_GCRYPT / #else; the EOPNOTSUPP fallback is duplicated four times. The metavar for --identifier/--exclude-identifier is changed to "ID" to make the layout nicer. (And because that seems to make more sense.) Co-developed-by: Claude Opus 4.7 --- src/journal/journalctl.c | 642 +++++++++++++++------------------------ 1 file changed, 246 insertions(+), 396 deletions(-) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 2aeeeb55314f0..2b4995714f53a 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-journal.h" @@ -9,7 +8,9 @@ #include "build.h" #include "dissect-image.h" #include "extract-word.h" +#include "format-table.h" #include "glob-util.h" +#include "help-util.h" #include "id128-print.h" #include "image-policy.h" #include "journalctl.h" @@ -24,12 +25,12 @@ #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "pcre2-util.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "set.h" #include "static-destruct.h" @@ -229,106 +230,42 @@ static int help_facilities(void) { } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char *const groups[] = { + "Source Options", + "Filtering Options", + "Output Control Options", + "Pager Control Options", + "Forward Secure Sealing (FSS) Options", + "Commands", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("journalctl", "1", &link); - if (r < 0) - return log_oom(); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_full("journalctl", groups[i], &tables[i]); + if (r < 0) + return r; + } + + assert_se(ELEMENTSOF(tables) == 6); + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], + tables[3], tables[4], tables[5]); - printf("%1$s [OPTIONS...] [MATCHES...]\n\n" - "%5$sQuery the journal.%6$s\n\n" - "%3$sSource Options:%4$s\n" - " --system Show the system journal\n" - " --user Show the user journal for the current user\n" - " -M --machine=CONTAINER Operate on local container\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " -i --file=PATH Show journal file\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" - " --namespace=NAMESPACE Show journal data from specified journal namespace\n" - "\n%3$sFiltering Options:%4$s\n" - " -S --since=DATE Show entries not older than the specified date\n" - " -U --until=DATE Show entries not newer than the specified date\n" - " -c --cursor=CURSOR Show entries starting at the specified cursor\n" - " --after-cursor=CURSOR Show entries after the specified cursor\n" - " --cursor-file=FILE Show entries after cursor in FILE and update FILE\n" - " -b --boot[=ID] Show current boot or the specified boot\n" - " -u --unit=UNIT Show logs from the specified unit\n" - " --user-unit=UNIT Show logs from the specified user unit\n" - " --invocation=ID Show logs from the matching invocation ID\n" - " -I Show logs from the latest invocation of unit\n" - " -t --identifier=STRING Show entries with the specified syslog identifier\n" - " -T --exclude-identifier=STRING\n" - " Hide entries with the specified syslog identifier\n" - " -p --priority=RANGE Show entries within the specified priority range\n" - " --facility=FACILITY... Show entries with the specified facilities\n" - " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n" - " --case-sensitive[=BOOL] Force case sensitive or insensitive matching\n" - " -k --dmesg Show kernel message log from the current boot\n" - "\n%3$sOutput Control Options:%4$s\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, verbose, export,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " with-unit)\n" - " --output-fields=LIST Select fields to print in verbose/export/json modes\n" - " -n --lines[=[+]INTEGER] Number of journal entries to show\n" - " -r --reverse Show the newest entries first\n" - " --show-cursor Print the cursor after all the entries\n" - " --utc Express time in Coordinated Universal Time (UTC)\n" - " -x --catalog Add message explanations where available\n" - " -W --no-hostname Suppress output of hostname field\n" - " --no-full Ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -f --follow Follow the journal\n" - " --no-tail Show all lines, even in follow mode\n" - " --truncate-newline Truncate entries by first newline character\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --synchronize-on-exit=BOOL\n" - " Wait for Journal synchronization before exiting\n" - "\n%3$sPager Control Options:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " -e --pager-end Immediately jump to the end in the pager\n" - "\n%3$sForward Secure Sealing (FSS) Options:%4$s\n" - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" - " --force Override of the FSS key pair with --setup-keys\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help text\n" - " --version Show package version\n" - " -N --fields List all field names currently used\n" - " -F --field=FIELD List all values that a specified field takes\n" - " --list-boots Show terse information about recorded boots\n" - " --list-invocations Show invocation IDs of specified unit\n" - " --list-namespaces Show list of journal namespaces\n" - " --disk-usage Show total disk usage of all journal files\n" - " --vacuum-size=BYTES Reduce disk usage below specified size\n" - " --vacuum-files=INT Leave only the specified number of journal files\n" - " --vacuum-time=TIME Remove journal files older than specified time\n" - " --verify Verify journal file consistency\n" - " --sync Synchronize unwritten journal messages to disk\n" - " --relinquish-var Stop logging to disk, log to temporary file system\n" - " --smart-relinquish-var Similar, but NOP if log directory is on root mount\n" - " --flush Flush all journal data from /run into /var\n" - " --rotate Request immediate rotation of the journal files\n" - " --header Show journal header information\n" - " --list-catalog Show all message IDs in the catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" - " --setup-keys Generate a new FSS key pair\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] [MATCHES…]"); + help_abstract("Query the journal."); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("journalctl", "1"); return 0; } @@ -355,134 +292,12 @@ static int vl_server(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_FULL, - ARG_NO_TAIL, - ARG_NEW_ID128, - ARG_THIS_BOOT, - ARG_LIST_BOOTS, - ARG_LIST_INVOCATIONS, - ARG_USER, - ARG_SYSTEM, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_HEADER, - ARG_FACILITY, - ARG_SETUP_KEYS, - ARG_INTERVAL, - ARG_VERIFY, - ARG_VERIFY_KEY, - ARG_DISK_USAGE, - ARG_AFTER_CURSOR, - ARG_CURSOR_FILE, - ARG_SHOW_CURSOR, - ARG_USER_UNIT, - ARG_INVOCATION, - ARG_LIST_CATALOG, - ARG_DUMP_CATALOG, - ARG_UPDATE_CATALOG, - ARG_FORCE, - ARG_CASE_SENSITIVE, - ARG_UTC, - ARG_SYNC, - ARG_FLUSH, - ARG_RELINQUISH_VAR, - ARG_SMART_RELINQUISH_VAR, - ARG_ROTATE, - ARG_TRUNCATE_NEWLINE, - ARG_VACUUM_SIZE, - ARG_VACUUM_FILES, - ARG_VACUUM_TIME, - ARG_OUTPUT_FIELDS, - ARG_NAMESPACE, - ARG_LIST_NAMESPACES, - ARG_SYNCHRONIZE_ON_EXIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "pager-end", no_argument, NULL, 'e' }, - { "follow", no_argument, NULL, 'f' }, - { "force", no_argument, NULL, ARG_FORCE }, - { "output", required_argument, NULL, 'o' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-full", no_argument, NULL, ARG_NO_FULL }, - { "lines", optional_argument, NULL, 'n' }, - { "truncate-newline", no_argument, NULL, ARG_TRUNCATE_NEWLINE }, - { "no-tail", no_argument, NULL, ARG_NO_TAIL }, - { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, /* deprecated */ - { "quiet", no_argument, NULL, 'q' }, - { "merge", no_argument, NULL, 'm' }, - { "this-boot", no_argument, NULL, ARG_THIS_BOOT }, /* deprecated */ - { "boot", optional_argument, NULL, 'b' }, - { "list-boots", no_argument, NULL, ARG_LIST_BOOTS }, - { "list-invocations", no_argument, NULL, ARG_LIST_INVOCATIONS }, - { "dmesg", no_argument, NULL, 'k' }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, 'i' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "header", no_argument, NULL, ARG_HEADER }, - { "identifier", required_argument, NULL, 't' }, - { "exclude-identifier", required_argument, NULL, 'T' }, - { "priority", required_argument, NULL, 'p' }, - { "facility", required_argument, NULL, ARG_FACILITY }, - { "grep", required_argument, NULL, 'g' }, - { "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE }, - { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, - { "interval", required_argument, NULL, ARG_INTERVAL }, - { "verify", no_argument, NULL, ARG_VERIFY }, - { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, - { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, - { "cursor", required_argument, NULL, 'c' }, - { "cursor-file", required_argument, NULL, ARG_CURSOR_FILE }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "unit", required_argument, NULL, 'u' }, - { "user-unit", required_argument, NULL, ARG_USER_UNIT }, - { "invocation", required_argument, NULL, ARG_INVOCATION }, - { "field", required_argument, NULL, 'F' }, - { "fields", no_argument, NULL, 'N' }, - { "catalog", no_argument, NULL, 'x' }, - { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, - { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, - { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, - { "reverse", no_argument, NULL, 'r' }, - { "machine", required_argument, NULL, 'M' }, - { "utc", no_argument, NULL, ARG_UTC }, - { "flush", no_argument, NULL, ARG_FLUSH }, - { "relinquish-var", no_argument, NULL, ARG_RELINQUISH_VAR }, - { "smart-relinquish-var", no_argument, NULL, ARG_SMART_RELINQUISH_VAR }, - { "sync", no_argument, NULL, ARG_SYNC }, - { "rotate", no_argument, NULL, ARG_ROTATE }, - { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE }, - { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, - { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, - { "no-hostname", no_argument, NULL, 'W' }, - { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES }, - { "synchronize-on-exit", required_argument, NULL, ARG_SYNCHRONIZE_ON_EXIT }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) @@ -490,228 +305,237 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) { arg_varlink = true; - static const struct option varlink_options[] = { - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - while ((c = getopt_long(argc, argv, "", varlink_options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl-varlink" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_SYSTEM: + OPTION_NAMESPACE("journalctl-varlink"): {} + + OPTION_COMMON_SYSTEM: arg_varlink_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: arg_varlink_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_varlink_runtime_scope < 0) return log_error_errno(arg_varlink_runtime_scope, "Cannot run in Varlink mode with no runtime scope specified."); + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected in Varlink mode."); + + *remaining_args = NULL; return 1; } - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:W", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_SYSTEM: + OPTION_NAMESPACE("journalctl"): {} + + OPTION_GROUP("Source Options"): {} + + OPTION_LONG("system", NULL, "Show the system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Show the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'M': - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION_COMMON_MACHINE: + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; break; - case 'm': + OPTION('m', "merge", NULL, "Show entries from all available journals"): arg_merge = true; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Show journal files from directory"): + r = free_and_strdup_warn(&arg_directory, opts.arg); if (r < 0) return r; break; - case 'i': - if (streq(optarg, "-")) + OPTION('i', "file", "PATH", "Show journal file"): + if (streq(opts.arg, "-")) /* An undocumented feature: we can read journal files from STDIN. We don't document * this though, since after all we only support this for mmap-able, seekable files, and * not for example pipes which are probably the primary use case for reading things from * STDIN. To avoid confusion we hence don't document this feature. */ arg_file_stdin = true; else { - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); } 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(opts.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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { + OPTION_LONG("namespace", "NAMESPACE", + "Show journal data from specified journal namespace"): + if (streq(opts.arg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = mfree(arg_namespace); - } else if (startswith(optarg, "+")) { + } else if (startswith(opts.arg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); + r = free_and_strdup_warn(&arg_namespace, opts.arg + 1); if (r < 0) return r; - } else if (isempty(optarg)) { + } else if (isempty(opts.arg)) { arg_namespace_flags = 0; arg_namespace = mfree(arg_namespace); } else { arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); + r = free_and_strdup_warn(&arg_namespace, opts.arg); if (r < 0) return r; } break; - case 'S': - r = parse_timestamp(optarg, &arg_since); + OPTION_GROUP("Filtering Options"): {} + + OPTION('S', "since", "DATE", "Show entries not older than the specified date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); + "Failed to parse timestamp: %s", opts.arg); arg_since_set = true; break; - case 'U': - r = parse_timestamp(optarg, &arg_until); + OPTION('U', "until", "DATE", "Show entries not newer than the specified date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); + "Failed to parse timestamp: %s", opts.arg); arg_until_set = true; break; - case 'c': - r = free_and_strdup_warn(&arg_cursor, optarg); + OPTION('c', "cursor", "CURSOR", "Show entries starting at the specified cursor"): + r = free_and_strdup_warn(&arg_cursor, opts.arg); if (r < 0) return r; break; - case ARG_AFTER_CURSOR: - r = free_and_strdup_warn(&arg_after_cursor, optarg); + OPTION_LONG("after-cursor", "CURSOR", "Show entries after the specified cursor"): + r = free_and_strdup_warn(&arg_after_cursor, opts.arg); if (r < 0) return r; break; - case ARG_CURSOR_FILE: - r = free_and_strdup_warn(&arg_cursor_file, optarg); + OPTION_LONG("cursor-file", "FILE", "Show entries after cursor in FILE and update FILE"): + r = free_and_strdup_warn(&arg_cursor_file, opts.arg); if (r < 0) return r; break; - case ARG_THIS_BOOT: + OPTION_LONG("this-boot", NULL, /* help= */ NULL): arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; break; - case 'b': + OPTION_FULL(OPTION_OPTIONAL_ARG, 'b', "boot", "ID", + "Show current boot or the specified boot"): arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; - if (optarg) { - r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + if (opts.arg) { + r = parse_id_descriptor(opts.arg, &arg_boot_id, &arg_boot_offset); if (r < 0) - return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); + return log_error_errno(r, "Failed to parse boot descriptor '%s'", opts.arg); arg_boot = r; - } else if (optind < argc) { - /* Hmm, no argument? Maybe the next word on the command line is supposed to be the - * argument? Let's see if there is one and is parsable as a boot descriptor... */ - r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); - if (r >= 0) { - arg_boot = r; - optind++; + } else { + /* Hmm, no argument? Maybe the next word on the command line is supposed to + * be the argument? Let's see if there is one and is parsable as a boot + * descriptor… */ + char *peek = option_parser_peek_next_arg(&opts); + if (peek) { + r = parse_id_descriptor(peek, &arg_boot_id, &arg_boot_offset); + if (r >= 0) { + arg_boot = r; + (void) option_parser_consume_next_arg(&opts); + } } } break; - case 'u': - r = strv_extend(&arg_system_units, optarg); + OPTION('u', "unit", "UNIT", "Show logs from the specified unit"): + r = strv_extend(&arg_system_units, opts.arg); if (r < 0) return log_oom(); break; - case ARG_USER_UNIT: - r = strv_extend(&arg_user_units, optarg); + OPTION_LONG("user-unit", "UNIT", "Show logs from the specified user unit"): + r = strv_extend(&arg_user_units, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INVOCATION: - r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); + OPTION_LONG("invocation", "ID", "Show logs from the matching invocation ID"): + r = parse_id_descriptor(opts.arg, &arg_invocation_id, &arg_invocation_offset); if (r < 0) - return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); + return log_error_errno(r, "Failed to parse invocation descriptor: %s", opts.arg); arg_invocation = r; break; - case 'I': + OPTION_SHORT('I', NULL, "Show logs from the latest invocation of unit"): /* Equivalent to --invocation=0 */ arg_invocation = true; arg_invocation_id = SD_ID128_NULL; arg_invocation_offset = 0; break; - case 't': - r = strv_extend(&arg_syslog_identifier, optarg); + OPTION('t', "identifier", "ID", "Show entries with the specified syslog identifier"): + r = strv_extend(&arg_syslog_identifier, opts.arg); if (r < 0) return log_oom(); break; - case 'T': - r = strv_extend(&arg_exclude_identifier, optarg); + OPTION('T', "exclude-identifier", "ID", + "Hide entries with the specified syslog identifier"): + r = strv_extend(&arg_exclude_identifier, opts.arg); if (r < 0) return log_oom(); break; - case 'p': { + OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): { const char *dots; - dots = strstr(optarg, ".."); + dots = strstr(opts.arg, ".."); if (dots) { _cleanup_free_ char *a = NULL; int from, to, i; /* a range */ - a = strndup(optarg, dots - optarg); + a = strndup(opts.arg, dots - opts.arg); if (!a) return log_oom(); @@ -720,7 +544,7 @@ static int parse_argv(int argc, char *argv[]) { if (from < 0 || to < 0) return log_error_errno(from < 0 ? from : to, - "Failed to parse log level range %s", optarg); + "Failed to parse log level range %s", opts.arg); arg_priorities = 0; @@ -735,9 +559,9 @@ static int parse_argv(int argc, char *argv[]) { } else { int p, i; - p = log_level_from_string(optarg); + p = log_level_from_string(opts.arg); if (p < 0) - return log_error_errno(p, "Unknown log level %s", optarg); + return log_error_errno(p, "Unknown log level %s", opts.arg); arg_priorities = 0; @@ -748,16 +572,16 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FACILITY: { + OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"): { const char *p; - for (p = optarg;;) { + for (p = opts.arg;;) { _cleanup_free_ char *fac = NULL; int num; r = extract_first_word(&p, &fac, ",", 0); if (r < 0) - return log_error_errno(r, "Failed to parse facilities: %s", optarg); + return log_error_errno(r, "Failed to parse facilities: %s", opts.arg); if (r == 0) break; @@ -777,34 +601,40 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'g': - r = free_and_strdup_warn(&arg_pattern, optarg); + OPTION('g', "grep", "PATTERN", "Show entries with MESSAGE matching PATTERN"): + r = free_and_strdup_warn(&arg_pattern, opts.arg); if (r < 0) return r; break; - case ARG_CASE_SENSITIVE: - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "case-sensitive", "BOOL", + "Force case sensitive or insensitive matching"): + if (opts.arg) { + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg); + return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", opts.arg); arg_case = r ? PATTERN_COMPILE_CASE_SENSITIVE : PATTERN_COMPILE_CASE_INSENSITIVE; } else arg_case = PATTERN_COMPILE_CASE_SENSITIVE; break; - case 'k': + OPTION('k', "dmesg", NULL, "Show kernel message log from the current boot"): arg_dmesg = true; break; - case 'o': - if (streq(optarg, "help")) + OPTION_GROUP("Output Control Options"): {} + + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, verbose, export, json, json-pretty, " + "json-sse, json-seq, cat, with-unit)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(opts.arg); if (arg_output < 0) - return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); + return log_error_errno(arg_output, "Unknown output format '%s'.", opts.arg); if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) arg_quiet = true; @@ -816,10 +646,10 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_OUTPUT_FIELDS: { + OPTION_LONG("output-fields", "LIST", "Select fields to print in verbose/export/json modes"): { _cleanup_strv_free_ char **v = NULL; - v = strv_split(optarg, ","); + v = strv_split(opts.arg, ","); if (!v) return log_oom(); @@ -830,182 +660,195 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'n': - r = parse_lines(optarg ?: argv[optind], !optarg); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'n', "lines", "[+]INTEGER", + "Number of journal entries to show"): { + const char *p = opts.arg ?: option_parser_peek_next_arg(&opts); + + r = parse_lines(p, /* graceful= */ !opts.arg); if (r < 0) return r; - if (r > 0 && !optarg) - optind++; + if (r > 0 && !opts.arg) + (void) option_parser_consume_next_arg(&opts); break; + } - case 'r': + OPTION('r', "reverse", NULL, "Show the newest entries first"): arg_reverse = true; break; - case ARG_SHOW_CURSOR: + OPTION_LONG("show-cursor", NULL, "Print the cursor after all the entries"): arg_show_cursor = true; break; - case ARG_UTC: + OPTION_LONG("utc", NULL, "Express time in Coordinated Universal Time (UTC)"): arg_utc = true; break; - case 'x': + OPTION('x', "catalog", NULL, "Add message explanations where available"): arg_catalog = true; break; - case 'W': + OPTION('W', "no-hostname", NULL, "Suppress output of hostname field"): arg_no_hostname = true; break; - case 'l': + OPTION('l', "full", NULL, /* help= */ NULL): arg_full = true; break; - case ARG_NO_FULL: + OPTION_LONG("no-full", NULL, "Ellipsize fields"): arg_full = false; break; - case 'a': + OPTION('a', "all", NULL, "Show all fields, including long and unprintable"): arg_all = true; break; - case 'f': + OPTION('f', "follow", NULL, "Follow the journal"): arg_follow = true; break; - case ARG_NO_TAIL: + OPTION_LONG("no-tail", NULL, "Show all lines, even in follow mode"): arg_no_tail = true; break; - case ARG_TRUNCATE_NEWLINE: + OPTION_LONG("truncate-newline", NULL, "Truncate entries by first newline character"): arg_truncate_newline = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): arg_quiet = true; break; - case ARG_SYNCHRONIZE_ON_EXIT: - r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit); + OPTION_LONG("synchronize-on-exit", "BOOL", + "Wait for Journal synchronization before exiting"): + r = parse_boolean_argument("--synchronize-on-exit", opts.arg, &arg_synchronize_on_exit); if (r < 0) return r; - break; - case ARG_NO_PAGER: + OPTION_GROUP("Pager Control Options"): {} + + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'e': + OPTION('e', "pager-end", NULL, "Immediately jump to the end in the pager"): arg_pager_flags |= PAGER_JUMP_TO_END; break; + OPTION_GROUP("Forward Secure Sealing (FSS) Options"): {} + + OPTION_LONG("interval", "TIME", "Time interval for changing the FSS sealing key"): #if HAVE_GCRYPT - case ARG_INTERVAL: - r = parse_sec(optarg, &arg_interval); + r = parse_sec(opts.arg, &arg_interval); if (r < 0 || arg_interval <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse sealing key change interval: %s", optarg); + "Failed to parse sealing key change interval: %s", opts.arg); break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_VERIFY_KEY: + OPTION_LONG("verify-key", "KEY", "Specify FSS verification key"): +#if HAVE_GCRYPT erase_and_free(arg_verify_key); - arg_verify_key = strdup(optarg); + arg_verify_key = strdup(opts.arg); if (!arg_verify_key) return log_oom(); /* Use memset not explicit_bzero() or similar so this doesn't look confusing - * in ps or htop output. */ - memset(optarg, 'x', strlen(optarg)); + * in ps or htop output. We need to cast away the const to do this. */ + memset((char*) opts.arg, 'x', strlen(opts.arg)); arg_action = ACTION_VERIFY; arg_merge = false; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_FORCE: + OPTION_LONG("force", NULL, "Override of the FSS key pair with --setup-keys"): +#if HAVE_GCRYPT arg_force = true; break; - - case ARG_SETUP_KEYS: - arg_action = ACTION_SETUP_KEYS; - break; #else - case ARG_INTERVAL: - case ARG_VERIFY_KEY: - case ARG_FORCE: - case ARG_SETUP_KEYS: return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without forward-secure sealing support."); #endif - case 'h': + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'N': + OPTION('N', "fields", NULL, "List all field names currently used"): arg_action = ACTION_LIST_FIELD_NAMES; break; - case 'F': + OPTION('F', "field", "FIELD", "List all values that a specified field takes"): arg_action = ACTION_LIST_FIELDS; - r = free_and_strdup_warn(&arg_field, optarg); + r = free_and_strdup_warn(&arg_field, opts.arg); if (r < 0) return r; break; - case ARG_LIST_BOOTS: + OPTION_LONG("list-boots", NULL, "Show terse information about recorded boots"): arg_action = ACTION_LIST_BOOTS; break; - case ARG_LIST_INVOCATIONS: + OPTION_LONG("list-invocations", NULL, "Show invocation IDs of specified unit"): arg_action = ACTION_LIST_INVOCATIONS; break; - case ARG_LIST_NAMESPACES: + OPTION_LONG("list-namespaces", NULL, "Show list of journal namespaces"): arg_action = ACTION_LIST_NAMESPACES; break; - case ARG_DISK_USAGE: + OPTION_LONG("disk-usage", NULL, "Show total disk usage of all journal files"): arg_action = ACTION_DISK_USAGE; break; - case ARG_VACUUM_SIZE: - r = parse_size(optarg, 1024, &arg_vacuum_size); + OPTION_LONG("vacuum-size", "BYTES", "Reduce disk usage below specified size"): + r = parse_size(opts.arg, 1024, &arg_vacuum_size); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum size: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VACUUM_FILES: - r = safe_atou64(optarg, &arg_vacuum_n_files); + OPTION_LONG("vacuum-files", "INT", "Leave only the specified number of journal files"): + r = safe_atou64(opts.arg, &arg_vacuum_n_files); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum files: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VACUUM_TIME: - r = parse_sec(optarg, &arg_vacuum_time); + OPTION_LONG("vacuum-time", "TIME", "Remove journal files older than specified time"): + r = parse_sec(opts.arg, &arg_vacuum_time); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum time: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VERIFY: + OPTION_LONG("verify", NULL, "Verify journal file consistency"): arg_action = ACTION_VERIFY; break; - case ARG_SYNC: + OPTION_LONG("sync", NULL, "Synchronize unwritten journal messages to disk"): arg_action = ACTION_SYNC; break; - case ARG_SMART_RELINQUISH_VAR: { + OPTION_LONG("smart-relinquish-var", NULL, + "Similar, but NOP if log directory is on root mount"): { int root_mnt_id, log_mnt_id; /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: @@ -1030,45 +873,51 @@ static int parse_argv(int argc, char *argv[]) { _fallthrough_; } - case ARG_RELINQUISH_VAR: + OPTION_LONG("relinquish-var", NULL, "Stop logging to disk, log to temporary file system"): arg_action = ACTION_RELINQUISH_VAR; break; - case ARG_FLUSH: + OPTION_LONG("flush", NULL, "Flush all journal data from /run into /var"): arg_action = ACTION_FLUSH; break; - case ARG_ROTATE: + OPTION_LONG("rotate", NULL, "Request immediate rotation of the journal files"): arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; break; - case ARG_HEADER: + OPTION_LONG("header", NULL, "Show journal header information"): arg_action = ACTION_PRINT_HEADER; break; - case ARG_LIST_CATALOG: + OPTION_LONG("list-catalog", NULL, "Show all message IDs in the catalog"): arg_action = ACTION_LIST_CATALOG; break; - case ARG_DUMP_CATALOG: + OPTION_LONG("dump-catalog", NULL, "Show entries in the message catalog"): arg_action = ACTION_DUMP_CATALOG; break; - case ARG_UPDATE_CATALOG: + OPTION_LONG("update-catalog", NULL, "Update the message catalog database"): arg_action = ACTION_UPDATE_CATALOG; break; - case ARG_NEW_ID128: - arg_action = ACTION_NEW_ID128; + OPTION_LONG("setup-keys", NULL, "Generate a new FSS key pair"): +#if HAVE_GCRYPT + arg_action = ACTION_SETUP_KEYS; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("new-id128", NULL, /* help= */ NULL): + arg_action = ACTION_NEW_ID128; + break; } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + if (arg_no_tail) arg_lines = ARG_LINES_ALL; @@ -1109,10 +958,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--lines=+N is unsupported when --reverse or --follow is specified."); - if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) + if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments starting with '%s'", - argv[optind]); + args[0]); if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1146,6 +995,11 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_follow) arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE; + args = strv_copy(args); + if (!args) + return log_oom(); + + *remaining_args = args; return 1; } @@ -1158,17 +1012,13 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - r = strv_copy_unless_empty(strv_skip(argv, optind), &args); - if (r < 0) - return log_oom(); - if (arg_image) { assert(!arg_root); From 1dbd734048b9424f5eacfffe51ce3b5da6334df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 14:05:01 +0200 Subject: [PATCH 1586/2155] journalctl: drop some parentheses --- src/journal/journalctl.c | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 2b4995714f53a..a48e28497d3a6 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -527,55 +527,44 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { break; OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): { - const char *dots; - dots = strstr(opts.arg, ".."); + const char *dots = strstr(opts.arg, ".."); if (dots) { - _cleanup_free_ char *a = NULL; - int from, to, i; - /* a range */ - a = strndup(opts.arg, dots - opts.arg); + _cleanup_free_ char *a = strndup(opts.arg, dots - opts.arg); if (!a) return log_oom(); - from = log_level_from_string(a); - to = log_level_from_string(dots + 2); + int from = log_level_from_string(a), + to = log_level_from_string(dots + 2); if (from < 0 || to < 0) return log_error_errno(from < 0 ? from : to, "Failed to parse log level range %s", opts.arg); arg_priorities = 0; - - if (from < to) { - for (i = from; i <= to; i++) + if (from < to) + for (int i = from; i <= to; i++) arg_priorities |= 1 << i; - } else { - for (i = to; i <= from; i++) + else + for (int i = to; i <= from; i++) arg_priorities |= 1 << i; - } } else { - int p, i; - - p = log_level_from_string(opts.arg); + int p = log_level_from_string(opts.arg); if (p < 0) return log_error_errno(p, "Unknown log level %s", opts.arg); arg_priorities = 0; - - for (i = 0; i <= p; i++) + for (int i = 0; i <= p; i++) arg_priorities |= 1 << i; } break; } - OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"): { - const char *p; - - for (p = opts.arg;;) { + OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"): + for (const char *p = opts.arg;;) { _cleanup_free_ char *fac = NULL; int num; @@ -599,7 +588,6 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { } break; - } OPTION('g', "grep", "PATTERN", "Show entries with MESSAGE matching PATTERN"): r = free_and_strdup_warn(&arg_pattern, opts.arg); From 1955c18a18b2822b35b3ab37bb868fff05f33fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 14:37:35 +0200 Subject: [PATCH 1587/2155] journalctl: move handling of --smart-relinquish-var to action logic The help string for --smart-relinquish-var and --relinquish-var were in reversed order because of the _fallthrough_. We would resolve the conditions for "smart relinquish" immediately in parse_argv() and call 'return 0' if the conditions were wrong, terminating option parsing and the program. It seems nicer to delay action until later. This makes the logic flow more standard. This also allows the option parsing cases to be exchanged, fixing the issue with --help. --- src/journal/journalctl-varlink.c | 2 +- src/journal/journalctl.c | 56 +++++++++++++++++--------------- src/journal/journalctl.h | 1 + 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/journal/journalctl-varlink.c b/src/journal/journalctl-varlink.c index c278bc3365724..a38c320a4f868 100644 --- a/src/journal/journalctl-varlink.c +++ b/src/journal/journalctl-varlink.c @@ -65,7 +65,7 @@ int action_relinquish_var(void) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *link = NULL; int r; - assert(arg_action == ACTION_RELINQUISH_VAR); + assert(IN_SET(arg_action, ACTION_RELINQUISH_VAR, ACTION_SMART_RELINQUISH_VAR)); if (arg_machine || arg_namespace) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index a48e28497d3a6..c5009eb6f0560 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -835,36 +835,15 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_action = ACTION_SYNC; break; - OPTION_LONG("smart-relinquish-var", NULL, - "Similar, but NOP if log directory is on root mount"): { - int root_mnt_id, log_mnt_id; - - /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: - * if it's on the same mount as the root file system there's no point in - * relinquishing access and we can leave journald write to it until the very last - * moment. */ - - r = path_get_mnt_id("/", &root_mnt_id); - if (r < 0) - log_debug_errno(r, "Failed to get root mount ID, ignoring: %m"); - else { - r = path_get_mnt_id("/var/log/journal/", &log_mnt_id); - if (r < 0) - log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m"); - else if (root_mnt_id == log_mnt_id) { - log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var."); - return 0; - } else - log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it."); - } - - _fallthrough_; - } - OPTION_LONG("relinquish-var", NULL, "Stop logging to disk, log to temporary file system"): arg_action = ACTION_RELINQUISH_VAR; break; + OPTION_LONG("smart-relinquish-var", NULL, + "Similar, but NOP if log directory is on root mount"): + arg_action = ACTION_SMART_RELINQUISH_VAR; + break; + OPTION_LONG("flush", NULL, "Flush all journal data from /run into /var"): arg_action = ACTION_FLUSH; break; @@ -1075,6 +1054,31 @@ static int run(int argc, char *argv[]) { case ACTION_FLUSH: return action_flush_to_var(); + case ACTION_SMART_RELINQUISH_VAR: { + int root_mnt_id, log_mnt_id; + + /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: + * if it's on the same mount as the root file system there's no point in + * relinquishing access and we can leave journald write to it until the very last + * moment. */ + + r = path_get_mnt_id("/", &root_mnt_id); + if (r < 0) + log_debug_errno(r, "Failed to get root mount ID, ignoring: %m"); + else { + r = path_get_mnt_id("/var/log/journal/", &log_mnt_id); + if (r < 0) + log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m"); + else if (root_mnt_id == log_mnt_id) { + log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var."); + return 0; + } else + log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it."); + } + + _fallthrough_; + } + case ACTION_RELINQUISH_VAR: return action_relinquish_var(); diff --git a/src/journal/journalctl.h b/src/journal/journalctl.h index 090e3382496ca..406a16077be0f 100644 --- a/src/journal/journalctl.h +++ b/src/journal/journalctl.h @@ -21,6 +21,7 @@ typedef enum JournalctlAction { ACTION_LIST_NAMESPACES, ACTION_FLUSH, ACTION_RELINQUISH_VAR, + ACTION_SMART_RELINQUISH_VAR, ACTION_SYNC, ACTION_ROTATE, ACTION_VACUUM, From e530ef6761f3e0a763ea3f1536d90c367229355e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 13:19:18 +0200 Subject: [PATCH 1588/2155] btrfs-util: clear RDONLY flag on subvolume before destroy ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without CAP_SYS_ADMIN, btrfs_ioctl_snap_destroy() runs an inode_permission(MAY_WRITE) check against the target subvolume root, which btrfs_permission() rejects with EROFS for a read-only subvolume. As a result, unprivileged removal of a read-only subvolume fails — both via btrfs_subvol_remove_at() directly and via the recursive cleanup path used by rm_rf_subvolume_and_freep(), which propagates the EROFS up. Detect EROFS after the destroy ioctl, clear the RDONLY flag (only inode ownership is required for BTRFS_IOC_SUBVOL_SETFLAGS), and retry once. While at it, fix the surrounding comments: BTRFS_IOC_SNAP_DESTROY drops the entire subvolume tree, so regular files inside are irrelevant; ENOTEMPTY from the ioctl indicates nested subvolumes (BTRFS_ROOT_REF_KEY entries) via may_destroy_subvol(), not non-empty contents. --- src/shared/btrfs-util.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 9c86e49f0f86c..33d6af3942ec6 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -891,8 +891,9 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (r == 0) /* Not a btrfs subvolume */ return -ENOTTY; - /* Before we try anything, let's see if 'user_subvol_rm_allowed' is enabled and we can just remove - * the dir directly */ + /* Before we try anything, attempt VFS rmdir(). For an empty subvolume btrfs_rmdir dispatches + * to btrfs_delete_subvolume; for the non-empty case it returns ENOTEMPTY and we fall through + * to the destroy ioctl below. */ if (unlinkat(fd, subvolume, AT_REMOVEDIR) >= 0) goto finish; @@ -902,15 +903,29 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol return r; } - /* First, try to remove the subvolume. If it happens to be - * already empty, this will just work. */ + /* Try the destroy ioctl. Unlike unlinkat() above, this drops the entire subvolume tree, so + * regular files inside don't matter; it only returns ENOTEMPTY when there are nested + * subvolumes (BTRFS_ROOT_REF_KEY entries), which we then handle below. */ strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) - goto finish; + for (;;) { + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) + goto finish; + + /* Without CAP_SYS_ADMIN the kernel runs an inode_permission(MAY_WRITE) check against + * the subvolume root, which btrfs_permission() rejects with EROFS for a read-only + * subvolume. Clear the RDONLY flag and retry. */ + if (errno != EROFS || made_writable) + break; + + r = btrfs_subvol_set_read_only_fd(subvol_fd, false); + if (r < 0) + return r; + made_writable = true; + } if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) return -errno; - /* OK, the subvolume is not empty, let's look for child subvolumes, and remove them, first. + /* OK, there are nested subvolumes — enumerate and recurse into them, then retry. * BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER (kernel 4.18+) enumerate child * subvolumes without requiring CAP_SYS_ADMIN in the initial user namespace, unlike the older * BTRFS_IOC_TREE_SEARCH ioctl. */ From f2a4288412da482612220f78a2410a4a9dcd1176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 16:22:18 +0200 Subject: [PATCH 1589/2155] storagectl: add assert for args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit coccinelle-storage check started failing… --- src/storage/storagectl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index d3ae3f9145327..1a10a0979446f 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -399,11 +399,12 @@ static int verb_providers(int argc, char *argv[], uintptr_t data, void *userdata return 0; } -static int parse_argv(int argc, char *argv[], char ***args) { +static int parse_argv(int argc, char *argv[], char ***remaining_args) { int r; assert(argc >= 0); assert(argv); + assert(remaining_args); OptionParser opts = { argc, argv }; FOREACH_OPTION_OR_RETURN(c, &opts) @@ -442,7 +443,7 @@ static int parse_argv(int argc, char *argv[], char ***args) { break; } - *args = option_parser_get_args(&opts); + *remaining_args = option_parser_get_args(&opts); return 1; } From 8c0464b7b954e11f4733b1383eb917e80d3e6237 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 12 May 2026 08:10:46 -0700 Subject: [PATCH 1590/2155] shared: move exec_command_list_build_json() to varlink-common exec_command_list_build_json() serializes a linked list of ExecCommand into a JSON array. It was static in varlink-unit.c (used by service_context_build_json) but is also needed by the upcoming socket context builder. Move it next to exec_command_build_json() in varlink-common where it can be shared. --- src/core/varlink-common.c | 23 +++++++++++++++++++++++ src/core/varlink-common.h | 1 + src/core/varlink-unit.c | 23 ----------------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index 6f876db85b04f..f5745e12e8e49 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -136,3 +136,26 @@ int exec_command_build_json(sd_json_variant **ret, const char *name, void *userd 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))); } + +int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_build_json(&entry, /* name= */ NULL, c); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&v, entry); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index aa9f26145c1d9..8a238c34a5d73 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -8,4 +8,5 @@ int rlimit_table_build_json(sd_json_variant **ret, const char *name, void *userd 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); +int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata); int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2e3b1dd1961b8..e73e2d50e6792 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -118,29 +118,6 @@ static int unit_conditions_build_json(sd_json_variant **ret, const char *name, v return 0; } -static int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - ExecCommand *list = userdata; - int r; - - assert(ret); - - LIST_FOREACH(command, c, list) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; - - r = exec_command_build_json(&entry, /* name= */ NULL, c); - if (r < 0) - return r; - - r = sd_json_variant_append_array(&v, entry); - if (r < 0) - return r; - } - - *ret = TAKE_PTR(v); - return 0; -} - /* TODO: This covers only a small subset of a service object's properties. Extend to make more available to * consumers like Unit.StartTransient */ static int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { From 8c18bb6547c2138f2f17b921ec06f2c1f7cd17cd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 16:19:06 +0900 Subject: [PATCH 1591/2155] dhcp-message: introduce sd_dhcp_message object and several functions for the object --- src/libsystemd-network/dhcp-message.c | 349 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 53 ++++ src/libsystemd-network/dhcp-protocol.h | 8 + src/libsystemd-network/meson.build | 4 + src/libsystemd-network/test-dhcp-message.c | 134 ++++++++ 5 files changed, 548 insertions(+) create mode 100644 src/libsystemd-network/dhcp-message.c create mode 100644 src/libsystemd-network/dhcp-message.h create mode 100644 src/libsystemd-network/test-dhcp-message.c diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c new file mode 100644 index 0000000000000..eb43c7e6e534f --- /dev/null +++ b/src/libsystemd-network/dhcp-message.c @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "ether-addr-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" + +static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { + if (!message) + return NULL; + + tlv_done(&message->options); + return mfree(message); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_message, sd_dhcp_message, dhcp_message_free); + +int dhcp_message_new(sd_dhcp_message **ret) { + assert(ret); + + sd_dhcp_message *message = new(sd_dhcp_message, 1); + if (!message) + return -ENOMEM; + + *message = (sd_dhcp_message) { + .n_ref = 1, + .options = TLV_INIT(TLV_DHCP4), + }; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(message); + assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); + + /* RFC 2131 section 4.1.1: + * The client MUST include its hardware address in the ’chaddr’ field, if necessary for delivery of + * DHCP reply messages. + * + * RFC 4390 section 2.1: + * A DHCP client, when working over an IPoIB interface, MUST follow the following rules: + * "htype" (hardware address type) MUST be 32 [ARPPARAM]. + * "hlen" (hardware address length) MUST be 0. + * "chaddr" (client hardware address) field MUST be zeroed. + * + * Note, the maximum hardware address length (HW_ADDR_MAX_SIZE) is 32, but the size of the chaddr + * field is 16. + * + * Also, ARP type is 2 bytes, but the htype field is 1 byte. */ + + if (arp_type == ARPHRD_INFINIBAND) + hw_addr = NULL; + + if (hw_addr && hw_addr->length > sizeof_field(DHCPMessageHeader, chaddr)) + return -EINVAL; + + message->header = (DHCPMessageHeader) { + .op = op, + .htype = arp_type <= UINT8_MAX ? arp_type : 0, + .hlen = hw_addr ? hw_addr->length : 0, + .xid = htobe32(xid), + .magic = htobe32(DHCP_MAGIC_COOKIE), + }; + + if (hw_addr) + memcpy_safe(message->header.chaddr, hw_addr->bytes, hw_addr->length); + return 0; +} + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b) { + assert(message); + + SET_FLAG(message->header.flags, htobe16(0x8000), b); +} + +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message) { + assert(message); + + return FLAGS_SET(message->header.flags, htobe16(0x8000)); +} + +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret) { + assert(message); + assert(ret); + + if (message->header.hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + ret->length = message->header.hlen; + memcpy_safe(ret->bytes, message->header.chaddr, message->header.hlen); + return 0; +} + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + return tlv_contains(&message->options, code); +} + +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + tlv_remove(&message->options, code); +} + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data) { + assert(message); + return tlv_append(&message->options, code, length, data); +} + +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv) { + assert(message); + return tlv_append_tlv(&message->options, tlv); +} + +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, /* length= */ 0, /* data= */ NULL); +} + +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(uint8_t), &data); +} + +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + be16_t b = htobe16(data); + return dhcp_message_append_option(message, code, sizeof(be16_t), &b); +} + +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(be32_t), &data); +} + +int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { + int r; + + assert(message); + + struct iovec iov; + r = tlv_get_full(&message->options, code, length, ret ? &iov : NULL); + if (r < 0) + return r; + + if (ret) + memcpy_safe(ret, iov.iov_base, iov.iov_len); + return 0; +} + +int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret) { + assert(message); + return tlv_get_alloc(&message->options, code, ret); +} + +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + return dhcp_message_get_option(message, code, /* length= */ 0, /* ret= */ NULL); +} + +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(uint8_t), ret); +} + +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret) { + be16_t b; + int r; + + assert(message); + + r = dhcp_message_get_option(message, code, sizeof(be16_t), ret ? &b : NULL); + if (r < 0) + return r; + + if (ret) + *ret = be16toh(b); + return 0; +} + +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(be32_t), ret); +} + +static int dhcp_message_verify_header( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(iov); + assert(iovec_is_valid(iov)); + assert(IN_SET(op, 0, BOOTREQUEST, BOOTREPLY)); /* when 0, both BOOTREQUEST and BOOTREPLY are accepted */ + + POINTER_MAY_BE_NULL(xid); + POINTER_MAY_BE_NULL(hw_addr); + + if (iov->iov_len < sizeof(DHCPMessageHeader)) + return -EBADMSG; + + const DHCPMessageHeader *header = iov->iov_base; + + if (!IN_SET(header->op, BOOTREQUEST, BOOTREPLY)) + return -EBADMSG; + if (op != 0 && header->op != op) + return -EBADMSG; + + if (xid && be32toh(header->xid) != *xid) + return -EBADMSG; + + if (arp_type <= UINT8_MAX && header->htype != arp_type) + return -EBADMSG; + + if (header->hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + if (hw_addr && memcmp_nn(header->chaddr, header->hlen, hw_addr->bytes, hw_addr->length) != 0) + return -EBADMSG; + + return 0; +} + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret) { + + int r; + + assert(iov); + assert(ret); + + r = dhcp_message_verify_header(iov, op, xid, arp_type, hw_addr); + if (r < 0) + return r; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + memcpy(&message->header, iov->iov_base, sizeof(DHCPMessageHeader)); + + if (be32toh(message->header.magic) != DHCP_MAGIC_COOKIE) { + /* Should be BOOTP, and no options. */ + *ret = TAKE_PTR(message); + return 0; + } + + /* In the BOOTP protocol (RFC 951), the vendor field (magic + options) is 64 bytes, but here we do + * not check the length, and support all DHCP options even if we are running as BOOTP client. */ + + r = tlv_parse(&message->options, &IOVEC_SHIFT(iov, sizeof(DHCPMessageHeader))); + if (r < 0) + return r; + + /* Parse SD_DHCP_OPTION_OVERLOAD (52) to determine if we should parse sname and/or file. */ + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.file, sizeof(message->header.file))); + if (r < 0) + return r; + } + + if (FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.sname, sizeof(message->header.sname))); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret) { + int r; + + assert(message); + assert(ret); + + size_t size = size_add(sizeof(DHCPMessageHeader), tlv_size(&message->options)); + if (size > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovw_extend(&iovw, &message->header, sizeof(DHCPMessageHeader)); + if (r < 0) + return r; + + struct iovec options; + r = tlv_build(&message->options, &options); + if (r < 0) + return r; + + r = iovw_consume_iov(&iovw, &options); + if (r < 0) + return r; + + /* For compatibility with other implementations and network appliances, the message size should be at + * least 300 bytes, which is the size of BOOTP message. */ + size_t padding_size = LESS_BY(BOOTP_MESSAGE_SIZE, size); + if (padding_size > 0) { + uint8_t *padding = new0(uint8_t, padding_size); + if (!padding) + return -ENOMEM; + + r = iovw_consume(&iovw, padding, padding_size); + if (r < 0) + return r; + } + + *ret = TAKE_STRUCT(iovw); + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h new file mode 100644 index 0000000000000..a4ff1c8a656a3 --- /dev/null +++ b/src/libsystemd-network/dhcp-message.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "sparse-endian.h" +#include "tlv-util.h" + +typedef struct sd_dhcp_message sd_dhcp_message; + +sd_dhcp_message* sd_dhcp_message_ref(sd_dhcp_message *p); +sd_dhcp_message* sd_dhcp_message_unref(sd_dhcp_message *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_message*, sd_dhcp_message_unref); + +int dhcp_message_new(sd_dhcp_message **ret); + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr); + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b); +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message); +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret); + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code); +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code); + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data); +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv); +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data); +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); + +int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); +int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret); +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret); + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index c2df5a574a89e..00d232a74eacd 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -13,6 +13,7 @@ #include "sd-forward.h" #include "sparse-endian.h" #include "time-util.h" +#include "tlv-util.h" /* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4. * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */ @@ -49,6 +50,13 @@ struct DHCPMessage { typedef struct DHCPMessage DHCPMessage; assert_cc(sizeof(DHCPMessageHeader) == offsetof(DHCPMessage, options)); +struct sd_dhcp_message { + unsigned n_ref; + + DHCPMessageHeader header; + TLV options; +}; + struct DHCPPacket { struct iphdr ip; struct udphdr udp; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 6239056e3b4b1..b21f138aa6bc9 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -3,6 +3,7 @@ libsystemd_network_sources = files( 'arp-util.c', 'dhcp-client-send.c', + 'dhcp-message.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', @@ -77,6 +78,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-client.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-message.c'), + }, network_test_template + { 'sources' : files('test-dhcp-option.c'), }, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c new file mode 100644 index 0000000000000..407fc0f16a1bb --- /dev/null +++ b/src/libsystemd-network/test-dhcp-message.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "ether-addr-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "random-util.h" +#include "tests.h" + +static void verify_header(sd_dhcp_message *m, uint32_t xid, const struct hw_addr_data *hw_addr) { + ASSERT_EQ(be32toh(m->header.xid), xid); + + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, true); + ASSERT_TRUE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, false); + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + + struct hw_addr_data a; + ASSERT_OK(dhcp_message_get_hw_addr(m, &a)); + ASSERT_TRUE(hw_addr_equal(&a, hw_addr)); +} + +static void verify_flag(sd_dhcp_message *m) { + ASSERT_TRUE(dhcp_message_has_option(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_OK(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_RAPID_COMMIT, NULL), ENODATA); /* size mismatch */ +} + +static void verify_u8(sd_dhcp_message *m, uint8_t expected) { + uint8_t u; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_u16(sd_dhcp_message *m, uint16_t expected) { + uint16_t u; + ASSERT_OK(dhcp_message_get_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); + ASSERT_EQ(a.s_addr, expected->s_addr); +} + +TEST(dhcp_message) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_NOT_NULL(m); + + uint32_t xid = random_u32(); + + struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }; + + /* 192.0.2.42 */ + struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; + + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* header */ + verify_header(m, xid, &hw_addr); + + ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_PAD, 0, NULL), EINVAL); + ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_END, 0, NULL), EINVAL); + + /* flag */ + ASSERT_ERROR(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), ENODATA); + ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_ERROR(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), EEXIST); + verify_flag(m); + + /* u8 */ + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST), EEXIST); + verify_u8(m, DHCP_DISCOVER); + + /* u16 */ + ASSERT_OK(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 512)); + ASSERT_ERROR(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 1024), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 32), EEXIST); + verify_u16(m, 512); + + /* address */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); + ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); + verify_address(m, &addr); + + /* build and parse */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_build(m, &iovw)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&iovw, &joined)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m2 = NULL; + ASSERT_OK(dhcp_message_parse( + &joined, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m2)); + + ASSERT_EQ(memcmp(&m2->header, &m->header, sizeof(m->header)), 0); + + /* verify parsed message */ + verify_header(m2, xid, &hw_addr); + verify_flag(m2); + verify_u8(m2, DHCP_DISCOVER); + verify_u16(m2, 512); + verify_address(m2, &addr); + + /* build again, and verify the packet */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; + ASSERT_OK(dhcp_message_build(m2, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 7b97d772a25954d9b35e6856aed323ac54fa468b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 13 Apr 2026 11:23:03 +0900 Subject: [PATCH 1592/2155] dhcp-message: introduce dhcp_message_{append,get}_option_sec() These are for e.g. DHCP options 51 (lease time), 58 (renewal time), and 59 (rebinding time). --- src/libsystemd-network/dhcp-message.c | 21 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index eb43c7e6e534f..85976ec6ed1d0 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -9,6 +9,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "network-common.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { if (!message) @@ -161,6 +162,11 @@ int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32 return dhcp_message_append_option(message, code, sizeof(be32_t), &data); } +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec) { + assert(message); + return dhcp_message_append_option_be32(message, code, usec_to_be32_sec(usec)); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -211,6 +217,21 @@ int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t return dhcp_message_get_option(message, code, sizeof(be32_t), ret); } +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret) { + int r; + + assert(message); + + be32_t t; + r = dhcp_message_get_option_be32(message, code, &t); + if (r < 0) + return r; + + if (ret) + *ret = be32_sec_to_usec(t, max_as_infinity); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index a4ff1c8a656a3..6086cda3c58f1 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -34,6 +34,7 @@ int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code); int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data); int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -41,6 +42,7 @@ int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code); int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret); int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 407fc0f16a1bb..3ac8a66d21ea3 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -42,6 +42,18 @@ static void verify_u16(sd_dhcp_message *m, uint16_t expected) { ASSERT_EQ(u, expected); } +static void verify_sec(sd_dhcp_message *m, usec_t expected) { + usec_t t; + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, UINT32_MAX * USEC_PER_SEC); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, USEC_INFINITY); +} + static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); @@ -61,6 +73,8 @@ TEST(dhcp_message) { .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, }; + usec_t lease_time = USEC_PER_DAY; + /* 192.0.2.42 */ struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; @@ -95,6 +109,11 @@ TEST(dhcp_message) { ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 32), EEXIST); verify_u16(m, 512); + /* sec */ + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, lease_time)); + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, USEC_INFINITY)); + verify_sec(m, lease_time); + /* address */ ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); @@ -123,6 +142,7 @@ TEST(dhcp_message) { verify_flag(m2); verify_u8(m2, DHCP_DISCOVER); verify_u16(m2, 512); + verify_sec(m2, lease_time); verify_address(m2, &addr); /* build again, and verify the packet */ From 4b2ff0a00f1aa9ac491d6e73dce6d59af7261f4f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 14:46:59 +0900 Subject: [PATCH 1593/2155] dhcp-message: introduce dhcp_message_{append,get}_option_address() These are equivalent to dhcp_message_{append,get}_option_be32(), but for type safety. These are for e.g. DHCP options 50 (requested IP address). --- src/libsystemd-network/dhcp-message.c | 11 +++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 85976ec6ed1d0..6344a7a41aa79 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -167,6 +167,12 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_ return dhcp_message_append_option_be32(message, code, usec_to_be32_sec(usec)); } +int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr) { + assert(message); + assert(addr); + return dhcp_message_append_option_be32(message, code, addr->s_addr); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -232,6 +238,11 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max return 0; } +int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret) { + assert(message); + return dhcp_message_get_option_be32(message, code, ret ? &ret->s_addr : NULL); +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 6086cda3c58f1..e0e5d8758836b 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -35,6 +35,7 @@ int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_ int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); +int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -43,6 +44,7 @@ int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t * int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); +int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 3ac8a66d21ea3..92d22d4de1c88 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -58,6 +58,8 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); ASSERT_EQ(a.s_addr, expected->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a)); + ASSERT_EQ(a.s_addr, expected->s_addr); } TEST(dhcp_message) { @@ -117,6 +119,9 @@ TEST(dhcp_message) { /* address */ ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr)); verify_address(m, &addr); /* build and parse */ From 174a61135cad747dfcaa66bb9312fe63e94a8364 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:19:41 +0900 Subject: [PATCH 1594/2155] dhcp-message: introduce dhcp_message_{append,get}_option_addresses() These functions support multiple addresses. These are for e.g. DHCP options 6 (DNS servers), 42 (NTP servers) and so on. --- src/libsystemd-network/dhcp-message.c | 38 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 31 ++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 6344a7a41aa79..816d16955d6ba 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -173,6 +173,19 @@ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, c return dhcp_message_append_option_be32(message, code, addr->s_addr); } +int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr) { + assert(message); + assert(n_addr == 0 || addr); + + if (n_addr == 0) + return 0; + + if (size_multiply_overflow(sizeof(struct in_addr), n_addr)) + return -ENOBUFS; + + return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -243,6 +256,31 @@ int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, stru return dhcp_message_get_option_be32(message, code, ret ? &ret->s_addr : NULL); } +int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr) { + int r; + + assert(message); + assert(ret_n_addr || !ret_addr); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + if (iov.iov_len % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n = iov.iov_len / sizeof(struct in_addr); + if (n == 0) + return -ENODATA; + + if (ret_addr) + *ret_addr = (struct in_addr*) TAKE_PTR(iov.iov_base); + if (ret_n_addr) + *ret_n_addr = n; + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e0e5d8758836b..6d4e1939b0f2c 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -36,6 +36,7 @@ int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint1 int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); +int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -45,6 +46,7 @@ int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); +int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 92d22d4de1c88..7c1c255f951f6 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -62,6 +62,23 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { ASSERT_EQ(a.s_addr, expected->s_addr); } +static void verify_addresses( + sd_dhcp_message *m, + size_t n_ntp, const struct in_addr *ntp) { + + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &a)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_ntp); + ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -80,6 +97,14 @@ TEST(dhcp_message) { /* 192.0.2.42 */ struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; + /* 192.0.2.1 - 4 */ + struct in_addr ntp[4] = { + { .s_addr = htobe32(0xC0000201) }, + { .s_addr = htobe32(0xC0000202) }, + { .s_addr = htobe32(0xC0000203) }, + { .s_addr = htobe32(0xC0000204) }, + }; + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -124,6 +149,11 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr)); verify_address(m, &addr); + /* multiple addresses */ + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0])); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); + verify_addresses(m, ELEMENTSOF(ntp), ntp); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -149,6 +179,7 @@ TEST(dhcp_message) { verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); + verify_addresses(m2, ELEMENTSOF(ntp), ntp); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 1c4ee9e96993d213917c58c5d6ff5578c59311ed Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 14:41:29 +0900 Subject: [PATCH 1595/2155] dhcp-message: introduce dhcp_message_{append,get}_option_string() These are for DHCP options that takes a string e.g. DHCP options 17 (root path), 60 (vendor class identifier), and so on. --- src/libsystemd-network/dhcp-message.c | 48 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 38 +++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 816d16955d6ba..eafc84c179e13 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -10,6 +10,7 @@ #include "iovec-wrapper.h" #include "ip-util.h" #include "network-common.h" +#include "string-util.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { if (!message) @@ -186,6 +187,21 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); } +int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data) { + assert(message); + + if (isempty(data)) + return 0; + + if (!string_is_safe(data, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EINVAL; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, strlen(data), data); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -281,6 +297,38 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si return 0; } +int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + /* Allow NUL at the end for buggy DHCP servers, but refuse intermediate NUL. */ + if (memchr(iov.iov_base, 0, iov.iov_len - 1)) + return -EBADMSG; + + /* Note, dhcp_message_get_option_alloc() -> tlv_get_alloc() allocates an extra byte to make + * iov.iov_base can be handled as a NUL-terminated string. Hence, we can directly pass it to + * isempty() and string_is_safe(). */ + + if (isempty(iov.iov_base)) + return -ENODATA; + + if (!string_is_safe(iov.iov_base, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(iov.iov_base); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 6d4e1939b0f2c..d47d7ba4094ba 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -37,6 +37,7 @@ int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32 int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); +int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -47,6 +48,7 @@ int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); +int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 7c1c255f951f6..31e0754d9316d 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -2,12 +2,14 @@ #include +#include "alloc-util.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "random-util.h" +#include "strv.h" #include "tests.h" static void verify_header(sd_dhcp_message *m, uint32_t xid, const struct hw_addr_data *hw_addr) { @@ -79,6 +81,19 @@ static void verify_addresses( ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); } +static void verify_string(sd_dhcp_message *m, const char *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, &s)); + ASSERT_STREQ(s, expected); +} + +static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_ROOT_PATH, &s)); + _cleanup_free_ char *joined = ASSERT_NOT_NULL(strv_join(expected, /* separator= */ "")); + ASSERT_STREQ(s, joined); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -105,6 +120,9 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + const char *vendor_class = "hogehoge"; + char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -118,6 +136,11 @@ TEST(dhcp_message) { ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_PAD, 0, NULL), EINVAL); ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_END, 0, NULL), EINVAL); + /* multiple strings */ + STRV_FOREACH(s, root_path) + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_ROOT_PATH, strlen(*s), *s)); + verify_multiple_strings(m, root_path); + /* flag */ ASSERT_ERROR(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), ENODATA); ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); @@ -154,6 +177,19 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); verify_addresses(m, ELEMENTSOF(ntp), ntp); + /* string */ + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 0, NULL)); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 1, "\0")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 9, "hoge\0hoge")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), EBADMSG); + ASSERT_ERROR(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); + verify_string(m, vendor_class); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -174,12 +210,14 @@ TEST(dhcp_message) { /* verify parsed message */ verify_header(m2, xid, &hw_addr); + verify_multiple_strings(m2, root_path); verify_flag(m2); verify_u8(m2, DHCP_DISCOVER); verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); verify_addresses(m2, ELEMENTSOF(ntp), ntp); + verify_string(m2, vendor_class); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From a6a6e371f76f5fac2abd6255be076941a2841403 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:51:39 +0900 Subject: [PATCH 1596/2155] dhcp-message: introduce dhcp_message_{append,get}_option_client_id() These are for DHCP option 61 (client ID). --- src/libsystemd-network/dhcp-message.c | 28 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 3 +++ src/libsystemd-network/test-dhcp-message.c | 17 +++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index eafc84c179e13..0882422d11528 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" @@ -202,6 +203,19 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co return dhcp_message_append_option(message, code, strlen(data), data); } +int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { + assert(message); + assert(id); + + if (!sd_dhcp_client_id_is_set(id)) + return -EINVAL; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER)) + return -EEXIST; + + return dhcp_message_append_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, id->size, id->raw); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -329,6 +343,20 @@ int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char return 0; } +int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { + int r; + + assert(message); + assert(ret); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, &iov); + if (r < 0) + return r; + + return sd_dhcp_client_id_set_raw(ret, iov.iov_base, iov.iov_len); +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index d47d7ba4094ba..50c1810c7d672 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dhcp-client-id.h" #include "sd-forward.h" #include "sparse-endian.h" @@ -38,6 +39,7 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); +int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -49,6 +51,7 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 31e0754d9316d..cca5c0603b7bd 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" @@ -94,6 +95,12 @@ static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) ASSERT_STREQ(s, joined); } +static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) { + sd_dhcp_client_id id = {}; + ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); + ASSERT_EQ(client_id_compare_func(&id, expected), 0); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -120,6 +127,11 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + sd_dhcp_client_id id = { + .raw = { 1, 3, 3, 3, 3, 3, 3, }, + .size = 7, + }; + const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); @@ -190,6 +202,10 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); verify_string(m, vendor_class); + /* client ID */ + ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); + verify_client_id(m, &id); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -218,6 +234,7 @@ TEST(dhcp_message) { verify_address(m2, &addr); verify_addresses(m2, ELEMENTSOF(ntp), ntp); verify_string(m2, vendor_class); + verify_client_id(m2, &id); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From aa9584a0371a4e90b12869e7e7a945d46e4218b6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:47:45 +0900 Subject: [PATCH 1597/2155] dhcp-message: introduce dhcp_message_{append,get}_option_parameter_request_list() These are for DHCP option 55 (parameter request list). --- src/libsystemd-network/dhcp-message.c | 58 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 17 +++++++ 3 files changed, 77 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 0882422d11528..a2d0cb8c67744 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -11,6 +11,8 @@ #include "iovec-wrapper.h" #include "ip-util.h" #include "network-common.h" +#include "set.h" +#include "sort-util.h" #include "string-util.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { @@ -216,6 +218,35 @@ int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp return dhcp_message_append_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, id->size, id->raw); } +static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + assert(a); + assert(b); + + return CMP(*a, *b); +} + +int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl) { + assert(message); + + size_t len = set_size(prl); + if (len == 0) + return 0; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + void *q; + SET_FOREACH(q, prl) + *p++ = PTR_TO_UINT8(q); + + /* Sort the options to make the message reproducible. */ + typesafe_qsort(buf, len, cmp_uint8); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -357,6 +388,33 @@ int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_i return sd_dhcp_client_id_set_raw(ret, iov.iov_base, iov.iov_len); } +int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + if (!ret) + return 0; + + _cleanup_set_free_ Set *prl = NULL; + for (struct iovec i = iov; iovec_is_set(&i); iovec_inc(&i, 1)) { + r = set_ensure_put(&prl, /* hash_ops= */ NULL, UINT8_TO_PTR(*(uint8_t*) i.iov_base)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(prl); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 50c1810c7d672..4ceba1eec1316 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -40,6 +40,7 @@ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, c int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); +int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -52,6 +53,7 @@ int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, stru int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); +int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index cca5c0603b7bd..bdb3ef921a2bb 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -10,6 +10,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "random-util.h" +#include "set.h" #include "strv.h" #include "tests.h" @@ -101,6 +102,12 @@ static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expect ASSERT_EQ(client_id_compare_func(&id, expected), 0); } +static void verify_prl(sd_dhcp_message *m, Set *expected) { + _cleanup_set_free_ Set *set = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &set)); + ASSERT_TRUE(set_equal(set, expected)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -132,6 +139,10 @@ TEST(dhcp_message) { .size = 7, }; + _cleanup_set_free_ Set *prl = NULL; + for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) + ASSERT_OK(set_ensure_put(&prl, /* hash_ops= */ NULL, UINT_TO_PTR(i))); + const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); @@ -206,6 +217,11 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); verify_client_id(m, &id); + /* parameter request list */ + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + verify_prl(m, prl); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -235,6 +251,7 @@ TEST(dhcp_message) { verify_addresses(m2, ELEMENTSOF(ntp), ntp); verify_string(m2, vendor_class); verify_client_id(m2, &id); + verify_prl(m2, prl); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 28423ea18ceeae387ec4a67229f525b62bddcc3d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:42:18 +0900 Subject: [PATCH 1598/2155] dhcp-message: introduce dhcp_message_{append,get}_option_hostname() and related functions These are for DHCP options 12 (Hostname) and 81 (FQDN) options. --- src/libsystemd-network/dhcp-message.c | 164 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 4 + src/libsystemd-network/test-dhcp-message.c | 22 +++ 3 files changed, 190 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index a2d0cb8c67744..92f7d5ed8c0e5 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -6,7 +6,10 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#include "dns-domain.h" +#include "errno-util.h" #include "ether-addr-util.h" +#include "hostname-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" @@ -247,6 +250,62 @@ int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf); } +static int dhcp_message_append_option_fqdn(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *fqdn) { + int r; + + assert(message); + assert(fqdn); + + /* FIXME: Allow long fqdn, as now we support long option. */ + uint8_t buf[3 + DHCP_MAX_FQDN_LENGTH]; + + /* RFC 4702 section 2.1 + * The "E" bit indicates the encoding of the Domain Name field. 1 indicates canonical wire format, + * without compression. This encoding SHOULD be used by clients and MUST be supported by servers. + * A server MUST use the same encoding as that used by the client. A server that does not support + * the deprecated ASCII encoding MUST ignore Client FQDN options that use that encoding. + * + * Here, we unconditionally set the 'E' flag. Hence, sd_dhcp_server must ignore the option if a + * client does not set the 'E' flag in the request. */ + buf[0] = flags | DHCP_FQDN_FLAG_E; + + /* RFC 4702 section 2.2 + * The two 1-octet RCODE1 and RCODE2 fields are deprecated. A client SHOULD set these to 0 when + * sending the option and SHOULD ignore them on receipt. A server SHOULD set these to 255 when + * sending the option and MUST ignore them on receipt. */ + buf[1] = is_client ? 0 : 255; + buf[2] = is_client ? 0 : 255; + + r = dns_name_to_wire_format(fqdn, buf + 3, sizeof(buf) - 3, false); + if (r <= 0) + return r; + + return dhcp_message_append_option(message, SD_DHCP_OPTION_FQDN, 3 + r, buf); +} + +int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname) { + assert(message); + + /* Hostname (12) or FQDN (81) + * + * RFC 4702 section 3.1 + * clients that send the Client FQDN option in their messages MUST NOT also send the Host Name option. */ + + if (isempty(hostname)) + return 0; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_HOST_NAME)) + return -EEXIST; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_FQDN)) + return -EEXIST; + + if (dns_name_is_single_label(hostname)) + return dhcp_message_append_option_string(message, SD_DHCP_OPTION_HOST_NAME, hostname); + + return dhcp_message_append_option_fqdn(message, flags, is_client, hostname); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -415,6 +474,111 @@ int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set return 0; } +static int normalize_dns_name(const char *name, char **ret) { + int r; + + assert(name); + + _cleanup_free_ char *normalized = NULL; + r = dns_name_normalize(name, /* flags= */ 0, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + if (ret) + *ret = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_FQDN, &iov); + if (r < 0) + return r; + + if (iov.iov_len <= 3) + return -EBADMSG; + + uint8_t flags = *(uint8_t*) iov.iov_base; + if (!FLAGS_SET(flags, DHCP_FQDN_FLAG_E)) + return -EOPNOTSUPP; + + struct iovec i; + iovec_shift(&iov, 3, &i); + + _cleanup_free_ char *name = NULL; + const uint8_t *p = i.iov_base; + r = dns_name_from_wire_format(&p, &i.iov_len, &name); + if (r < 0) + return r; + if (i.iov_len > 0) /* trailing garbage? */ + return -EBADMSG; + + if (isempty(name)) + return -ENODATA; + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + if (ret_flags) + *ret_flags = flags; + if (ret_fqdn) + *ret_fqdn = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret) { + int r; + + assert(message); + + /* Mainly for Host Name or Domain Name options. */ + + _cleanup_free_ char *name = NULL; + r = dhcp_message_get_option_string(message, code, &name); + if (r < 0) + return r; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { + int r; + + assert(message); + + /* FQDN option always takes precedence. */ + r = dhcp_message_get_option_fqdn(message, /* ret_flags= */ NULL, ret); + if (ERRNO_IS_NEG_RESOURCE(r)) + return r; + if (r >= 0) + return 0; + + /* Then, fall back to Host Name option. */ + return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 4ceba1eec1316..ad1688389288e 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -41,6 +41,7 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); +int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -54,6 +55,9 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); +int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); +int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index bdb3ef921a2bb..816edc9fc69e7 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -108,6 +108,12 @@ static void verify_prl(sd_dhcp_message *m, Set *expected) { ASSERT_TRUE(set_equal(set, expected)); } +static void verify_hostname(sd_dhcp_message *m, const char *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_hostname(m, &s)); + ASSERT_STREQ(s, expected); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -143,6 +149,7 @@ TEST(dhcp_message) { for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) ASSERT_OK(set_ensure_put(&prl, /* hash_ops= */ NULL, UINT_TO_PTR(i))); + const char *hostname = "test-node.example.com"; const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); @@ -222,6 +229,20 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); verify_prl(m, prl); + /* hostname */ + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge")); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME); + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge.example.com")); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN); + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname)); + verify_hostname(m, hostname); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -252,6 +273,7 @@ TEST(dhcp_message) { verify_string(m2, vendor_class); verify_client_id(m2, &id); verify_prl(m2, prl); + verify_hostname(m2, hostname); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 25482d832c00b084c65fe0f541d0203cdb11dfad Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 16 Apr 2026 03:02:27 +0900 Subject: [PATCH 1599/2155] sd-dhcp-lease: drop sd_dhcp_lease.have_subnet_mask and have_broadcast NULL address is invalid in both cases. Let's refuse to use them when NULL. --- src/libsystemd-network/dhcp-lease-internal.h | 4 ---- src/libsystemd-network/sd-dhcp-lease.c | 15 +++------------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 3f5d638d67d39..b436287aeed04 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -40,11 +40,7 @@ struct sd_dhcp_lease { be32_t address; be32_t server_address; be32_t next_server; - - bool have_subnet_mask; be32_t subnet_mask; - - bool have_broadcast; be32_t broadcast; struct in_addr *router; diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 1eec439d81065..bfe4874be5a5f 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -69,7 +69,7 @@ int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - if (!lease->have_broadcast) + if (lease->broadcast == INADDR_ANY) return -ENODATA; addr->s_addr = lease->broadcast; @@ -259,7 +259,7 @@ int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - if (!lease->have_subnet_mask) + if (lease->subnet_mask == INADDR_ANY) return -ENODATA; addr->s_addr = lease->subnet_mask; @@ -920,16 +920,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void r = lease_parse_be32(option, len, &lease->subnet_mask); if (r < 0) log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); - else - lease->have_subnet_mask = true; break; case SD_DHCP_OPTION_BROADCAST: r = lease_parse_be32(option, len, &lease->broadcast); if (r < 0) log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); - else - lease->have_broadcast = true; break; case SD_DHCP_OPTION_ROUTER: @@ -1579,8 +1575,6 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { r = inet_pton(AF_INET, netmask, &lease->subnet_mask); if (r <= 0) log_debug("Failed to parse netmask %s, ignoring.", netmask); - else - lease->have_subnet_mask = true; } if (server_address) { @@ -1599,8 +1593,6 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { r = inet_pton(AF_INET, broadcast, &lease->broadcast); if (r <= 0) log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); - else - lease->have_broadcast = true; } if (dns) { @@ -1759,7 +1751,7 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { assert(lease); - if (lease->have_subnet_mask) + if (lease->subnet_mask != INADDR_ANY) return 0; if (lease->address == 0) @@ -1773,7 +1765,6 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { return r; lease->subnet_mask = mask.s_addr; - lease->have_subnet_mask = true; return 0; } From e507487b6dde82a391e62b7c4dc5c9500d00a2e0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 22:40:22 +0900 Subject: [PATCH 1600/2155] dhcp-protocol: introduce more sub-options for DHCP Relay Agent Information option These new values will be used later. --- src/libsystemd-network/dhcp-protocol.h | 5 +++++ src/systemd/sd-dhcp-protocol.h | 27 +++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index c2df5a574a89e..49f0125b20ce5 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -124,4 +124,9 @@ enum { DHCP_FQDN_FLAG_N = (1 << 3), }; +/* For SD_DHCP_RELAY_AGENT_FLAGS sub-option. */ +enum { + DHCP_RELAY_AGENT_FLAG_UNICAST = 1 << 0, +}; + DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index 55ce189cf4979..8ba28777a3c4f 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -193,10 +193,31 @@ enum { SD_DHCP_OPTION_END = 255 /* [RFC2132] */ }; -/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */ +/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option. See + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#relay-agent-sub-options */ enum { - SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1, - SD_DHCP_RELAY_AGENT_REMOTE_ID = 2 + SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1, + SD_DHCP_RELAY_AGENT_REMOTE_ID = 2, + /* suboption code 3 is reserved */ + SD_DHCP_RELAY_AGENT_DOCSIS_DEVICE_CLASS = 4, + SD_DHCP_RELAY_AGENT_LINK_SELECTION = 5, + SD_DHCP_RELAY_AGENT_SUBSCRIBER_ID = 6, + SD_DHCP_RELAY_AGENT_RADIUS_ATTRIBUTE = 7, + SD_DHCP_RELAY_AGENT_AUTHENTICATION = 8, + SD_DHCP_RELAY_AGENT_VENDOR_SPECIFIC_INFORMATION = 9, + SD_DHCP_RELAY_AGENT_FLAGS = 10, + SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE = 11, + SD_DHCP_RELAY_AGENT_IDENTIFIER = 12, + SD_DHCP_RELAY_AGENT_ACCESS_TECHNOLOGY_TYPE = 13, + SD_DHCP_RELAY_AGENT_ACCESS_NETWORK_NAME = 14, + SD_DHCP_RELAY_AGENT_ACCESS_POINT_NAME = 15, + SD_DHCP_RELAY_AGENT_ACCESS_POINT_BSSID = 16, + SD_DHCP_RELAY_AGENT_OPERATOR_IDENTIFIER = 17, + SD_DHCP_RELAY_AGENT_OPERATOR_REALM = 18, + SD_DHCP_RELAY_AGENT_SOURCE_PORT = 19, + /* suboption code 20-150 are unassigned */ + SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION = 151, + SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION_CONTROL = 152 }; _SD_END_DECLARATIONS; From f51e7eeb7c29020fb43b307b7dd09bb630cba1a0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 14:16:00 +0900 Subject: [PATCH 1601/2155] fuzz: modernize fuzz-dhcp-server - Do not include .c file. - Use ASSERT_OK() and friends. --- src/libsystemd-network/fuzz-dhcp-server.c | 55 +++++++++++------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index bcd8af629ed3d..037acdec07996 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -1,22 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" +#include "fd-util.h" #include "fuzz.h" +#include "hashmap.h" #include "rm-rf.h" -#include "sd-dhcp-server.c" +#include "tests.h" #include "tmpfile-util.h" -/* stub out network so that the server doesn't send */ -ssize_t sendto(int __fd, const void *__buf, size_t __n, int flags, const struct sockaddr *__addr, socklen_t __addr_len) { - return __n; -} - -ssize_t sendmsg(int __fd, const struct msghdr *__message, int flags) { - return 0; -} - static int add_lease(sd_dhcp_server *server, const struct in_addr *server_address, uint8_t i) { _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; int r; @@ -64,43 +61,41 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) { } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - _cleanup_free_ uint8_t *duped = NULL; - _cleanup_close_ int dir_fd = -EBADF; if (size < sizeof(DHCPMessage)) return 0; fuzz_setup_logging(); - assert_se(duped = memdup(data, size)); + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); - dir_fd = mkdtemp_open(NULL, 0, &tmpdir); - assert_se(dir_fd >= 0); + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_new(&event)); - assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_set_lease_file(server, dir_fd, "leases") >= 0); - server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(server->fd >= 0); - assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + server->fd = ASSERT_OK_ERRNO(open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY)); + ASSERT_OK(sd_dhcp_server_set_lease_file(server, dir_fd, "leases")); + ASSERT_OK(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0)); /* add leases to the pool to expose additional code paths */ - assert_se(add_lease(server, &address, 2) >= 0); - assert_se(add_lease(server, &address, 3) >= 0); + ASSERT_OK(add_lease(server, &address, 2)); + ASSERT_OK(add_lease(server, &address, 3)); /* add static leases */ - assert_se(add_static_lease(server, 3) >= 0); - assert_se(add_static_lease(server, 4) >= 0); + ASSERT_OK(add_static_lease(server, 3)); + ASSERT_OK(add_static_lease(server, 4)); + _cleanup_free_ uint8_t *duped = ASSERT_NOT_NULL(memdup(data, size)); (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL); - assert_se(dhcp_server_save_leases(server) >= 0); + ASSERT_OK(dhcp_server_save_leases(server)); server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address); server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id); - assert_se(dhcp_server_load_leases(server) >= 0); + ASSERT_OK(dhcp_server_load_leases(server)); return 0; } From 9179cb31ed9ddf8b5189b63b075816fd0ef222a9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 7 May 2026 11:18:05 +0900 Subject: [PATCH 1602/2155] sd-dhcp-server: coding style fix --- src/libsystemd-network/sd-dhcp-server.c | 4 ++-- src/systemd/sd-dhcp-server.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 48c481809f4e5..a377f9398efeb 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -119,7 +119,7 @@ int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) { return in4_addr_is_set(&server->relay_target); } -static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { +static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { assert(server); sd_dhcp_server_stop(server); @@ -236,7 +236,7 @@ int sd_dhcp_server_detach_event(sd_dhcp_server *server) { return 0; } -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) { +sd_event* sd_dhcp_server_get_event(sd_dhcp_server *server) { assert_return(server, NULL); return server->event; diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 4f2fc688352cf..1fb7d2e9ee60e 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -41,7 +41,7 @@ _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server); int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int64_t priority); int sd_dhcp_server_detach_event(sd_dhcp_server *server); -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server); +sd_event* sd_dhcp_server_get_event(sd_dhcp_server *server); typedef void (*sd_dhcp_server_callback_t)(sd_dhcp_server *server, uint64_t event, void *userdata); From 74f3cc4576b527e85b9f7ee6e53d03469e7e25cb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 10 May 2026 22:28:03 +0900 Subject: [PATCH 1603/2155] sd-dhcp-server-lease: rename dhcp_server_lease_append_json() -> dhcp_server_lease_build_json() It does not append, but build a new JSON variant. --- src/libsystemd-network/sd-dhcp-server-lease.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 0268bbf2c29af..a7d9947c294c0 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -237,7 +237,7 @@ int sd_dhcp_server_set_static_lease( return 0; } -static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, sd_json_variant **ret) { +static int dhcp_server_lease_build_json(sd_dhcp_server_lease *lease, sd_json_variant **ret) { assert(lease); assert(ret); @@ -271,7 +271,7 @@ int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = dhcp_server_lease_append_json(lease, &w); + r = dhcp_server_lease_build_json(lease, &w); if (r < 0) return r; @@ -303,7 +303,7 @@ int dhcp_server_static_leases_append_json(sd_dhcp_server *server, sd_json_varian HASHMAP_FOREACH(lease, server->static_leases_by_client_id) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = dhcp_server_lease_append_json(lease, &w); + r = dhcp_server_lease_build_json(lease, &w); if (r < 0) return r; From 5f1c10e7bccebd283baf5f8aeabb7630bafcba0e Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 12 May 2026 08:47:25 -0700 Subject: [PATCH 1604/2155] json: add JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO() Like JSON_BUILD_PAIR_FINITE_USEC() but also suppresses zero values. Useful for usec_t fields where zero means "disabled" or "not configured" (e.g. KeepAliveTimeUSec, DeferAcceptUSec, RandomizedDelayUSec) as opposed to timeout fields where zero would be unusual and worth showing. --- src/libsystemd/sd-json/json-util.h | 2 ++ src/libsystemd/sd-json/sd-json.c | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 6f06f6fb63500..0b5ea32f87ddd 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -172,6 +172,7 @@ enum { _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, _JSON_BUILD_PAIR_FINITE_USEC, + _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO, _JSON_BUILD_PAIR_STRING_NON_EMPTY, _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY, _JSON_BUILD_PAIR_STRV_NON_EMPTY, @@ -222,6 +223,7 @@ enum { #define JSON_BUILD_PAIR_UNSIGNED_NON_ZERO(name, u) _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, (const char*) { name }, (uint64_t) { u } #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_FINITE_USEC_NON_ZERO(name, u) _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO, (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 } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index fe8b8225c96d3..a7ff526bc8ed8 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -4543,6 +4543,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } + case _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO: case _JSON_BUILD_PAIR_FINITE_USEC: { const char *n; usec_t u; @@ -4555,7 +4556,9 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { n = va_arg(ap, const char *); u = va_arg(ap, usec_t); - if (u != USEC_INFINITY && current->n_suppress == 0) { + if (u != USEC_INFINITY && + (command != _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO || u > 0) && + current->n_suppress == 0) { r = sd_json_variant_new_string(&add, n); if (r < 0) goto finish; From c5406d3e1e494dc2af8a0d0fba39792d20f04b18 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 12 May 2026 08:50:46 -0700 Subject: [PATCH 1605/2155] core: use JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO() for "disabled when zero" usec fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scope: RuntimeMaxUSec, RuntimeRandomizedExtraUSec — zero means no limit Timer: RandomizedDelayUSec, RandomizedOffsetUSec — zero means no delay --- src/core/varlink-scope.c | 4 ++-- src/core/varlink-timer.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/varlink-scope.c b/src/core/varlink-scope.c index d22a2da5701bb..866d57db57e32 100644 --- a/src/core/varlink-scope.c +++ b/src/core/varlink-scope.c @@ -12,8 +12,8 @@ int scope_context_build_json(sd_json_variant **ret, const char *name, void *user return sd_json_buildo( ASSERT_PTR(ret), JSON_BUILD_PAIR_ENUM("OOMPolicy", oom_policy_to_string(s->oom_policy)), - JSON_BUILD_PAIR_FINITE_USEC("RuntimeMaxUSec", s->runtime_max_usec), - JSON_BUILD_PAIR_FINITE_USEC("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeMaxUSec", s->runtime_max_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), JSON_BUILD_PAIR_FINITE_USEC("TimeoutStopUSec", s->timeout_stop_usec)); } diff --git a/src/core/varlink-timer.c b/src/core/varlink-timer.c index e7858fa1d25bf..b2d1c330360b0 100644 --- a/src/core/varlink-timer.c +++ b/src/core/varlink-timer.c @@ -56,8 +56,8 @@ int timer_context_build_json(sd_json_variant **ret, const char *name, void *user SD_JSON_BUILD_PAIR_BOOLEAN("OnClockChange", t->on_clock_change), SD_JSON_BUILD_PAIR_BOOLEAN("OnTimezoneChange", t->on_timezone_change), JSON_BUILD_PAIR_FINITE_USEC("AccuracyUSec", t->accuracy_usec), - JSON_BUILD_PAIR_FINITE_USEC("RandomizedDelayUSec", t->random_delay_usec), - JSON_BUILD_PAIR_FINITE_USEC("RandomizedOffsetUSec", t->random_offset_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RandomizedDelayUSec", t->random_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RandomizedOffsetUSec", t->random_offset_usec), SD_JSON_BUILD_PAIR_BOOLEAN("FixedRandomDelay", t->fixed_random_delay), SD_JSON_BUILD_PAIR_BOOLEAN("Persistent", t->persistent), SD_JSON_BUILD_PAIR_BOOLEAN("WakeSystem", t->wake_system), From cce0a82c1008d30c22d909a60b7c57dcb7f2325d Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 12 May 2026 08:54:59 -0700 Subject: [PATCH 1606/2155] core: implement SocketContext/Runtime for io.systemd.Unit.List Add varlink context and runtime builders for .socket units: Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-socket.c | 116 +++++++++++++++++ src/core/varlink-socket.h | 7 + src/core/varlink-unit.c | 3 + src/shared/varlink-io.systemd.Unit.c | 186 +++++++++++++++++++++++++++ src/shared/varlink-io.systemd.Unit.h | 4 + 6 files changed, 317 insertions(+) create mode 100644 src/core/varlink-socket.c create mode 100644 src/core/varlink-socket.h diff --git a/src/core/meson.build b/src/core/meson.build index eef53be94bc75..32ef1b3131c2f 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -75,6 +75,7 @@ libcore_sources = files( 'varlink-mount.c', 'varlink-path.c', 'varlink-scope.c', + 'varlink-socket.c', 'varlink-swap.c', 'varlink-timer.c', 'varlink-unit.c', diff --git a/src/core/varlink-socket.c b/src/core/varlink-socket.c new file mode 100644 index 0000000000000..91053f9890ffb --- /dev/null +++ b/src/core/varlink-socket.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "ip-protocol-list.h" +#include "json-util.h" +#include "socket.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-socket.h" + +static int socket_listen_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Socket *s = ASSERT_PTR(SOCKET(userdata)); + int r; + + assert(ret); + + LIST_FOREACH(port, p, s->ports) { + _cleanup_free_ char *address = NULL; + + r = socket_port_to_address(p, &address); + if (r < 0) + return log_debug_errno(r, "Failed to call socket_port_to_address(): %m"); + + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_STRING("type", socket_port_type_to_string(p)), + SD_JSON_BUILD_PAIR_STRING("address", address)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int socket_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Socket *s = ASSERT_PTR(SOCKET(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Listen", socket_listen_build_json, s), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketProtocol", ip_protocol_to_name(s->socket_protocol)), + JSON_BUILD_PAIR_ENUM("BindIPv6Only", socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only)), + SD_JSON_BUILD_PAIR_UNSIGNED("Backlog", s->backlog), + JSON_BUILD_PAIR_STRING_NON_EMPTY("BindToDevice", s->bind_to_device), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketUser", s->user), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketGroup", s->group), + SD_JSON_BUILD_PAIR_UNSIGNED("SocketMode", s->socket_mode), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", s->directory_mode), + SD_JSON_BUILD_PAIR_BOOLEAN("Accept", s->accept), + SD_JSON_BUILD_PAIR_BOOLEAN("Writable", s->writable), + SD_JSON_BUILD_PAIR_BOOLEAN("FlushPending", s->flush_pending), + SD_JSON_BUILD_PAIR_UNSIGNED("MaxConnections", s->max_connections), + SD_JSON_BUILD_PAIR_UNSIGNED("MaxConnectionsPerSource", s->max_connections_per_source), + SD_JSON_BUILD_PAIR_BOOLEAN("KeepAlive", s->keep_alive), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("KeepAliveTimeUSec", s->keep_alive_time), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("KeepAliveIntervalUSec", s->keep_alive_interval), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("KeepAliveProbes", s->keep_alive_cnt), + SD_JSON_BUILD_PAIR_BOOLEAN("NoDelay", s->no_delay), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Priority", s->priority), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("DeferAcceptUSec", s->defer_accept), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ReceiveBuffer", s->receive_buffer), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SendBuffer", s->send_buffer), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("IPTOS", s->ip_tos), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("IPTTL", s->ip_ttl), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Mark", s->mark), + SD_JSON_BUILD_PAIR_BOOLEAN("ReusePort", s->reuse_port), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabel", s->smack), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabelIPIn", s->smack_ip_in), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabelIPOut", s->smack_ip_out), + SD_JSON_BUILD_PAIR_BOOLEAN("SELinuxContextFromNet", s->selinux_context_from_net), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("PipeSize", s->pipe_size), + JSON_BUILD_PAIR_INTEGER_NON_ZERO("MessageQueueMaxMessages", s->mq_maxmsg), + JSON_BUILD_PAIR_INTEGER_NON_ZERO("MessageQueueMessageSize", s->mq_msgsize), + SD_JSON_BUILD_PAIR_BOOLEAN("FreeBind", s->free_bind), + SD_JSON_BUILD_PAIR_BOOLEAN("Transparent", s->transparent), + SD_JSON_BUILD_PAIR_BOOLEAN("Broadcast", s->broadcast), + SD_JSON_BUILD_PAIR_BOOLEAN("PassCredentials", s->pass_cred), + SD_JSON_BUILD_PAIR_BOOLEAN("PassPIDFD", s->pass_pidfd), + SD_JSON_BUILD_PAIR_BOOLEAN("PassSecurity", s->pass_sec), + SD_JSON_BUILD_PAIR_BOOLEAN("PassPacketInfo", s->pass_pktinfo), + SD_JSON_BUILD_PAIR_BOOLEAN("AcceptFileDescriptors", s->pass_rights), + JSON_BUILD_PAIR_ENUM("Timestamping", socket_timestamping_to_string(s->timestamping)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("TCPCongestion", s->tcp_congestion), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPre", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPost", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPre", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_STOP_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPost", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_STOP_POST]), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", s->timeout_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("RemoveOnStop", s->remove_on_stop), + JSON_BUILD_PAIR_STRV_NON_EMPTY("Symlinks", s->symlinks), + SD_JSON_BUILD_PAIR_STRING("FileDescriptorName", socket_fdname(s)), + JSON_BUILD_PAIR_RATELIMIT("TriggerLimit", &s->trigger_limit), + JSON_BUILD_PAIR_RATELIMIT("PollLimit", &s->poll_limit), + JSON_BUILD_PAIR_ENUM("DeferTrigger", socket_defer_trigger_to_string(s->defer_trigger)), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("DeferTriggerMaxUSec", s->defer_trigger_max_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("PassFileDescriptorsToExec", s->pass_fds_to_exec)); +} + +int socket_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Socket *s = ASSERT_PTR(SOCKET(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", socket_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("CleanResult", socket_result_to_string(s->clean_result)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NConnections", s->n_connections), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NAccepted", s->n_accepted), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NRefused", s->n_refused), + 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-socket.h b/src/core/varlink-socket.h new file mode 100644 index 0000000000000..911fbed4e94ef --- /dev/null +++ b/src/core/varlink-socket.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int socket_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int socket_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 e73e2d50e6792..f7ef7506493c2 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -31,6 +31,7 @@ #include "varlink-mount.h" #include "varlink-path.h" #include "varlink-scope.h" +#include "varlink-socket.h" #include "varlink-swap.h" #include "varlink-timer.h" #include "varlink-unit.h" @@ -146,6 +147,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void [UNIT_PATH] = path_context_build_json, [UNIT_SCOPE] = scope_context_build_json, [UNIT_SERVICE] = service_context_build_json, + [UNIT_SOCKET] = socket_context_build_json, [UNIT_SWAP] = swap_context_build_json, [UNIT_TIMER] = timer_context_build_json, }; @@ -315,6 +317,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void [UNIT_MOUNT] = mount_runtime_build_json, [UNIT_PATH] = path_runtime_build_json, [UNIT_SCOPE] = scope_runtime_build_json, + [UNIT_SOCKET] = socket_runtime_build_json, [UNIT_SWAP] = swap_runtime_build_json, [UNIT_TIMER] = timer_runtime_build_json, }; diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 883154b2f1951..f708061b1ba75 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1041,6 +1041,181 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Result of scope operation"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ScopeResult, 0)); +/* SocketContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketBindIPv6Only, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(both), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_only)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketTimestamping, + SD_VARLINK_DEFINE_ENUM_VALUE(off), + SD_VARLINK_DEFINE_ENUM_VALUE(us), + SD_VARLINK_DEFINE_ENUM_VALUE(ns)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketDeferTrigger, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(patient)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketListen, + SD_VARLINK_FIELD_COMMENT("Socket type"), + SD_VARLINK_DEFINE_FIELD(type, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Socket address"), + SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ListenStream="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Listen, SocketListen, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketProtocol="), + SD_VARLINK_DEFINE_FIELD(SocketProtocol, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#BindIPv6Only="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(BindIPv6Only, SocketBindIPv6Only, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Backlog="), + SD_VARLINK_DEFINE_FIELD(Backlog, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#BindToDevice="), + SD_VARLINK_DEFINE_FIELD(BindToDevice, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketUser="), + SD_VARLINK_DEFINE_FIELD(SocketUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketUser="), + SD_VARLINK_DEFINE_FIELD(SocketGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketMode="), + SD_VARLINK_DEFINE_FIELD(SocketMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.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.socket.html#Accept="), + SD_VARLINK_DEFINE_FIELD(Accept, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Writable="), + SD_VARLINK_DEFINE_FIELD(Writable, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FlushPending="), + SD_VARLINK_DEFINE_FIELD(FlushPending, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MaxConnections="), + SD_VARLINK_DEFINE_FIELD(MaxConnections, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MaxConnectionsPerSource="), + SD_VARLINK_DEFINE_FIELD(MaxConnectionsPerSource, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAlive="), + SD_VARLINK_DEFINE_FIELD(KeepAlive, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveTimeSec="), + SD_VARLINK_DEFINE_FIELD(KeepAliveTimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveIntervalSec="), + SD_VARLINK_DEFINE_FIELD(KeepAliveIntervalUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveProbes="), + SD_VARLINK_DEFINE_FIELD(KeepAliveProbes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#NoDelay="), + SD_VARLINK_DEFINE_FIELD(NoDelay, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Priority="), + SD_VARLINK_DEFINE_FIELD(Priority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferAcceptSec="), + SD_VARLINK_DEFINE_FIELD(DeferAcceptUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ReceiveBuffer="), + SD_VARLINK_DEFINE_FIELD(ReceiveBuffer, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SendBuffer="), + SD_VARLINK_DEFINE_FIELD(SendBuffer, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#IPTOS="), + SD_VARLINK_DEFINE_FIELD(IPTOS, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#IPTTL="), + SD_VARLINK_DEFINE_FIELD(IPTTL, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Mark="), + SD_VARLINK_DEFINE_FIELD(Mark, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ReusePort="), + SD_VARLINK_DEFINE_FIELD(ReusePort, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabel="), + SD_VARLINK_DEFINE_FIELD(SmackLabel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabelIPIn="), + SD_VARLINK_DEFINE_FIELD(SmackLabelIPIn, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabelIPOut="), + SD_VARLINK_DEFINE_FIELD(SmackLabelIPOut, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SELinuxContextFromNet="), + SD_VARLINK_DEFINE_FIELD(SELinuxContextFromNet, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PipeSize="), + SD_VARLINK_DEFINE_FIELD(PipeSize, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MessageQueueMaxMessages="), + SD_VARLINK_DEFINE_FIELD(MessageQueueMaxMessages, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MessageQueueMessageSize="), + SD_VARLINK_DEFINE_FIELD(MessageQueueMessageSize, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FreeBind="), + SD_VARLINK_DEFINE_FIELD(FreeBind, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Transparent="), + SD_VARLINK_DEFINE_FIELD(Transparent, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Broadcast="), + SD_VARLINK_DEFINE_FIELD(Broadcast, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassCredentials="), + SD_VARLINK_DEFINE_FIELD(PassCredentials, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassPIDFD="), + SD_VARLINK_DEFINE_FIELD(PassPIDFD, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassSecurity="), + SD_VARLINK_DEFINE_FIELD(PassSecurity, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassPacketInfo="), + SD_VARLINK_DEFINE_FIELD(PassPacketInfo, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#AcceptFileDescriptors="), + SD_VARLINK_DEFINE_FIELD(AcceptFileDescriptors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Timestamping="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamping, SocketTimestamping, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TCPCongestion="), + SD_VARLINK_DEFINE_FIELD(TCPCongestion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStartPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStartPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStopPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStopPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#RemoveOnStop="), + SD_VARLINK_DEFINE_FIELD(RemoveOnStop, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Symlinks="), + SD_VARLINK_DEFINE_FIELD(Symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FileDescriptorName="), + SD_VARLINK_DEFINE_FIELD(FileDescriptorName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TriggerLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TriggerLimit, RateLimit, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PollLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PollLimit, RateLimit, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferTrigger="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DeferTrigger, SocketDeferTrigger, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferTriggerMaxSec="), + SD_VARLINK_DEFINE_FIELD(DeferTriggerMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassFileDescriptorsToExec="), + SD_VARLINK_DEFINE_FIELD(PassFileDescriptorsToExec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketResult, + 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(trigger_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(service_start_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current socket control process"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of socket operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, SocketResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, SocketResult, 0), + SD_VARLINK_FIELD_COMMENT("Number of current connections"), + SD_VARLINK_DEFINE_FIELD(NConnections, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of accepted connections"), + SD_VARLINK_DEFINE_FIELD(NAccepted, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of refused connections"), + SD_VARLINK_DEFINE_FIELD(NRefused, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + 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)); + /* SwapContext * https://www.freedesktop.org/software/systemd/man/latest/systemd.swap.html */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -1342,6 +1517,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The scope context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The socket context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Socket, SocketContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The swap context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The timer context of the unit"), @@ -1524,6 +1701,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The socket runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Socket, SocketRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The timer runtime of the unit"), @@ -1698,6 +1877,13 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ScopeContext, &vl_type_ScopeResult, &vl_type_ScopeRuntime, + &vl_type_SocketBindIPv6Only, + &vl_type_SocketTimestamping, + &vl_type_SocketDeferTrigger, + &vl_type_SocketListen, + &vl_type_SocketContext, + &vl_type_SocketResult, + &vl_type_SocketRuntime, &vl_type_SwapContext, &vl_type_SwapResult, &vl_type_SwapRuntime, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 128170eaf4079..a5e2e33df22b3 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -33,6 +33,10 @@ extern const sd_varlink_symbol vl_type_MountResult; extern const sd_varlink_symbol vl_type_PathType; extern const sd_varlink_symbol vl_type_PathResult; extern const sd_varlink_symbol vl_type_ScopeResult; +extern const sd_varlink_symbol vl_type_SocketBindIPv6Only; +extern const sd_varlink_symbol vl_type_SocketTimestamping; +extern const sd_varlink_symbol vl_type_SocketDeferTrigger; +extern const sd_varlink_symbol vl_type_SocketResult; extern const sd_varlink_symbol vl_type_SwapResult; extern const sd_varlink_symbol vl_type_TimerBase; extern const sd_varlink_symbol vl_type_TimerResult; From cf6e46c863af63b8eef18eae0258adb8e7f55cfd Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 12 May 2026 08:55:08 -0700 Subject: [PATCH 1607/2155] test: add SocketContext/Runtime enum and integration tests Co-developed-by: Claude Opus 4.6 --- src/test/test-varlink-idl-unit.c | 9 +++++++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index e03f5edff5aa2..7859f2d735965 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -10,6 +10,7 @@ #include "process-util.h" #include "scope.h" #include "service.h" +#include "socket.h" #include "swap.h" #include "tests.h" #include "test-varlink-idl-util.h" @@ -76,6 +77,14 @@ TEST(unit_enums_idl) { /* ScopeRuntime enums */ TEST_IDL_ENUM(ScopeResult, scope_result, vl_type_ScopeResult); + /* SocketContext enums */ + TEST_IDL_ENUM(SocketAddressBindIPv6Only, socket_address_bind_ipv6_only, vl_type_SocketBindIPv6Only); + TEST_IDL_ENUM(SocketTimestamping, socket_timestamping, vl_type_SocketTimestamping); + TEST_IDL_ENUM(SocketDeferTrigger, socket_defer_trigger, vl_type_SocketDeferTrigger); + + /* SocketRuntime enums */ + TEST_IDL_ENUM(SocketResult, socket_result, vl_type_SocketResult); + /* SwapRuntime enums */ TEST_IDL_ENUM(SwapResult, swap_result, vl_type_SwapResult); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 3e78cf34e9e48..de56b37c3153e 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -258,6 +258,12 @@ test -n "$scope_id" scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' +# test for SocketContext/Runtime +socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$socket_id" +socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' # test for SwapContext/Runtime (swap units may not be present on all systems) swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) if test -n "$swap_id"; then From ee2c2b1b89fd4bddf6b91f18b5d0bc4524986bb4 Mon Sep 17 00:00:00 2001 From: Bone NI Date: Tue, 12 May 2026 06:16:55 +0000 Subject: [PATCH 1608/2155] po: Translated using Weblate (Lao) Currently translated at 100.0% (266 of 266 strings) po: Added translation using Weblate (Lao) Co-authored-by: Bone NI Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/lo/ Translation: systemd/main --- po/LINGUAS | 1 + po/lo.po | 1238 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1239 insertions(+) create mode 100644 po/lo.po diff --git a/po/LINGUAS b/po/LINGUAS index 283750d34ef3b..44c233fc5c58c 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -48,3 +48,4 @@ ug uk zh_CN zh_TW +lo diff --git a/po/lo.po b/po/lo.po new file mode 100644 index 0000000000000..d63eff97546a6 --- /dev/null +++ b/po/lo.po @@ -0,0 +1,1238 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the systemd package. +# Bone NI , 2026. +msgid "" +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-05-12 06:16+0000\n" +"Last-Translator: Bone NI \n" +"Language-Team: Lao \n" +"Language: lo\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: Weblate 5.17.1\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 "ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພບໍ່ຖືກຕ້ອງສຳລັບຜູ້ໃຊ້ %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 "ຂໍອະໄພ, ລອງປ້ອນ PIN ຂອງໂທເຄັນຄວາມປອດໄພໃໝ່: " + +#: src/home/pam_systemd_home.c:493 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "" +"ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s ບໍ່ຖືກຕ້ອງ (ເຫຼືອການພະຍາຍາມອີກບໍ່ເທົ່າໃດຄັ້ງ!)" + +#: src/home/pam_systemd_home.c:512 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "" +"ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s ບໍ່ຖືກຕ້ອງ (ເຫຼືອການພະຍາຍາມອີກພຽງຄັ້ງດຽວ!)" + +#: 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 "ຕັ້ງຊື່ໂຮສ (hostname)" + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າທ້ອງຖິ່ນຂອງລະບົບ." + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມເປີດປິດ (power key) ຂອງລະບົບ" + +#: 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 "" +"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມພັກເຄື່ອງ (suspend key) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມຈຳສີນ (hibernate key) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການສະວິດຝາປິດ (lid switch) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ເຊື່ອມຕໍ່ອຸປະກອນເຂົ້າກັບບ່ອນນັ່ງ (seats)" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອເຊື່ອມຕໍ່ອຸປະກອນເຂົ້າກັບບ່ອນນັ່ງ." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "ລ້າງການເຊື່ອມຕໍ່ອຸປະກອນກັບບ່ອນນັ່ງ" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າການເຊື່ອມຕໍ່ອຸປະກອນກັບບ່ອນນັ່ງຄືນໃໝ່." + +#: 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 "ຢຸດລະບົບ (Halt)" + +#: 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 "ພັກລະບົບ (Suspend)" + +#: 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 "ຈຳສີນລະບົບ (Hibernate)" + +#: 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 "ຈັດການເຊສຊັນທີ່ເປີດຢູ່, ຜູ້ໃຊ້ ແລະ ບ່ອນນັ່ງ" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຈັດການເຊສຊັນທີ່ເປີດຢູ່, ຜູ້ໃຊ້ ແລະ ບ່ອນນັ່ງ." + +#: 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 message)" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຂໍ້ຄວາມແຈ້ງເຕືອນລະບົບ." + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອປ່ຽນເທີມີນໍສະເໝືອນ (virtual terminal)." + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຂໍໃຊ້ເຊວໃນຄອນເທນເນີພາຍໃນເຄື່ອງ." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "ຂໍໃຊ້ເຊວໃນໂຮສພາຍໃນເຄື່ອງ" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຂໍໃຊ້ເຊວໃນໂຮສພາຍໃນເຄື່ອງ." + +#: 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 "ຕັ້ງຄ່າເສັ້ນທາງຫຼັກ (default route)" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າເສັ້ນທາງຫຼັກ." + +#: 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." + +#: 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 "ຈັດການການເຊື່ອມຕໍ່ເຄືອຂ່າຍ (network links)" + +#: 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)" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອກວດສອບອິເມຈບໍລິການແບບພົກພາ." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "ເຊື່ອມຕໍ່ ຫຼື ຕັດການເຊື່ອມຕໍ່ອິເມຈບໍລິການແບບພົກພາ" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "" +"ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອເຊື່ອມຕໍ່ ຫຼື ຕັດການເຊື່ອມຕໍ່ອິເມຈບໍລິການແບບພົກພາ." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "ລຶບ ຫຼື ແກ້ໄຂອິເມຈບໍລິການແບບພົກພາ" + +#: src/portable/org.freedesktop.portable1.policy:35 +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 +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 "ຄືນຄ່າການຕັ້ງຄ່າການແປງຊື່ (name resolution)" + +#: 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 "ຕິດຕາມຜົນການສອບຖາມ (query results)" + +#: 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 "ບັນທຶກແຄຊ໌ (Dump cache)" + +#: 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 "" +"ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງສະຖານະ \"ຫຼົ້ມເຫຼວ\" (failed) ຂອງ '$(unit)' ຄືນໃໝ່." + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄຸນສົມບັດ (properties) ຂອງ '$(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 "" +"ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອແຊ່ແຂງ (freeze) ຫຼື ປົດແຊ່ແຂງ (thaw) ໂພຣເຊສຂອງໜ່ວຍງານ '$(unit)'." From 3ac8bd0e97c4e3991c8d79df60549a4aee0f88ae Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:23 +0100 Subject: [PATCH 1609/2155] repart: Reuse the backing fd for fdisk Because fdisk_assign_device tries to open block devices with O_EXCL, when it does it blocks cryptsetup from using partition block devices for the same disk. Since we already have a file descriptor for the device, we can just share it and use fdisk_assign_device_by_fd instead. This requires at least libfdisk 2.35 (part of util-linux) which was released in 2020. --- README | 2 +- meson.build | 2 +- src/repart/repart.c | 54 ++++++++++++++++++++++++++++++----------- src/shared/fdisk-util.c | 2 ++ src/shared/fdisk-util.h | 1 + 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/README b/README index 51a78bd2a1d00..30bb9ee5116d8 100644 --- a/README +++ b/README @@ -219,7 +219,7 @@ REQUIREMENTS: libacl (optional) libbpf >= 0.1.0 (optional), >= 1.4.0 is required for using GCC as a bpf compiler - libfdisk >= 2.32 (from util-linux) (optional) + libfdisk >= 2.35 (from util-linux) (optional) libselinux >= 2.1.9 (optional) libapparmor >= 2.13 (optional) libxenctrl >= 4.9 (optional) diff --git a/meson.build b/meson.build index d5405995db22c..2e78b359e6ea6 100644 --- a/meson.build +++ b/meson.build @@ -1076,7 +1076,7 @@ conf.set10('HAVE_LIBMOUNT', have) libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true) libfdisk = dependency('fdisk', - version : '>= 2.32', + version : '>= 2.35', required : get_option('fdisk')) libfdisk_cflags = libfdisk.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBFDISK', libfdisk.found()) diff --git a/src/repart/repart.c b/src/repart/repart.c index 168092d43a8eb..8246b87045c40 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -544,6 +544,7 @@ struct Context { uint64_t start, end, total; struct fdisk_context *fdisk_context; + int fdisk_context_fd; uint64_t sector_size, grain_size, default_fs_sector_size; sd_id128_t seed; @@ -948,6 +949,7 @@ static Context* context_new( .empty = empty, .dry_run = dry_run, .backing_fd = -EBADF, + .fdisk_context_fd = -EBADF, }; return context; @@ -977,6 +979,7 @@ static Context* context_free(Context *context) { if (context->fdisk_context) sym_fdisk_unref_context(context->fdisk_context); + safe_close(context->fdisk_context_fd); safe_close(context->backing_fd); if (context->node_is_our_file) @@ -3162,16 +3165,17 @@ static int find_verity_sibling(Context *context, Partition *p, VerityMode mode, return 0; } -static int context_open_and_lock_backing_fd(const char *node, int operation, int *backing_fd) { +static int context_open_and_lock_backing_fd(const char *node, int operation, int mode, int *backing_fd) { _cleanup_close_ int fd = -EBADF; assert(node); assert(backing_fd); + assert(IN_SET(mode, O_RDONLY, O_RDWR)); if (*backing_fd >= 0) return 0; - fd = open(node, O_RDONLY|O_CLOEXEC); + fd = open(node, mode|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open device '%s': %m", node); @@ -3264,7 +3268,7 @@ static int context_copy_from_one(Context *context, const char *src) { assert(src); - r = context_open_and_lock_backing_fd(src, LOCK_SH, &fd); + r = context_open_and_lock_backing_fd(src, LOCK_SH, O_RDONLY, &fd); if (r < 0) return r; @@ -3674,7 +3678,12 @@ static int context_load_fallback_metrics(Context *context) { return 1; /* Starting from scratch */ } +static int context_open_mode(Context *context) { + return ASSERT_PTR(context)->dry_run ? O_RDONLY : O_RDWR; +} + static int context_load_partition_table(Context *context) { + _cleanup_close_ int fd = -EBADF; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL; uint64_t left_boundary = UINT64_MAX, first_lba, last_lba, nsectors; @@ -3688,6 +3697,7 @@ static int context_load_partition_table(Context *context) { assert(context); assert(context->node); assert(!context->fdisk_context); + assert(context->fdisk_context_fd < 0); assert(!context->free_areas); assert(context->start == UINT64_MAX); assert(context->end == UINT64_MAX); @@ -3709,6 +3719,7 @@ static int context_load_partition_table(Context *context) { r = context_open_and_lock_backing_fd( context->node, context->dry_run ? LOCK_SH : LOCK_EX, + context_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -3739,11 +3750,15 @@ static int context_load_partition_table(Context *context) { 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 = sym_fdisk_assign_device( + if (context->backing_fd < 0) { + fd = open(context->node, context_open_mode(context)|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open backing node '%s': %m", context->node); + } + r = sym_fdisk_assign_device_by_fd( c, - context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, + context->backing_fd >= 0 ? context->backing_fd : fd, + context->node, context->dry_run); if (r == -EINVAL && arg_size_auto) { struct stat st; @@ -3752,7 +3767,7 @@ static int context_load_partition_table(Context *context) { * it if automatic sizing is requested. */ if (context->backing_fd < 0) - r = stat(context->node, &st); + r = fstat(fd, &st); else r = fstat(context->backing_fd, &st); if (r < 0) @@ -3775,6 +3790,7 @@ static int context_load_partition_table(Context *context) { /* 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(sym_fdisk_get_devfd(c)), context->dry_run ? LOCK_SH : LOCK_EX, + context_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -4047,6 +4063,7 @@ static int context_load_partition_table(Context *context) { context->default_fs_sector_size = fs_secsz; context->grain_size = grainsz; context->fdisk_context = TAKE_PTR(c); + context->fdisk_context_fd = TAKE_FD(fd); return from_scratch; } @@ -4103,6 +4120,7 @@ static void context_unload_partition_table(Context *context) { sym_fdisk_unref_context(context->fdisk_context); context->fdisk_context = NULL; } + context->fdisk_context_fd = safe_close(context->fdisk_context_fd); context_free_free_areas(context); } @@ -10433,13 +10451,21 @@ static int acquire_root_devno( assert(ret); assert(ret_fd); - fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path); + fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, (mode & ~O_ACCMODE_STRICT) | O_RDONLY, &found_path); if (fd < 0) return fd; if (fstat(fd, &st) < 0) return -errno; + if ((S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) && (mode & O_ACCMODE_STRICT) != O_RDONLY) { + _cleanup_close_ int new_fd = fd_reopen(fd, mode); + if (new_fd < 0) + return new_fd; + + close_and_replace(fd, new_fd); + } + if (S_ISREG(st.st_mode)) { *ret = TAKE_PTR(found_path); *ret_fd = TAKE_FD(fd); @@ -10499,7 +10525,7 @@ static int acquire_root_devno( static int find_root(Context *context) { _cleanup_free_ char *device = NULL; - int r; + int r, open_flags = O_CLOEXEC|context_open_mode(context); assert(context); @@ -10515,7 +10541,7 @@ static int find_root(Context *context) { if (!s) return log_oom(); - fd = xopenat_full(AT_FDCWD, arg_node, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, XO_NOCOW, 0666); + fd = xopenat_full(AT_FDCWD, arg_node, open_flags|O_CREAT|O_EXCL|O_NOFOLLOW, XO_NOCOW, 0666); if (fd < 0) return log_error_errno(fd, "Failed to create '%s': %m", arg_node); @@ -10527,7 +10553,7 @@ static int find_root(Context *context) { /* Note that we don't specify a root argument here: if the user explicitly configured a node * we'll take it relative to the host, not the image */ - r = acquire_root_devno(arg_node, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(arg_node, NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, arg_node); if (r < 0) @@ -10549,7 +10575,7 @@ static int find_root(Context *context) { FOREACH_STRING(p, "/", "/usr") { - r = acquire_root_devno(p, arg_root, O_RDONLY|O_DIRECTORY|O_CLOEXEC, &context->node, + r = acquire_root_devno(p, arg_root, open_flags|O_DIRECTORY, &context->node, &context->backing_fd); if (r < 0) { if (r == -EUCLEAN) @@ -10562,7 +10588,7 @@ static int find_root(Context *context) { } else if (r < 0) return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m"); else { - r = acquire_root_devno(device, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(device, NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, device); if (r < 0) diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 5eaa91160acd4..00f1bfeaea246 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -23,6 +23,7 @@ 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_assign_device_by_fd) = NULL; DLSYM_PROTOTYPE(fdisk_create_disklabel) = NULL; DLSYM_PROTOTYPE(fdisk_delete_partition) = NULL; DLSYM_PROTOTYPE(fdisk_get_devfd) = NULL; @@ -97,6 +98,7 @@ int dlopen_fdisk(int log_level) { DLSYM_ARG(fdisk_ask_get_type), DLSYM_ARG(fdisk_ask_string_set_result), DLSYM_ARG(fdisk_assign_device), + DLSYM_ARG(fdisk_assign_device_by_fd), DLSYM_ARG(fdisk_create_disklabel), DLSYM_ARG(fdisk_delete_partition), DLSYM_ARG(fdisk_get_devfd), diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index bdac11c5071aa..66620c9c1cf50 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -14,6 +14,7 @@ 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_assign_device_by_fd); extern DLSYM_PROTOTYPE(fdisk_create_disklabel); extern DLSYM_PROTOTYPE(fdisk_delete_partition); extern DLSYM_PROTOTYPE(fdisk_get_devfd); From 0ef08242016dd1d6cd2ed79c83460e8853c29d31 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:34 +0100 Subject: [PATCH 1610/2155] repart: Use blkpg partitions instead of loop devices when possible We will want to allow future features to keep some devices mounted or active. So in order to avoid leaving a mess of many loop devices, we can just already use the partition block device already. --- src/repart/repart.c | 138 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 8246b87045c40..854ca644f7573 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -4944,8 +4944,30 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit return NULL; } +/* BlockPartition represents partitions that have been created with BLKPG */ +typedef struct { + int fd; + int whole_fd; /* not owned */ + int nr; + uint64_t offset; + uint64_t size; + char *node; +} BlockPartition; + +static BlockPartition* block_partition_free(BlockPartition *p) { + if (!p) + return NULL; + + safe_close(p->fd); + free(p->node); + return mfree(p); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BlockPartition*, block_partition_free); + typedef struct { LoopDevice *loop; + BlockPartition *block_partition; int fd; char *path; int whole_fd; @@ -4954,7 +4976,7 @@ typedef struct { static int partition_target_fd(PartitionTarget *t) { assert(t); - assert(t->loop || t->fd >= 0 || t->whole_fd >= 0); + assert(t->loop || t->fd >= 0 || t->whole_fd >= 0 || t->block_partition); if (t->decrypted) return t->decrypted->fd; @@ -4962,6 +4984,9 @@ static int partition_target_fd(PartitionTarget *t) { if (t->loop) return t->loop->fd; + if (t->block_partition) + return t->block_partition->fd; + if (t->fd >= 0) return t->fd; @@ -4970,7 +4995,7 @@ static int partition_target_fd(PartitionTarget *t) { static const char* partition_target_path(PartitionTarget *t) { assert(t); - assert(t->loop || t->path); + assert(t->loop || t->path || t->block_partition); if (t->decrypted) return t->decrypted->volume; @@ -4978,6 +5003,9 @@ static const char* partition_target_path(PartitionTarget *t) { if (t->loop) return t->loop->node; + if (t->block_partition) + return t->block_partition->node; + return t->path; } @@ -4989,6 +5017,7 @@ static PartitionTarget* partition_target_free(PartitionTarget *t) { loop_device_unref(t->loop); safe_close(t->fd); unlink_and_free(t->path); + block_partition_free(t->block_partition); return mfree(t); } @@ -5078,20 +5107,79 @@ static int partition_target_prepare( return 0; } - /* Loopback block devices are not only useful to turn regular files into block devices, but - * also to cut out sections of block devices into new block devices. */ - if (arg_offline <= 0) { - r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && loop_device_error_is_fatal(p, r)) - return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - if (r >= 0) { - t->loop = TAKE_PTR(d); + r = blockdev_partscan_enabled_fd(whole_fd); + if (r > 0) { + _cleanup_close_ int dev_fd = -EBADF; + _cleanup_free_ char *part_node = NULL; + _cleanup_(block_partition_freep) BlockPartition *b = NULL; + int nr; + + /* blkpg takes int for partition numbers */ + if (p->partno >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition number %" PRIu64 " is too large for blkpg.", p->partno); + nr = p->partno + 1; + + part_node = sym_fdisk_partname(context->node, p->partno + 1); + if (!part_node) + return log_oom(); + + /* There is no corresponding call to block_device_remove_partition because we want to + * keep them alive if we succeed, and the rescan will remove them if possible if + * there is an error before writing the partition table. + */ + r = block_device_add_partition(whole_fd, part_node, nr, p->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); + + dev_fd = open(part_node, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (dev_fd < 0) { + r = -errno; + int q = block_device_remove_partition(whole_fd, part_node, nr); + if (q < 0) + log_warning_errno(q, "Error while removing block device partition '%s', ignoring: %m", part_node); + return log_error_errno(r, "Failed to open new partition '%s': %m", part_node); + } + + /* No need to flock for udev, the whole disk fd is already locked. */ + + b = new(BlockPartition, 1); + if (!b) { + r = block_device_remove_partition(whole_fd, part_node, nr); + if (r < 0) + log_warning_errno(r, "Error while removing block device partition '%s', ignoring: %m", part_node); + + return log_oom(); + } + + *b = (BlockPartition) { + .fd = TAKE_FD(dev_fd), + .whole_fd = whole_fd, + .nr = nr, + .offset = p->offset, + .size = size, + .node = TAKE_PTR(part_node), + }; + + t->block_partition = TAKE_PTR(b); *ret = TAKE_PTR(t); return 0; - } + } else { + if (!IN_SET(r, 0, -ENOTBLK)) + log_warning_errno(r, "Could not detect whether the device can be partitioned, assuming it cannot be: %m"); + /* Loopback block devices are not only useful to turn regular files into block devices, but + * also to cut out sections of block devices into new block devices. */ + r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, /* loop_flags= */ 0, LOCK_EX, &d); + if (r < 0 && loop_device_error_is_fatal(p, r)) + return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); + if (r >= 0) { + t->loop = TAKE_PTR(d); + *ret = TAKE_PTR(t); + return 0; + } - log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + } } /* If we can't allocate a loop device, let's write to a regular file that we copy into the final @@ -5118,6 +5206,11 @@ static int partition_target_grow(PartitionTarget *t, uint64_t size) { r = loop_device_refresh_size(t->loop, UINT64_MAX, size); if (r < 0) return log_error_errno(r, "Failed to refresh loopback device size: %m"); + } else if (t->block_partition) { + r = block_device_resize_partition(t->block_partition->whole_fd, t->block_partition->nr, t->block_partition->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to resize partition %d: %m", t->block_partition->nr); + t->block_partition->size = size; } else if (t->fd >= 0) { if (ftruncate(t->fd, size) < 0) return log_error_errno(errno, "Failed to grow '%s' to %s by truncation: %m", @@ -5145,6 +5238,9 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget r = loop_device_sync(t->loop); if (r < 0) return log_error_errno(r, "Failed to sync loopback device: %m"); + } else if (t->block_partition) { + if (fsync(t->block_partition->fd) < 0) + return log_error_errno(errno, "Failed to sync blkpg partition: %m"); } else if (t->fd >= 0) { struct stat st; @@ -6123,7 +6219,7 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { r = partition_encrypt(context, p, t, /* offline= */ false); if (r < 0) return r; @@ -6147,7 +6243,7 @@ static int context_copy_blocks(Context *context) { log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); - if (p->encrypt != ENCRYPT_OFF && !t->loop) { + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { r = partition_encrypt(context, p, t, /* offline= */ true); if (r < 0) return r; @@ -7129,7 +7225,7 @@ static int context_mkfs(Context *context) { if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; @@ -7146,10 +7242,10 @@ static int context_mkfs(Context *context) { * we need to set up the final directory tree beforehand. */ if (partition_needs_populate(p) && - (!t->loop || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { + ((!t->loop && !t->block_partition) || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), - "Loop device access is required to populate %s filesystems.", + "Loop device or block partition access is required to populate %s filesystems.", p->format); r = partition_populate_directory(context, p, &root); @@ -7179,7 +7275,7 @@ static int context_mkfs(Context *context) { * on a loop device, so open the file again to make sure our file descriptor points to actual * new file. */ - if (t->fd >= 0 && t->path && !t->loop) { + if (t->fd >= 0 && t->path && !t->loop && !t->block_partition) { safe_close(t->fd); t->fd = open(t->path, O_RDWR|O_CLOEXEC); if (t->fd < 0) @@ -7188,16 +7284,16 @@ static int context_mkfs(Context *context) { log_info("Successfully formatted future partition %" PRIu64 ".", p->partno); - /* If we're writing to a loop device, we can now mount the empty filesystem and populate it. */ + /* If we're writing to a loop device or BLKPG partition, we can now mount the empty filesystem and populate it. */ if (partition_needs_populate(p) && !root) { - assert(t->loop); + assert(t->loop || t->block_partition); r = partition_populate_filesystem(context, p, partition_target_path(t)); if (r < 0) return r; } - if (p->encrypt != ENCRYPT_OFF && !t->loop) { + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; From d0d00297a43e7d824fb68f2cad702ba366abeb59 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:39 +0100 Subject: [PATCH 1611/2155] repart: Rescan disk on failure if we create blkpg partitions on the fly Since we did not write the partition table, then the created partitions should get removed on error. --- src/repart/repart.c | 68 +++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 854ca644f7573..0e5ba58af2a69 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -568,6 +568,8 @@ struct Context { bool defer_partitions_factory_reset; sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */ + + bool needs_rescan; }; static const char *empty_mode_table[_EMPTY_MODE_MAX] = { @@ -5132,6 +5134,8 @@ static int partition_target_prepare( if (r < 0) return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); + context->needs_rescan = true; + dev_fd = open(part_node, O_RDWR|O_CLOEXEC|O_NOCTTY); if (dev_fd < 0) { r = -errno; @@ -8244,9 +8248,34 @@ static int context_find_esp_offset(Context *context, uint64_t *ret) { return 0; } +static int context_partscan(Context *context) { + int capable, r; + + assert(context); + + context->needs_rescan = false; + + 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) + return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); + else if (capable > 0) { + log_info("Informing kernel about changed partitions..."); + (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); + + 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 + log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + + return 0; +} + static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; - int capable, r; + int r; assert(context); @@ -8292,46 +8321,43 @@ static int context_write_partition_table(Context *context) { * gaps between partitions, just to be sure. */ r = context_wipe_and_discard(context); if (r < 0) - return r; + goto error; r = context_copy_blocks(context); if (r < 0) - return r; + goto error; r = context_mkfs(context); if (r < 0) - return r; + goto error; r = context_mangle_partitions(context); if (r < 0) - return r; + goto error; log_info("Writing new partition table."); (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); 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(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) - return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); - else if (capable > 0) { - log_info("Informing kernel about changed partitions..."); - (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); + if (r < 0) { + r = log_error_errno(r, "Failed to write partition table: %m"); + goto error; + } - 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 - log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + r = context_partscan(context); + if (r < 0) + return r; log_info("Partition table written."); return 0; + + error: + if (context->needs_rescan) + (void) context_partscan(context); + + return r; } static int context_write_eltorito(Context *context) { From 77b15343bcd861b866007035895e9a51bd0a0063 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:45 +0100 Subject: [PATCH 1612/2155] repart: Allow keeping luks2 volumes opened --- src/repart/repart.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 0e5ba58af2a69..a2958f3212f12 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -4920,6 +4920,7 @@ typedef struct DecryptedPartitionTarget { int fd; char *dm_name; char *volume; + bool keep; struct crypt_device *device; } DecryptedPartitionTarget; @@ -4932,11 +4933,14 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit safe_close(t->fd); - /* udev or so might access out block device in the background while we are done. Let's hence - * force detach the volume. We sync'ed before, hence this should be safe. */ - r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); - if (r < 0) - log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + if (!t->keep) { + /* udev or so might access our block device in the background while we are done. Let's hence + * force detach the volume. We sync'ed before, hence this should be safe. */ + r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); + if (r < 0) + log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + } else + log_debug("Keeping encrypted device '%s' open.", t->dm_name); sym_crypt_free(t->device); free(t->dm_name); From c07fcbb50747cf1b37e3bf7d86e55d3734da9455 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:52 +0100 Subject: [PATCH 1613/2155] repart: Add BlockDeviceReplace= BlockDeviceReplace=/mntpnt will move the btrfs filesystem from mount point to the partition created. After moving, it will resize to take the whole partition. This is useful for OS installers that move a live system into a disk and do not require a reboot. --- man/repart.d.xml | 18 +++ src/repart/repart.c | 261 +++++++++++++++++++++++++++++++++++++--- src/shared/btrfs-util.c | 77 +++++++++++- src/shared/btrfs-util.h | 8 +- 4 files changed, 342 insertions(+), 22 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 028929ac9e842..598cdc2310dcb 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -652,6 +652,24 @@ + + BlockDeviceReplace= + + Takes a path to a mountpoint. Its filesystem's backing device will be replaced + with the newly created partition. This works only for btrfs filesystems. This option is ignored for already existing partitions. + + If after moving the filesystem, an error happens before the writing of the partition table is + finished, there will be an attempt to move the filesystem back to its original device. But if that + attempt also fails, the filesystem might be living on a partition that does not exist in the + partition table and will be lost on reboot. This feature is intended to save initially volatile + mounted filesystems into the new disk with no important data, like a live boot system. They might + need to be recreated after a failure writing the partition table. + + This option is not compatible with . + + + + Encrypt= diff --git a/src/repart/repart.c b/src/repart/repart.c index a2958f3212f12..1620cc3530b5a 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -250,6 +251,7 @@ typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, PROGRESS_LOADING_TABLE, PROGRESS_OPENING_COPY_BLOCK_SOURCES, + PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, PROGRESS_ACQUIRING_PARTITION_LABELS, PROGRESS_MINIMIZING, PROGRESS_PLACING, @@ -260,6 +262,7 @@ typedef enum ProgressPhase { PROGRESS_ADJUSTING_PARTITION, PROGRESS_WRITING_TABLE, PROGRESS_REREADING_TABLE, + PROGRESS_REPLACING_DEVICE, _PROGRESS_PHASE_MAX, _PROGRESS_PHASE_INVALID = -EINVAL, } ProgressPhase; @@ -423,6 +426,27 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(subvolume_hash_ops, char, path_has typedef struct Context Context; +typedef struct { + uint64_t devid; + int mountpoint_fd; + int source_fd; + char *source_path; + uint64_t source_size; + bool done; +} BtrfsReplacement; + +static BtrfsReplacement* btrfs_replacement_free(BtrfsReplacement *b) { + if (!b) + return NULL; + + safe_close(b->mountpoint_fd); + safe_close(b->source_fd); + free(b->source_path); + return mfree(b); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BtrfsReplacement*, btrfs_replacement_free); + typedef struct Partition { Context *context; @@ -468,6 +492,8 @@ typedef struct Partition { uint64_t copy_blocks_done; char *format; + char *block_device_replace; + BtrfsReplacement *btrfs_replaced; char **exclude_files_source; char **exclude_files_target; char **make_directories; @@ -630,19 +656,21 @@ static const char *minimize_mode_table[_MINIMIZE_MODE_MAX] = { }; static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { - [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", - [PROGRESS_LOADING_TABLE] = "loading-table", - [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", - [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", - [PROGRESS_MINIMIZING] = "minimizing", - [PROGRESS_PLACING] = "placing", - [PROGRESS_WIPING_DISK] = "wiping-disk", - [PROGRESS_WIPING_PARTITION] = "wiping-partition", - [PROGRESS_COPYING_PARTITION] = "copying-partition", - [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", - [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", - [PROGRESS_WRITING_TABLE] = "writing-table", - [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", + [PROGRESS_LOADING_TABLE] = "loading-table", + [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", + [PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES] = "opening-block-device-replace-sources", + [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", + [PROGRESS_MINIMIZING] = "minimizing", + [PROGRESS_PLACING] = "placing", + [PROGRESS_WIPING_DISK] = "wiping-disk", + [PROGRESS_WIPING_PARTITION] = "wiping-partition", + [PROGRESS_COPYING_PARTITION] = "copying-partition", + [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", + [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", + [PROGRESS_WRITING_TABLE] = "writing-table", + [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_REPLACING_DEVICE] = "replacing-device", }; static uint64_t determine_grain_size(uint64_t sector_size) { @@ -807,6 +835,8 @@ static Partition* partition_free(Partition *p) { safe_close(p->copy_blocks_fd); free(p->format); + free(p->block_device_replace); + btrfs_replacement_free(p->btrfs_replaced); strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); @@ -851,6 +881,8 @@ static void partition_foreignize(Partition *p) { p->copy_blocks_root = NULL; p->format = mfree(p->format); + p->block_device_replace = mfree(p->block_device_replace); + p->btrfs_replaced = btrfs_replacement_free(p->btrfs_replaced); p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); @@ -1161,6 +1193,8 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { if (p->copy_blocks_size != UINT64_MAX) assert_se(INC_SAFE(&d, round_up_size(p->copy_blocks_size, context->grain_size))); + else if (p->btrfs_replaced) + assert_se(INC_SAFE(&d, round_up_size(p->btrfs_replaced->source_size, context->grain_size))); else if (p->format || p->encrypt != ENCRYPT_OFF) { uint64_t f; @@ -2924,6 +2958,7 @@ static int partition_read_definition( { "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 }, + { "Partition", "BlockDeviceReplace", config_parse_path, 0, &p->block_device_replace }, {} }; _cleanup_free_ char *filename = NULL; @@ -2974,6 +3009,18 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and CopyBlocks= cannot be combined, refusing."); + if (p->block_device_replace && (p->format || partition_needs_populate(p))) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and BlockDeviceReplace= cannot be combined, refusing."); + + if ((p->copy_blocks_path || p->copy_blocks_auto) && p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= and BlockDeviceReplace= cannot be combined, refusing."); + + if (p->block_device_replace && arg_offline == 1) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "BlockDeviceReplace= is incompatible with --offline=yes, refusing."); + if (partition_needs_populate(p) && streq_ptr(p->format, "swap")) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=swap and CopyFiles=/MakeDirectories=/MakeSymlinks= cannot be combined, refusing."); @@ -2983,7 +3030,7 @@ static int partition_read_definition( if (p->type.designator == PARTITION_SWAP) format = "swap"; - else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto))) + else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto || p->block_device_replace))) /* Pick "vfat" as file system for esp and xbootldr partitions, otherwise default to "ext4". */ format = IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR) ? "vfat" : "ext4"; @@ -7186,6 +7233,102 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha return 0; } +static int context_block_device_replace(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) + continue; + + if (!p->btrfs_replaced) + continue; + + if (partition_defer(context, p)) + continue; + + assert(!p->btrfs_replaced->done); + + (void) context_notify(context, PROGRESS_REPLACING_DEVICE, p->definition_path, UINT_MAX); + + assert(p->offset != UINT64_MAX); + assert(p->new_size != UINT64_MAX); + + r = partition_target_prepare(context, p, + p->new_size, + /* need_path= */ true, + &t); + if (r < 0) + return r; + + if (p->encrypt != ENCRYPT_OFF) { + r = partition_encrypt(context, p, t, /* offline= */ false); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); + } + + log_info("Replacing partition %" PRIu64 ".", p->partno); + + /* btrfs_replace calls a synchronous ioctl and will return when replace is finished */ + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, partition_target_path(t)); + if (r < 0) + return log_error_errno(r, "Failed to replace btrfs device on partition %" PRIu64 ": %m", p->partno); + + p->btrfs_replaced->done = true; + + if (t->decrypted) + t->decrypted->keep = true; + + log_info("Successfully replaced partition %" PRIu64 ".", p->partno); + } + + return 0; +} + +static void context_btrfs_replace_resize(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_resize_max(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid); + if (r < 0) + log_warning_errno(r, "Could not resize btrfs filesystem moved to partition %" PRIu64 ", proceeding without resizing: %m", p->partno); + else + log_info("Successfully resized partition %" PRIu64 ".", p->partno); + } +} + +static void context_btrfs_replace_back(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, p->btrfs_replaced->source_path); + if (r < 0) + log_warning_errno(r, "Could not move back btrfs filesystem from partition %" PRIu64 ", leaving it on new device: %m", p->partno); + } +} + static int context_mkfs(Context *context) { int r; @@ -8335,6 +8478,15 @@ static int context_write_partition_table(Context *context) { if (r < 0) goto error; + /* We are now moving destructively btrfs filesystems into the disk before we have written the + * partitions. This is OK because the main use case is that the btrfs filesystems moved are initially + * volatile (in ram disk for example) with little data to save. But we do not want to finish the gpt + * table in case we lose power and reboot and try to boot that incomplete disk. + */ + r = context_block_device_replace(context); + if (r < 0) + goto error; + r = context_mangle_partitions(context); if (r < 0) goto error; @@ -8349,6 +8501,8 @@ static int context_write_partition_table(Context *context) { goto error; } + context_btrfs_replace_resize(context); + r = context_partscan(context); if (r < 0) return r; @@ -8358,6 +8512,8 @@ static int context_write_partition_table(Context *context) { return 0; error: + context_btrfs_replace_back(context); + if (context->needs_rescan) (void) context_partscan(context); @@ -8884,9 +9040,6 @@ static int context_open_copy_block_paths( assert(context); - if (!context->partitions) - return 0; - LIST_FOREACH(partitions, p, context->partitions) { _cleanup_close_ int source_fd = -EBADF; _cleanup_free_ char *opened = NULL; @@ -8995,6 +9148,72 @@ static int context_open_copy_block_paths( return 0; } +static int context_open_btrfs_filesystems(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(btrfs_replacement_freep) BtrfsReplacement *replacement = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) + continue; + + if (!p->block_device_replace) + continue; + + if (partition_defer(context, p)) + continue; + + (void) context_notify(context, PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, p->definition_path, UINT_MAX); + + replacement = new(BtrfsReplacement, 1); + if (!replacement) + return log_oom(); + + *replacement = (BtrfsReplacement) { + .mountpoint_fd = -EBADF, + .source_fd = -EBADF, + }; + + replacement->mountpoint_fd = xopenat(AT_FDCWD, p->block_device_replace, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (replacement->mountpoint_fd < 0) + return log_error_errno(replacement->mountpoint_fd, "Failed to open mountpoint %s for btrfs filesystem: %m", p->block_device_replace); + + r = fd_is_fs_type(replacement->mountpoint_fd, BTRFS_SUPER_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to check filesystem for mountpoint %s: %m", p->block_device_replace); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Mountpoint %s is not a btrfs filesystem", p->block_device_replace); + + r = btrfs_get_block_device_at_full(replacement->mountpoint_fd, "", &replacement->devid, &replacement->source_path, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find device id for btrfs filesystem: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Btrfs filesystem has multiple devices."); + + /* We need to keep the source device open otherwise, it might be collected. */ + replacement->source_fd = open(replacement->source_path, O_RDONLY|O_CLOEXEC); + if (replacement->source_fd < 0) + return log_error_errno(errno, "Failed to open source device %s: %m", replacement->source_path); + + r = fd_verify_block(replacement->source_fd); + if (r < 0) + return log_error_errno(r, "Device %s is not a block device: %m", replacement->source_path); + + r = blockdev_get_device_size(replacement->source_fd, &replacement->source_size); + if (r < 0) + return log_error_errno(r, "Failed to get device size %s: %m", replacement->source_path); + + p->btrfs_replaced = TAKE_PTR(replacement); + } + + return 0; +} + static int fd_apparent_size(int fd, uint64_t *ret) { off_t initial = 0; uint64_t size = 0; @@ -11154,6 +11373,10 @@ static int vl_method_run( if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + r = context_acquire_partition_uuids_and_labels(context); if (r < 0) return r; @@ -11451,6 +11674,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + /* Make sure each partition has a unique UUID and unique label */ r = context_acquire_partition_uuids_and_labels(context); if (r < 0) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 33d6af3942ec6..daa67e6f5cfe3 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -23,6 +23,7 @@ #include "rm-rf.h" #include "sparse-endian.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "time-util.h" @@ -95,14 +96,20 @@ int btrfs_subvol_get_read_only_fd(int fd) { return !!(flags & BTRFS_SUBVOL_RDONLY); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret) { struct btrfs_ioctl_fs_info_args fsi = {}; _cleanup_close_ int fd = -EBADF; uint64_t id; int r; + /* + * Returns: + * ret_devid - the device id in the filesystem for the returned block device + * ret_path - the path to the returned block device + * ret - the returned block device + */ + 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); if (fd < 0) @@ -119,7 +126,12 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { /* We won't do this for btrfs RAID */ if (fsi.num_devices != 1) { - *ret = 0; + if (ret_devid) + *ret_devid = 0; + if (ret_path) + *ret_path = NULL; + if (ret) + *ret = 0; return 0; } @@ -128,6 +140,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { .devid = id, }; struct stat st; + _cleanup_free_ char *device_path = NULL; if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { if (errno == ENODEV) @@ -155,7 +168,16 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { if (major(st.st_rdev) == 0) return -ENODEV; - *ret = st.st_rdev; + device_path = strdup((char*) di.path); + if (!device_path) + return -ENOMEM; + + if (ret_path) + *ret_path = TAKE_PTR(device_path); + if (ret) + *ret = st.st_rdev; + if (ret_devid) + *ret_devid = id; return 1; } @@ -2122,3 +2144,50 @@ int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret) { return -ENODATA; } + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target) { + struct btrfs_ioctl_dev_replace_args replace = { + .cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START, + .result = UINT64_MAX, + .start = { + .srcdevid = device_id, + .cont_reading_from_srcdev_mode = BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS, + }, + }; + + assert(fdmntpnt >= 0); + assert(target); + + if (strlen(target) >= sizeof(replace.start.tgtdev_name)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Path to the btrfs replace target is too long"); + strncpy((char *)replace.start.tgtdev_name, target, sizeof(replace.start.tgtdev_name)); + + if (ioctl(fdmntpnt, BTRFS_IOC_DEV_REPLACE, &replace) < 0) + return -errno; + + switch (replace.result) { + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR: + break; + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "btrfs replace was not started"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "btrfs replace was already started on this device"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS: + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "btrfs scrub is in progress"); + default: + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "An unknown btrfs error status occurred"); + } + + return 0; +} + +int btrfs_resize_max(int fdmntpnt, uint64_t devid) { + struct btrfs_ioctl_vol_args args = {}; + + assert(fdmntpnt >= 0); + + assert_cc(STRLEN(":max") + DECIMAL_STR_MAX(uint64_t) + 1 <= sizeof(args.name)); + xsprintf(args.name, "%" PRIu64 ":max", devid); + + return RET_NERRNO(ioctl(fdmntpnt, BTRFS_IOC_RESIZE, &args)); +} diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index afa54cde4c434..da7c89794542e 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -49,7 +49,10 @@ static inline int btrfs_is_subvol(const char *path) { return btrfs_is_subvol_at(AT_FDCWD, path); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret); +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret); +static inline int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { + return btrfs_get_block_device_at_full(dir_fd, path, NULL, NULL, ret); +} static inline int btrfs_get_block_device(const char *path, dev_t *ret) { return btrfs_get_block_device_at(AT_FDCWD, path, ret); } @@ -133,3 +136,6 @@ bool btrfs_might_be_subvol(const struct stat *st) _pure_; int btrfs_forget_device(const char *path); int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret); + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target); +int btrfs_resize_max(int fdmntpnt, uint64_t devid); From 4021f565e2aa4fbbe2e0728a7f76a37751bf9cc9 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:14:57 +0100 Subject: [PATCH 1614/2155] repart: Add VolumeName= When a luks2 device mapper is to be kept alive after execution of systemd-cryptsetup, the name of the volume will be taken from this value. --- man/repart.d.xml | 15 +++++++++++++++ src/repart/repart.c | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 598cdc2310dcb..8d53805bb7f18 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -670,6 +670,21 @@ + + VolumeName= + + If an encrypted partition is created through Encrypt=, and it will + stay activated after systemd-repart is done (using + BlockDeviceReplace=), then the name of the device mapper volume created will use + the name specified by VolumeName=. + + The value must be a valid filename. If not specified, it will default to the value of + VolumeLabel= (which could be derived from Label=) if + valid. + + + + Encrypt= diff --git a/src/repart/repart.c b/src/repart/repart.c index 1620cc3530b5a..331b31b789991 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -494,6 +495,7 @@ typedef struct Partition { char *format; char *block_device_replace; BtrfsReplacement *btrfs_replaced; + char *volume_name; char **exclude_files_source; char **exclude_files_target; char **make_directories; @@ -837,6 +839,7 @@ static Partition* partition_free(Partition *p) { free(p->format); free(p->block_device_replace); btrfs_replacement_free(p->btrfs_replaced); + free(p->volume_name); strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); @@ -883,6 +886,7 @@ static void partition_foreignize(Partition *p) { p->format = mfree(p->format); p->block_device_replace = mfree(p->block_device_replace); p->btrfs_replaced = btrfs_replacement_free(p->btrfs_replaced); + p->volume_name = mfree(p->volume_name); p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); @@ -2959,6 +2963,7 @@ static int partition_read_definition( { "Partition", "FileSystemSectorSize", config_parse_fs_sector_size, 0, &p->fs_sector_size }, { "Partition", "Discard", config_parse_tristate, 0, &p->discard }, { "Partition", "BlockDeviceReplace", config_parse_path, 0, &p->block_device_replace }, + { "Partition", "VolumeName", config_parse_string, CONFIG_PARSE_STRING_SAFE, &p->volume_name }, {} }; _cleanup_free_ char *filename = NULL; @@ -3021,6 +3026,22 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "BlockDeviceReplace= is incompatible with --offline=yes, refusing."); + if (p->volume_name && !filename_is_valid(p->volume_name)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= has an invalid filename value, refusing."); + + if (p->volume_name && strlen(p->volume_name) > (DM_NAME_LEN - 1)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= is too long, refusing."); + + if (p->volume_name && p->encrypt == ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires Encrypt= to be enabled, refusing."); + + if (p->volume_name && !p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires BlockDeviceReplace= to be set, refusing."); + if (partition_needs_populate(p) && streq_ptr(p->format, "swap")) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=swap and CopyFiles=/MakeDirectories=/MakeSymlinks= cannot be combined, refusing."); @@ -5354,7 +5375,7 @@ static size_t dmcrypt_proper_key_size(Partition *p) { } } -static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { +static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline, bool temporary) { #if HAVE_LIBCRYPTSETUP #if HAVE_TPM2 _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -5416,7 +5437,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (ftruncate(fileno(h), luks_params.sector_size) < 0) return log_error_errno(errno, "Failed to grow temporary LUKS header file: %m"); } else { - if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) + if (!temporary && p->volume_name) + dm_name = strdup(p->volume_name); + else if (!temporary && filename_is_valid(vl)) + dm_name = strdup(vl); + else + dm_name = asprintf_safe("luks-repart-%08" PRIx64, random_u64()); + if (!dm_name) return log_oom(); vol = path_join("/dev/mapper/", dm_name); @@ -6275,7 +6302,7 @@ static int context_copy_blocks(Context *context) { return r; if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { - r = partition_encrypt(context, p, t, /* offline= */ false); + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); if (r < 0) return r; } @@ -6299,7 +6326,7 @@ static int context_copy_blocks(Context *context) { log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { - r = partition_encrypt(context, p, t, /* offline= */ true); + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return r; } @@ -7268,7 +7295,7 @@ static int context_block_device_replace(Context *context) { return r; if (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, t, /* offline= */ false); + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ false); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -7381,7 +7408,7 @@ static int context_mkfs(Context *context) { if (r < 0) return r; - r = partition_encrypt(context, p, t, /* offline= */ false); + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -7449,7 +7476,7 @@ static int context_mkfs(Context *context) { if (r < 0) return r; - r = partition_encrypt(context, p, t, /* offline= */ true); + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } From 120f906c8b72b8818fa89017acaf08125342d745 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 12 Mar 2026 23:15:06 +0100 Subject: [PATCH 1615/2155] test: Add test for repart's BlockDeviceReplace --- test/units/TEST-58-REPART.sh | 91 ++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index df6f935c98035..319b682298ce5 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -2132,6 +2132,97 @@ EOF losetup -d "$loop" } +testcase_block_device_replace() { + if [[ "$OFFLINE" == "yes" ]]; then + return 0 + fi + + if ! command -v btrfs >/dev/null; then + echo "btrfs not found, skipping." + return 0 + fi + + if ! command -v mkfs.btrfs >/dev/null; then + echo "mkfs.btrfs not found, skipping." + return 0 + fi + + local defs imgs btrfs_mntpoint_plain btrfs_mntpoint_encrypted + local loop loop_btrfs_plain loop_btrfs_encrypted + local encrypted_device + + btrfs_mntpoint_plain="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-plain.XXXXXXXXXX")" + btrfs_mntpoint_encrypted="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-encrypted.XXXXXXXXXX")" + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + chmod 0755 "$defs" + + truncate --size 500M "${imgs}/btrfs-plain" + mkfs.btrfs "${imgs}/btrfs-plain" + loop_btrfs_plain="$(losetup --show --find "$imgs/btrfs-plain")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_plain}" "${btrfs_mntpoint_plain}" + echo tada >"${btrfs_mntpoint_plain}/magic-plain" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 500M "${imgs}/btrfs-encrypted" + mkfs.btrfs "${imgs}/btrfs-encrypted" + loop_btrfs_encrypted="$(losetup --show --find "$imgs/btrfs-encrypted")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_encrypted}" "${btrfs_mntpoint_encrypted}" + echo tada >"${btrfs_mntpoint_encrypted}/magic-encrypted" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_encrypted}'; losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 2G "${imgs}/img" + + tee "$defs/01-plain.conf" < Date: Tue, 12 May 2026 16:00:05 +0200 Subject: [PATCH 1616/2155] update TODO --- TODO.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 466eb223426dc..40751f6d64304 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,23 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- pcrextend: we probably should measure /etc/machine-info during boot somehow + +- pcrextend: we should measure something when we enter developer mode, by some + definition of developer mode. + +- /etc/machine-info should have a concept of a "role" that we can put a machine + into, which can be consumed by sysupdate and similar. A role should be + something we can set once (i.e. the initial setting should be protected by + polkit and be somewhat losely access control, and later settings should use a + different/tougher polkit authorization, so that people can implement a + no-way-back mechanism) + +- firstboot: optionally accept credentials at firstboot without authentication + +- firstboot/sysinstall: add simple interface for prompting users to enable + "features" exposed by of sysupdate. + - bootctl link + sysupdate integration - make sysupdate call out to a special varlink dir on completion - bind bootctl link socket in there, which when invoked goes to new dir in @@ -135,8 +152,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later .v/) and then does "bootctl link" on them. - a tool that can prep credentials, put them in the ESP, for provisioning - systems for SBC. Should be doing what sysinstall does with the credentials, - and maybe even *be* sysinstall. + systems for SBC or UEFI/HTTP boot. Should be doing what sysinstall does with + the credentials, and maybe even *be* sysinstall. - make sure we always pass O_NOFOLLOW on O_CREAT From d0234450b621568896e00d5869e091be1d1f83b3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 12:26:37 +0200 Subject: [PATCH 1617/2155] json-stream: tolerate truncated SCM_RIGHTS on inbound messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an LSM (e.g. SELinux) denies an fd transfer or the receiver hits RLIMIT_NOFILE, the kernel drops the fd(s) from the SCM_RIGHTS cmsg and sets MSG_CTRUNC on the recvmsg(). recvmsg_safe() turns that into -ECHRNG, which causes json_stream_read() to discard the data bytes that were nevertheless received and the varlink server to silently tear down the connection — leaving the caller waiting for a reply that never comes. Inline the recvmsg() call instead and, on MSG_CTRUNC, drop the partial fds but keep the message data. The method handler will surface a clean -ENXIO when it tries to peek the missing fd, which sd-varlink wraps as io.systemd.System for the peer, instead of a hang. This matches the recent sd-bus fix in 6c8de404c9 ('sd-bus: allow receiving messages with MSG_CTRUNC set'). --- src/libsystemd/sd-json/json-stream.c | 16 +++- src/libsystemd/sd-varlink/test-varlink.c | 96 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c index 3775691d47f67..88473b81929a7 100644 --- a/src/libsystemd/sd-json/json-stream.c +++ b/src/libsystemd/sd-json/json-stream.c @@ -1259,7 +1259,21 @@ int json_stream_read(JsonStream *s) { .msg_controllen = s->input_control_buffer_size, }; - n = recvmsg_safe(s->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + n = RET_NERRNO(recvmsg(s->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC)); + if (n >= 0 && FLAGS_SET(mh.msg_flags, MSG_TRUNC)) { + cmsg_close_all(&mh); + return -EXFULL; + } + if (n >= 0 && FLAGS_SET(mh.msg_flags, MSG_CTRUNC)) { + /* SCM_RIGHTS got truncated — typically because an LSM (e.g. SELinux) + * denied the fd transfer. Drop the partial fds and continue with the + * data bytes: the request handler will surface a clean error to the + * peer when it tries to peek the missing fd, instead of us tearing + * the connection down silently and leaving the caller waiting. */ + json_stream_log(s, "SCM_RIGHTS truncated on inbound message, dropping received file descriptors."); + cmsg_close_all(&mh); + mh.msg_controllen = 0; + } } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) n = RET_NERRNO(read(s->input_fd, p, rs)); else diff --git a/src/libsystemd/sd-varlink/test-varlink.c b/src/libsystemd/sd-varlink/test-varlink.c index 72edc033dd068..cca859c2f6867 100644 --- a/src/libsystemd/sd-varlink/test-varlink.c +++ b/src/libsystemd/sd-varlink/test-varlink.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include "sd-json.h" #include "sd-varlink.h" +#include "dirent-util.h" #include "fd-util.h" #include "io-util.h" #include "json-util.h" @@ -1045,4 +1047,98 @@ TEST(execute_directory) { ASSERT_EQ(reply_count, count_before); } +#define CTRUNC_N_FDS 64U + +static int method_ctrunc(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Peek the first fd the client supposedly sent. With SCM_RIGHTS truncated by the kernel + * because the receiver hit RLIMIT_NOFILE, sd_varlink_peek_fd() returns -ENXIO once we + * walk past the last successfully installed fd. We're forcing the missing-fd case here, + * so we expect to fail and let varlink translate -ENXIO into io.systemd.System for the + * peer. */ + int fd = sd_varlink_peek_fd(link, CTRUNC_N_FDS - 1); + if (fd < 0) + return fd; + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("ok", 1)); +} + +static int reply_ctrunc(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + /* We expect a clean system error back rather than a hanging connection: the server + * dropped the truncated fds and our handler surfaced -ENXIO, which varlink wraps as + * io.systemd.System. */ + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_SYSTEM); + ASSERT_ERROR(sd_varlink_error_to_errno(error_id, parameters), ENXIO); + + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(ctrunc) { + int r; + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT|SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.CTrunc", method_ctrunc)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + ASSERT_OK(sd_varlink_set_allow_fd_passing_input(c, true)); + ASSERT_OK(sd_varlink_set_allow_fd_passing_output(c, true)); + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + ASSERT_OK(sd_varlink_bind_reply(c, reply_ctrunc)); + + /* Open a batch of memfds we'll attach to the call. We push duplicates so the originals + * keep occupying fd table slots after the stream sends and closes the dup'd copies, + * making the receiver hit RLIMIT_NOFILE when it tries to install the incoming fds. */ + int originals[CTRUNC_N_FDS]; + for (size_t i = 0; i < CTRUNC_N_FDS; i++) + originals[i] = -EBADF; + CLEANUP_ELEMENTS(originals, close_many_unset); + + for (size_t i = 0; i < CTRUNC_N_FDS; i++) { + originals[i] = ASSERT_OK(memfd_new_and_seal_string("ctrunc", "x")); + ASSERT_OK_EQ(sd_varlink_push_dup_fd(c, originals[i]), (int) i); + } + + /* Constrain RLIMIT_NOFILE so the server can't install every received fd. The kernel + * will then drop the remaining fds from the SCM_RIGHTS message and set MSG_CTRUNC, + * which is precisely what an LSM denial (or a real fd-table-full peer) looks like to + * the receive side. Pick the new limit slightly above our current open-fd count so + * the kernel can install only a handful of received fds before failing the rest. */ + struct rlimit orig_rl; + ASSERT_OK_ERRNO(getrlimit(RLIMIT_NOFILE, &orig_rl)); + + size_t n_open = 0; + _cleanup_closedir_ DIR *d = ASSERT_NOT_NULL(opendir("/proc/self/fd")); + FOREACH_DIRENT_ALL(de, d, break) + if (!dot_or_dot_dot(de->d_name)) + n_open++; + + /* n_open currently includes the CTRUNC_N_FDS dup'd fds that the stream will close once + * the message has been sent. After the send, we'll be back down to n_open - CTRUNC_N_FDS + * fds. Set the limit just slightly above that, so the kernel can install only a handful + * of the CTRUNC_N_FDS incoming fds before failing the rest with MSG_CTRUNC. */ + ASSERT_GT(n_open, CTRUNC_N_FDS); + struct rlimit new_rl = { + .rlim_cur = n_open - CTRUNC_N_FDS + 8, + .rlim_max = orig_rl.rlim_max, + }; + ASSERT_OK_ERRNO(setrlimit(RLIMIT_NOFILE, &new_rl)); + + r = sd_varlink_invoke(c, "io.test.CTrunc", /* parameters= */ NULL); + if (r >= 0) + r = sd_event_loop(e); + + ASSERT_OK_ERRNO(setrlimit(RLIMIT_NOFILE, &orig_rl)); + ASSERT_OK(r); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 47eb11e4c2822def59789a691db8a5e8968c0163 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 13 May 2026 01:50:30 +0900 Subject: [PATCH 1618/2155] po: update Japanese translation --- po/ja.po | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/po/ja.po b/po/ja.po index 28bf9ae0f4f61..ebb87a3afc2c5 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-13 01:48+0900\n" "PO-Revision-Date: 2025-03-17 03:11+0000\n" "Last-Translator: Y T \n" "Language-Team: Japanese Date: Wed, 13 May 2026 01:57:12 +0900 Subject: [PATCH 1619/2155] option: fix typo --- src/shared/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/options.c b/src/shared/options.c index 85ab3155bf867..34d02d2850e9c 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -32,7 +32,7 @@ static void shift_arg(char* argv[], int target, int source) { assert(argv); assert(target <= source); - /* Move argv[source] before argv[target], shifting arguments inbetween */ + /* Move argv[source] before argv[target], shifting the arguments in between. */ char *saved = argv[source]; memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*)); argv[target] = saved; From d56adae62459e1b375c55930352c87c26e996704 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 13 May 2026 02:04:33 +0900 Subject: [PATCH 1620/2155] copy: fix typo and slightly update comment --- src/shared/copy.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shared/copy.c b/src/shared/copy.c index a8e2b68db1414..d5788c7d77fb5 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -206,8 +206,7 @@ int copy_bytes_full( if ((copy_flags & COPY_REFLINK)) { off_t foffset; - /* 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. */ + /* In reflink mode, we need to know the current file offset, unless we already sought to 0 anyway. */ foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR); if (foffset >= 0) { off_t toffset; From 9f08885b0e72e7756849cf1abab929a380166e34 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 12 May 2026 12:40:54 +0100 Subject: [PATCH 1621/2155] test: make TEST-75-RESOLVED robust against journald metadata race Even after switching the wait loop to a polling `journalctl --grep`, the test still fails intermittently because the very first messages emitted by the freshly-spawned systemd-networkd-wait-online process can carry stale journald metadata. journald associates `_SYSTEMD_UNIT=` (and friends) with each entry by reading `/proc/$pid/cgroup` of the originating PID; if those messages are produced before journald notices the cgroup migration into the new service, they get tagged with `_SYSTEMD_UNIT=init.scope`. The `-u $unit` filter then fails to match them. Capture a journal cursor before launching the unit, and grep using `--after-cursor=` plus `SYSLOG_IDENTIFIER=systemd-networkd-wait-online` instead of `-u $unit`. SYSLOG_IDENTIFIER is set by the program itself, so it's not subject to the cgroup-discovery race. The cursor bounds the search to entries produced by this invocation, so prior wait-online runs in earlier testcases don't interfere. Logs from the failing run showing the messages exist but are tagged with the wrong unit: [ 2570.948554] TEST-75-RESOLVED.sh[2178]: + unit=wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service [ 2571.023162] TEST-75-RESOLVED.sh[2178]: + systemd-run -u wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service ... [ 2571.049189] TEST-75-RESOLVED.sh[2178]: + timeout 30 bash -c 'until journalctl -b -u wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service --grep ...' [ 2571.964986] systemd-networkd-wait-online[2190]: dns0: No DNS server is accessible. [ 2601.051088] TEST-75-RESOLVED.sh[2178]: ++ cleanup And for that 2571.964986 entry: _SYSTEMD_CGROUP=/init.scope _SYSTEMD_UNIT=init.scope _EXE=/usr/lib/systemd/systemd-executor _CMDLINE=/usr/lib/systemd/systemd-executor --deserialize 68 ... SYSLOG_IDENTIFIER=systemd-networkd-wait-online MESSAGE=dns0: No DNS server is accessible. Follow-up for d4bc62713e09df09281f26f4bf385801a3ee2897 Co-developed-by: Claude Opus 4.7 --- test/units/TEST-75-RESOLVED.sh | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index 7b5670a4bc726..03e50db7b32ec 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1387,7 +1387,7 @@ testcase_15_wait_online_dns() { echo "===== journalctl -u $unit =====" journalctl -b --no-pager --no-hostname --full -u "$unit" echo "==========" - rm -f "$override" + rm -f "$override" "$cursor_file" restart_resolved resolvectl revert dns0 } @@ -1396,6 +1396,7 @@ testcase_15_wait_online_dns() { local unit local override + local cursor_file unit="wait-online-dns-$(systemd-id128 new -u).service" override="/run/systemd/resolved.conf.d/90-global-dns.conf" @@ -1416,12 +1417,26 @@ testcase_15_wait_online_dns() { systemctl stop systemd-resolved.service systemctl start systemd-resolved-monitor.socket systemd-resolved-varlink.socket + # Capture a journal cursor before starting the unit so we can match only on + # log messages emitted afterwards. We deliberately do not filter on + # _SYSTEMD_UNIT= because journald may attach stale cgroup metadata + # (e.g. _SYSTEMD_UNIT=init.scope) to the very first messages emitted by a + # freshly-spawned process, before its cgroup migration into the new service + # is observed. Filtering by SYSLOG_IDENTIFIER and a cursor is not affected + # by that race. + cursor_file=$(mktemp) + journalctl -n 0 --cursor-file="$cursor_file" + # Begin systemd-networkd-wait-online --dns systemd-run -u "$unit" -p "Environment=SYSTEMD_LOG_LEVEL=debug" -p "Environment=SYSTEMD_LOG_TARGET=journal" --service-type=exec \ /usr/lib/systemd/systemd-networkd-wait-online --timeout=0 --dns --interface=dns0 - # Wait until it blocks waiting for updated DNS config - timeout 30 bash -c "until journalctl -b -u $unit --grep 'dns0: No.*DNS server is accessible' >/dev/null 2>&1; do sleep 0.5; done" + # Wait until it blocks waiting for updated DNS config. + # Note: don't use 'journalctl -f | grep -m1 ...' here. Once grep exits on + # match, journalctl -f will only notice the closed pipe on its next write + # attempt, which may never come for an otherwise idle unit, causing the + # pipeline to hang. + timeout 30 bash -c "until journalctl --after-cursor=\"\$(cat \"$cursor_file\")\" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep 'dns0: No.*DNS server is accessible' >/dev/null 2>&1; do sleep 0.5; done" # Update the global configuration. Restart rather than reload systemd-resolved so that # systemd-networkd-wait-online has to re-connect to the varlink service. @@ -1436,10 +1451,10 @@ testcase_15_wait_online_dns() { journalctl --sync # Check that a disconnect happened, and was handled. - journalctl -b -u "$unit" --grep="DNS configuration monitor disconnected, reconnecting..." >/dev/null + journalctl --after-cursor="$(cat "$cursor_file")" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep="DNS configuration monitor disconnected, reconnecting..." >/dev/null # Check that dns0 was found to be online. - journalctl -b -u "$unit" --grep="dns0: link is configured by networkd and online." >/dev/null + journalctl --after-cursor="$(cat "$cursor_file")" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep="dns0: link is configured by networkd and online." >/dev/null } testcase_delegate() { From bcc775543894b88f88f443019599acaba637ecd0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 21 Mar 2026 05:16:04 +0900 Subject: [PATCH 1622/2155] dhcp-network: make dhcp_network_send_{raw,udp}_socket() take iovec_wrapper --- src/libsystemd-network/dhcp-client-send.c | 14 +++++--- src/libsystemd-network/dhcp-network.c | 42 ++++++++++++++--------- src/libsystemd-network/dhcp-network.h | 10 +++--- src/libsystemd-network/fuzz-dhcp-client.c | 4 +-- src/libsystemd-network/sd-dhcp-server.c | 8 ++++- src/libsystemd-network/test-dhcp-client.c | 17 +++++---- 6 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index 56306fbf53647..d1f20fef46e69 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -8,6 +8,8 @@ #include "dhcp-network.h" #include "dhcp-packet.h" #include "fd-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "socket-util.h" static int client_get_socket(sd_dhcp_client *client, int domain) { @@ -112,8 +114,10 @@ int dhcp_client_send_raw( r = dhcp_network_send_raw_socket( fd, &client->link, - packet, - sizeof(DHCPPacket) + optoffset); + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(packet, sizeof(DHCPPacket) + optoffset), + .count = 1, + }); if (r < 0) return r; @@ -166,8 +170,10 @@ int dhcp_client_send_udp( fd, client->lease->server_address, client->server_port, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(&packet->dhcp, sizeof(DHCPMessage) + optoffset), + .count = 1, + }); if (r < 0) return r; diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c index 24b8c12011f73..c78f49159f7e4 100644 --- a/src/libsystemd-network/dhcp-network.c +++ b/src/libsystemd-network/dhcp-network.c @@ -7,13 +7,13 @@ #include #include #include -#include #include #include "dhcp-network.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "iovec-wrapper.h" #include "socket-util.h" #include "unaligned.h" @@ -244,30 +244,37 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int } int dhcp_network_send_raw_socket( - int s, + int fd, const union sockaddr_union *link, - const void *packet, - size_t len) { + const struct iovec_wrapper *iovw) { - /* Do not add assert(s >= 0) here, as this is called in fuzz-dhcp-server, and in that case this - * function should fail with negative errno. */ + /* Do not add assert(fd >= 0) here, as this is also called from fuzz-dhcp-server, and in that case + * fd is negative and this function should fail with negative errno. */ assert(link); - assert(packet); - assert(len > 0); + assert(!iovw_isempty(iovw)); - if (sendto(s, packet, len, 0, &link->sa, sockaddr_ll_len(&link->ll)) < 0) + struct msghdr mh = { + .msg_name = (struct sockaddr*) &link->sa, + .msg_namelen = sockaddr_ll_len(&link->ll), + .msg_iov = iovw->iovec, + .msg_iovlen = iovw->count, + }; + + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) return -errno; return 0; } int dhcp_network_send_udp_socket( - int s, + int fd, be32_t address, uint16_t port, - const void *packet, - size_t len) { + const struct iovec_wrapper *iovw) { + + assert(fd >= 0); + assert(!iovw_isempty(iovw)); union sockaddr_union dest = { .in.sin_family = AF_INET, @@ -275,11 +282,14 @@ int dhcp_network_send_udp_socket( .in.sin_addr.s_addr = address, }; - assert(s >= 0); - assert(packet); - assert(len > 0); + struct msghdr mh = { + .msg_name = &dest.sa, + .msg_namelen = sizeof(dest.in), + .msg_iov = iovw->iovec, + .msg_iovlen = iovw->count, + }; - if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)) < 0) + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) return -errno; return 0; diff --git a/src/libsystemd-network/dhcp-network.h b/src/libsystemd-network/dhcp-network.h index 3cc66dad9da91..6bef8ddaf9cbf 100644 --- a/src/libsystemd-network/dhcp-network.h +++ b/src/libsystemd-network/dhcp-network.h @@ -20,13 +20,11 @@ int dhcp_network_bind_udp_socket( uint16_t port, int ip_service_type); int dhcp_network_send_raw_socket( - int s, + int fd, const union sockaddr_union *link, - const void *packet, - size_t len); + const struct iovec_wrapper *iovw); int dhcp_network_send_udp_socket( - int s, + int fd, be32_t address, uint16_t port, - const void *packet, - size_t len); + const struct iovec_wrapper *iovw); diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 21f693c4873a3..d6a2577194d51 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -24,7 +24,7 @@ int dhcp_network_bind_raw_socket( 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) { +int dhcp_network_send_raw_socket(int fd, const union sockaddr_union *link, const struct iovec_wrapper *iovw) { return 0; } @@ -32,7 +32,7 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int 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) { +int dhcp_network_send_udp_socket(int fd, be32_t address, uint16_t port, const struct iovec_wrapper *iovw) { return 0; } diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 48c481809f4e5..702a2d67553e0 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -349,7 +349,13 @@ static int dhcp_server_send_unicast_raw( if (r < 0) return r; - return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); + return dhcp_network_send_raw_socket( + server->fd_raw, + &link, + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(packet, len), + .count = 1, + }); } static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index b2cadc07e5b1e..957e43d76274d 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -13,12 +13,13 @@ #include "sd-dhcp-lease.h" #include "sd-event.h" -#include "alloc-util.h" #include "dhcp-duid-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" #include "log.h" #include "tests.h" @@ -149,15 +150,19 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us return 0; } -int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { +int dhcp_network_send_raw_socket(int fd, const union sockaddr_union *link, const struct iovec_wrapper *iovw) { uint16_t ip_check, udp_check; - ASSERT_OK(s); - ASSERT_NOT_NULL(packet); + ASSERT_OK(fd); + ASSERT_NOT_NULL(iovw); + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(iovw, &iov)); + + size_t len = iov.iov_len; ASSERT_GT(len, sizeof(DHCPPacket)); - _cleanup_free_ DHCPPacket *discover = ASSERT_NOT_NULL(memdup(packet, len)); + DHCPPacket *discover = ASSERT_NOT_NULL(iov.iov_base); ASSERT_EQ(discover->ip.ttl, IPDEFTTL); ASSERT_EQ(discover->ip.protocol, IPPROTO_UDP); @@ -208,7 +213,7 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int 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) { +int dhcp_network_send_udp_socket(int fd, be32_t address, uint16_t port, const struct iovec_wrapper *iovw) { return 0; } From 000eb0f90886375bc534261dea679901bda557ff Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 3 May 2026 06:58:10 +0900 Subject: [PATCH 1623/2155] test: move unit test for dhcp_identifier_set_iaid() to test-dhcp-duid.c --- src/libsystemd-network/meson.build | 3 +++ src/libsystemd-network/test-dhcp-client.c | 17 ------------- src/libsystemd-network/test-dhcp-duid.c | 29 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 src/libsystemd-network/test-dhcp-duid.c diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index b21f138aa6bc9..9a2d1bc776517 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -78,6 +78,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-client.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-duid.c'), + }, network_test_template + { 'sources' : files('test-dhcp-message.c'), }, diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 957e43d76274d..d7dd01cc813ae 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -108,23 +108,6 @@ TEST(dhcp_client_anonymize) { ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); } -TEST(dhcp_identifier_set_iaid) { - uint32_t iaid_legacy; - be32_t iaid; - - 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_EQ(iaid_legacy, 0x8dde4ba8u); - ASSERT_EQ(iaid, htole32(0x8dde4ba8u)); -#if __BYTE_ORDER == __LITTLE_ENDIAN - ASSERT_EQ(iaid, iaid_legacy); -#else - ASSERT_EQ(iaid, bswap_32(iaid_legacy)); -#endif -} - static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { switch (code) { case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { diff --git a/src/libsystemd-network/test-dhcp-duid.c b/src/libsystemd-network/test-dhcp-duid.c new file mode 100644 index 0000000000000..363295782d0b5 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-duid.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-duid-internal.h" +#include "ether-addr-util.h" +#include "tests.h" + +TEST(dhcp_identifier_set_iaid) { + uint32_t iaid_legacy; + be32_t iaid; + + static struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }; + + ASSERT_OK(dhcp_identifier_set_iaid(/* dev= */ NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy)); + ASSERT_OK(dhcp_identifier_set_iaid(/* dev= */ NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid)); + + /* 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_EQ(iaid, iaid_legacy); +#else + ASSERT_EQ(iaid, bswap_32(iaid_legacy)); +#endif +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From c28ec168aa728e5338f8914ca4dfeb8e28a7dfa2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 14:15:44 +0900 Subject: [PATCH 1624/2155] test: move test cases for client_id_{hash,compare}_func() to test-dhcp-client-id.c --- src/libsystemd-network/meson.build | 3 ++ src/libsystemd-network/test-dhcp-client-id.c | 51 ++++++++++++++++++++ src/libsystemd-network/test-dhcp-server.c | 46 ------------------ 3 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 src/libsystemd-network/test-dhcp-client-id.c diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 9a2d1bc776517..500882319e63e 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -78,6 +78,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-client.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-client-id.c'), + }, network_test_template + { 'sources' : files('test-dhcp-duid.c'), }, diff --git a/src/libsystemd-network/test-dhcp-client-id.c b/src/libsystemd-network/test-dhcp-client-id.c new file mode 100644 index 0000000000000..60cb682c88bb0 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-client-id.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-client-id-internal.h" +#include "hashmap.h" +#include "siphash24.h" +#include "tests.h" + +static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { + struct siphash state; + + siphash24_init(&state, key); + client_id_hash_func(id, &state); + + return htole64(siphash24_finalize(&state)); +} + +TEST(client_id_hash) { + sd_dhcp_client_id a = { + .size = 4, + }, b = { + .size = 4, + }; + uint8_t hash_key[HASH_KEY_SIZE] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + }; + + log_debug("/* %s */", __func__); + + memcpy(a.raw, "abcd", 4); + memcpy(b.raw, "abcd", 4); + + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + a.size = 3; + ASSERT_NE(client_id_compare_func(&a, &b), 0); + a.size = 4; + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + + b.size = 3; + ASSERT_NE(client_id_compare_func(&a, &b), 0); + b.size = 4; + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + + memcpy(b.raw, "abce", 4); + ASSERT_NE(client_id_compare_func(&a, &b), 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 3dc49491269b1..4da4fdb116b5b 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -9,8 +9,6 @@ #include "sd-event.h" #include "dhcp-server-internal.h" -#include "hashmap.h" -#include "siphash24.h" #include "tests.h" static void test_pool(struct in_addr *address, unsigned size, int ret) { @@ -285,49 +283,6 @@ static void test_message_handler(void) { ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); } -static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { - struct siphash state; - - siphash24_init(&state, key); - client_id_hash_func(id, &state); - - return htole64(siphash24_finalize(&state)); -} - -static void test_client_id_hash(void) { - sd_dhcp_client_id a = { - .size = 4, - }, b = { - .size = 4, - }; - uint8_t hash_key[HASH_KEY_SIZE] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - }; - - log_debug("/* %s */", __func__); - - memcpy(a.raw, "abcd", 4); - memcpy(b.raw, "abcd", 4); - - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - a.size = 3; - ASSERT_NE(client_id_compare_func(&a, &b), 0); - a.size = 4; - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - - b.size = 3; - ASSERT_NE(client_id_compare_func(&a, &b), 0); - b.size = 4; - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - - memcpy(b.raw, "abce", 4); - ASSERT_NE(client_id_compare_func(&a, &b), 0); -} - static void test_static_lease(void) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; @@ -452,7 +407,6 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - test_client_id_hash(); test_static_lease(); test_domain_name(); From 408d474dfa1a320cfa650b7624f9efdf55f639f2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 13:03:49 +0000 Subject: [PATCH 1625/2155] vmspawn: Prefer systemd-journal-remote from $PATH $PATH might point to a systemd checkout containing a newer version of systemd-journal-remote which we should use, hence prefer an executable from $PATH over the one from /usr/lib/systemd. --- src/shared/fork-notify.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index 0a34c491b7aa5..ce9d965677211 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -287,13 +287,15 @@ int fork_journal_remote( 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); + r = find_executable("systemd-journal-remote", &sd_journal_remote); + if (r == -ENOENT) + r = find_executable_full( + "systemd-journal-remote", + /* root= */ NULL, + STRV_MAKE(LIBEXECDIR), + /* use_path_envvar= */ false, + &sd_journal_remote, + /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); From 98d061e28b3bc7ae93857cfd77f39da600dc357c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 13 May 2026 05:34:21 +0900 Subject: [PATCH 1626/2155] TODO: fix typo --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 40751f6d64304..30cc5d02dcfba 100644 --- a/TODO.md +++ b/TODO.md @@ -136,7 +136,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later - /etc/machine-info should have a concept of a "role" that we can put a machine into, which can be consumed by sysupdate and similar. A role should be something we can set once (i.e. the initial setting should be protected by - polkit and be somewhat losely access control, and later settings should use a + polkit and be somewhat loosely access control, and later settings should use a different/tougher polkit authorization, so that people can implement a no-way-back mechanism) From 8c40f3bea0a4e0ae1d65b077ebf593231f823647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 21:18:15 +0200 Subject: [PATCH 1627/2155] journalctl,analyze: use assert_cc in two more places --- src/analyze/analyze.c | 2 +- src/journal/journalctl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 0f848264c73f0..759c5600ad276 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -227,7 +227,7 @@ static int help(void) { if (r < 0) return r; - assert_se(ELEMENTSOF(vtables) == 9); + assert_cc(ELEMENTSOF(vtables) == 9); (void) table_sync_column_widths(0, options, vtables[0], vtables[1], vtables[2], vtables[3], vtables[4], vtables[5], vtables[6], vtables[7], vtables[8]); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index c5009eb6f0560..5d0b8e228577a 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -251,7 +251,7 @@ static int help(void) { return r; } - assert_se(ELEMENTSOF(tables) == 6); + assert_cc(ELEMENTSOF(tables) == 6); (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]); From b5038cfc65952b0b71531a9984ed94fb136abffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 13:37:35 +0200 Subject: [PATCH 1628/2155] loginctl: move options and verbs to match order in --help First, "output modifier" options --no-pager/--no-legend/--no-ask-password are moved to the end next to --output and --json. I think it makes sense to group them. Then the implementing code is reordered to match the order in --help. --- src/login/loginctl.c | 530 +++++++++++++++++++++---------------------- 1 file changed, 265 insertions(+), 265 deletions(-) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index cdffd79d8ca12..73dbc1f7163b7 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -308,126 +308,6 @@ static int verb_list_sessions(int argc, char *argv[], uintptr_t _data, void *use return list_table_print(table, "sessions"); } -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) }, - { "State", "s", NULL, offsetof(UserStatusInfo, state) }, - {}, - }; - - _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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - assert(argv); - - r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(uso)"); - if (r < 0) - return bus_log_parse_error(r); - - table = table_new("uid", "user", "linger", "state"); - if (!table) - return log_oom(); - - (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error_property = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_property = NULL; - _cleanup_(user_status_info_done) UserStatusInfo info = {}; - const char *user, *object; - uint32_t uid; - - r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - r = bus_map_all_properties(bus, - "org.freedesktop.login1", - object, - property_map, - BUS_MAP_BOOLEAN_AS_BOOL, - &error_property, - &reply_property, - &info); - if (r < 0) { - log_full_errno(sd_bus_error_has_name(&error_property, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING, - r, - "Failed to get properties of user %s, ignoring: %s", - user, bus_error_message(&error_property, r)); - continue; - } - - r = table_add_many(table, - TABLE_UID, (uid_t) uid, - TABLE_STRING, user, - TABLE_BOOLEAN, info.linger, - TABLE_STRING, info.state); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return list_table_print(table, "users"); -} - -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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - assert(argv); - - r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(so)"); - if (r < 0) - return bus_log_parse_error(r); - - table = table_new("seat"); - if (!table) - return log_oom(); - - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - - for (;;) { - const char *seat; - - r = sd_bus_message_read(reply, "(so)", &seat, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - r = table_add_cell(table, NULL, TABLE_STRING, seat); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return list_table_print(table, "seats"); -} - static int show_unit_cgroup( sd_bus *bus, const char *unit, @@ -1084,103 +964,6 @@ static int verb_show_session(int argc, char *argv[], uintptr_t _data, void *user return 0; } -static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - bool properties; - int r; - - assert(argv); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_pager_flags); - - if (argc <= 1) { - /* If no argument is specified inspect the manager itself */ - if (properties) - return show_properties(bus, "/org/freedesktop/login1"); - - return print_user_status_info(bus, "/org/freedesktop/login1/user/self"); - } - - for (int i = 1, first = true; i < argc; i++, first = false) { - _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 *path; - uid_t uid; - - r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); - - r = bus_call_method(bus, bus_login_mgr, "GetUser", &error, &reply, "u", (uint32_t) uid); - if (r < 0) - return log_error_errno(r, "Failed to get user: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (!first) - putchar('\n'); - - if (properties) - r = show_properties(bus, path); - else - r = print_user_status_info(bus, path); - if (r < 0) - return r; - } - - return 0; -} - -static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - bool properties; - int r; - - assert(argv); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_pager_flags); - - if (argc <= 1) { - _cleanup_free_ char *path = NULL; - - /* If no argument is specified inspect the manager itself */ - if (properties) - return show_properties(bus, "/org/freedesktop/login1"); - - r = get_bus_path_by_id(bus, "seat", "GetSeat", "auto", &path); - if (r < 0) - return r; - - return print_seat_status_info(bus, path); - } - - for (int i = 1, first = true; i < argc; i++, first = false) { - _cleanup_free_ char *path = NULL; - - r = get_bus_path_by_id(bus, "seat", "GetSeat", argv[i], &path); - if (r < 0) - return r; - - if (!first) - putchar('\n'); - - if (properties) - r = show_properties(bus, path); - else - r = print_seat_status_info(bus, path); - if (r < 0) - return r; - } - - return 0; -} - 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); @@ -1224,6 +1007,27 @@ static int verb_activate(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +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; + + assert(argv); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method( + bus, + bus_login_mgr, + streq(argv[0], "lock-sessions") ? "LockSessions" : "UnlockSessions", + &error, NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Could not lock sessions: %s", bus_error_message(&error, r)); + + return 0; +} + 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); @@ -1250,18 +1054,145 @@ static int verb_kill_session(int argc, char *argv[], uintptr_t _data, void *user return 0; } -static int verb_enable_linger(int argc, char *argv[], uintptr_t _data, 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) }, + { "State", "s", NULL, offsetof(UserStatusInfo, state) }, + {}, + }; + _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; sd_bus *bus = ASSERT_PTR(userdata); - char* short_argv[3]; - bool b; int r; assert(argv); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r)); - b = streq(argv[0], "enable-linger"); + r = sd_bus_message_enter_container(reply, 'a', "(uso)"); + if (r < 0) + return bus_log_parse_error(r); + + table = table_new("uid", "user", "linger", "state"); + if (!table) + return log_oom(); + + (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error_property = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_property = NULL; + _cleanup_(user_status_info_done) UserStatusInfo info = {}; + const char *user, *object; + uint32_t uid; + + r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = bus_map_all_properties(bus, + "org.freedesktop.login1", + object, + property_map, + BUS_MAP_BOOLEAN_AS_BOOL, + &error_property, + &reply_property, + &info); + if (r < 0) { + log_full_errno(sd_bus_error_has_name(&error_property, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING, + r, + "Failed to get properties of user %s, ignoring: %s", + user, bus_error_message(&error_property, r)); + continue; + } + + r = table_add_many(table, + TABLE_UID, (uid_t) uid, + TABLE_STRING, user, + TABLE_BOOLEAN, info.linger, + TABLE_STRING, info.state); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return list_table_print(table, "users"); +} + +static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + bool properties; + int r; + + assert(argv); + + properties = !strstr(argv[0], "status"); + + pager_open(arg_pager_flags); + + if (argc <= 1) { + /* If no argument is specified inspect the manager itself */ + if (properties) + return show_properties(bus, "/org/freedesktop/login1"); + + return print_user_status_info(bus, "/org/freedesktop/login1/user/self"); + } + + for (int i = 1, first = true; i < argc; i++, first = false) { + _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 *path; + uid_t uid; + + r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); + + r = bus_call_method(bus, bus_login_mgr, "GetUser", &error, &reply, "u", (uint32_t) uid); + if (r < 0) + return log_error_errno(r, "Failed to get user: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (!first) + putchar('\n'); + + if (properties) + r = show_properties(bus, path); + else + r = print_user_status_info(bus, path); + if (r < 0) + return r; + } + + return 0; +} + +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]; + bool b; + int r; + + assert(argv); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + b = streq(argv[0], "enable-linger"); if (argc < 2) { /* No argument? Let's use an empty user name, @@ -1366,6 +1297,96 @@ static int verb_kill_user(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +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; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + assert(argv); + + r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(so)"); + if (r < 0) + return bus_log_parse_error(r); + + table = table_new("seat"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + for (;;) { + const char *seat; + + r = sd_bus_message_read(reply, "(so)", &seat, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_cell(table, NULL, TABLE_STRING, seat); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return list_table_print(table, "seats"); +} + +static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + bool properties; + int r; + + assert(argv); + + properties = !strstr(argv[0], "status"); + + pager_open(arg_pager_flags); + + if (argc <= 1) { + _cleanup_free_ char *path = NULL; + + /* If no argument is specified inspect the manager itself */ + if (properties) + return show_properties(bus, "/org/freedesktop/login1"); + + r = get_bus_path_by_id(bus, "seat", "GetSeat", "auto", &path); + if (r < 0) + return r; + + return print_seat_status_info(bus, path); + } + + for (int i = 1, first = true; i < argc; i++, first = false) { + _cleanup_free_ char *path = NULL; + + r = get_bus_path_by_id(bus, "seat", "GetSeat", argv[i], &path); + if (r < 0) + return r; + + if (!first) + putchar('\n'); + + if (properties) + r = show_properties(bus, path); + else + r = print_seat_status_info(bus, path); + if (r < 0) + return r; + } + + return 0; +} + 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); @@ -1406,27 +1427,6 @@ static int verb_flush_devices(int argc, char *argv[], uintptr_t _data, void *use return 0; } -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; - - assert(argv); - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method( - bus, - bus_login_mgr, - streq(argv[0], "lock-sessions") ? "LockSessions" : "UnlockSessions", - &error, NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Could not lock sessions: %s", bus_error_message(&error, r)); - - return 0; -} - 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); @@ -1487,9 +1487,6 @@ static int help(void) { "\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 Don't prompt for password\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" " -p --property=NAME Show only properties by this name\n" @@ -1508,6 +1505,9 @@ static int help(void) { " short-monotonic, short-unix, short-delta,\n" " json, json-pretty, json-sse, json-seq, cat,\n" " verbose, export, with-unit)\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Don't prompt for password\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -1569,6 +1569,17 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + r = parse_machine_argument(optarg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + case 'P': SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); _fallthrough_; @@ -1628,18 +1639,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - case ARG_KILL_WHOM: arg_kill_whom = optarg; break; @@ -1650,15 +1649,16 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; break; case '?': From 8dc5dbe3c01e937ef79c9bc63ca1051ae22447ad Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:42:08 +0200 Subject: [PATCH 1629/2155] vmspawn: split blockdev-add into separate file and format calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current vmspawn_qmp_add_block_device() emits a single blockdev-add that combines the format-level node ("vmspawn-N-storage") with an inline file child. QEMU's qmp_blockdev_add() only marks the top-level returned BDS as monitor-owned (qemu/blockdev.c:3440); inline children are NOT, so qmp_blockdev_del() rejects them with "Node X is not owned by the monitor" (qemu/blockdev.c:3513-3517). To prepare for ReplaceStorage — which needs to swap the file child of an existing format node via blockdev-reopen, and then blockdev-del the old file node — make the file node monitor-owned by issuing it as its own blockdev-add call. The 4-stage add chain becomes 5 stages: add-fd blockdev-add (file) → on_add_file_node_stage sets FILE_NODE_ADDED blockdev-add (format) → on_add_format_node_stage sets BLOCKDEV_ADDED remove-fd device_add DriveInfo gains qmp_file_node_name ("vmspawn-N-file-G", G a generation counter bumped on every replace), file_generation, and a stashed fdset_id so future ReplaceStorage can target both for cleanup. vmspawn_qmp_block_device_teardown() now deletes both nodes in order — format first, then file — because the format holds a strong reference to its file child and a file-first del is rejected with "Node X is busy: node is used as 'file' of Y". Folds bridge->features VMSPAWN_QMP_FEATURE_IO_URING into the file node's flags so the new path inherits io_uring just like the old inline form did. The format-level options (read-only, discard, discard-no-unref) are unchanged. The ephemeral path is structurally already separate file+format with monitor-owned children; no behavioural change there beyond the on_add_blockdev_stage → on_add_format_node_stage rename. Drops the now-unused qmp_build_blockdev_add_inline() helper. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 170 +++++++++++++++++++++++--------------- src/vmspawn/vmspawn-qmp.h | 10 ++- 2 files changed, 110 insertions(+), 70 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 52d9e11b9f89e..d2cbb67e79ebb 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -102,6 +102,7 @@ static DriveInfo* drive_info_free(DriveInfo *d) { free(d->id); free(d->qmp_node_name); free(d->qmp_device_id); + free(d->qmp_file_node_name); free(d->fdset_path); sd_varlink_unref(d->link); safe_close(d->fd); @@ -324,43 +325,6 @@ 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; @@ -424,8 +388,8 @@ static int get_image_virtual_size(int fd, const char *format, bool is_block_devi /* 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_format_node_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); @@ -474,7 +438,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { 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); + on_add_format_node_stage, slot_ref); if (r < 0) return drive_info_add_fail(drive, r, NULL); TAKE_PTR(slot_ref); @@ -670,19 +634,29 @@ static int reply_qmp_error(sd_varlink *link, const char *error_desc, int error) } /* 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) { + * raw_close (during blockdev-del) releases the last dup. Teardown deletes the + * format node first, then the file node — order matters because the format + * node holds a strong reference to its `file` child, which would block a + * file-first del with "Node X is busy: node is used as 'file' of Y". */ +static void vmspawn_qmp_block_device_teardown(QmpClient *client, + const char *qmp_node_name, + const char *qmp_file_node_name, + BlockDeviceStateFlags stages) { assert(client); - assert(qmp_node_name); - if (!FLAGS_SET(stages, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) - return; + if (FLAGS_SET(stages, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED) && qmp_node_name) { + _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 format"); + } - _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"); + if (FLAGS_SET(stages, BLOCK_DEVICE_STATE_FILE_NODE_ADDED) && qmp_file_node_name) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", qmp_file_node_name)) >= 0) + (void) qmp_client_invoke(client, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "teardown blockdev-del file"); + } } /* Insert into the owning primary map and the non-owning qmp_device_id view. On @@ -733,7 +707,8 @@ static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc) /* Pin the object alive across bridge_unregister_drive() + drive_info_unref() below. */ _cleanup_(drive_info_unrefp) DriveInfo *ref = drive_info_ref(d); - vmspawn_qmp_block_device_teardown(ref->bridge->qmp, ref->qmp_node_name, ref->state); + vmspawn_qmp_block_device_teardown(ref->bridge->qmp, ref->qmp_node_name, + ref->qmp_file_node_name, ref->state); ref->state = BLOCK_DEVICE_STATE_ADD_FAILED; if (bridge_unregister_drive(ref->bridge, ref)) @@ -782,7 +757,35 @@ static int on_add_observe_stage( return 0; } -static int on_add_blockdev_stage( +static int on_add_file_node_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(file) was queued may have marked the + * chain FAILED. The file node we just created is orphaned — tear it + * down retroactively. */ + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { + vmspawn_qmp_block_device_teardown(d->bridge->qmp, + /* qmp_node_name= */ NULL, + d->qmp_file_node_name, + BLOCK_DEVICE_STATE_FILE_NODE_ADDED); + return 0; + } + + d->state |= BLOCK_DEVICE_STATE_FILE_NODE_ADDED; + return 0; +} + +static int on_add_format_node_stage( QmpClient *client, sd_json_variant *result, const char *error_desc, @@ -795,11 +798,13 @@ static int on_add_blockdev_stage( 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. */ + /* A sync error after blockdev-add(format) was queued may have marked + * the chain FAILED. The format node we just created is orphaned — + * tear it down retroactively. The file node was already torn down by + * drive_info_add_fail at original failure time. */ if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, + /* qmp_file_node_name= */ NULL, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED); return 0; } @@ -901,6 +906,10 @@ int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { return log_oom(); if (asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0) return log_oom(); + drive->file_generation = 0; + if (asprintf(&drive->qmp_file_node_name, "vmspawn-%" PRIu64 "-file-%" PRIu64, + drive->counter, drive->file_generation) < 0) + return log_oom(); /* Auto-assigned user ids reuse qmp_device_id. */ if (!drive->id) { drive->id = strdup(drive->qmp_device_id); @@ -939,32 +948,58 @@ int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { 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); + on_add_observe_stage, slot_ref, &drive->fdset_path, &drive->fdset_id); if (r < 0) return r; TAKE_PTR(slot_ref); - _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); + /* Build flags for the file-level node: RO and NO_FLUSH from the drive + * plus IO_URING from the bridge feature probe. */ + QmpDriveFlags file_flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_NO_FLUSH); + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + file_flags |= QMP_DRIVE_IO_URING; + + QmpFileNodeParams file_params = { + .node_name = drive->qmp_file_node_name, + .filename = drive->fdset_path, + .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = file_flags, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *file_args = NULL; + r = qmp_build_blockdev_add_file(&file_params, &file_args); + if (r < 0) + return r; + + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(file_args), + on_add_file_node_stage, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + QmpFormatNodeParams format_params = { + .node_name = drive->qmp_node_name, + .format = drive->format, + .file_node_name = drive->qmp_file_node_name, + .flags = drive->flags, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *format_args = NULL; + r = qmp_build_blockdev_add_format(&format_params, &format_args); if (r < 0) return r; 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); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(format_args), + on_add_format_node_stage, slot_ref); if (r < 0) return r; TAKE_PTR(slot_ref); - /* Release the monitor's original fd; the blockdev-add above took a dup. */ + /* Release the monitor's original fd; blockdev-add(file) above took a dup. */ slot_ref = drive_info_ref(drive); - r = qmp_fdset_remove(bridge->qmp, fdset_id, on_add_observe_stage, slot_ref); + r = qmp_fdset_remove(bridge->qmp, drive->fdset_id, on_add_observe_stage, slot_ref); if (r < 0) return r; TAKE_PTR(slot_ref); @@ -1071,7 +1106,8 @@ int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_varian if (!drive) return 0; - vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, drive->state); + vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, + drive->qmp_file_node_name, drive->state); assert_se(bridge_unregister_drive(bridge, drive) == drive); drive_info_unref(drive); diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index a2f929732086e..dca0b03b10a82 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -76,9 +76,10 @@ typedef enum QmpDriveFlags { } 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 */ + 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 */ + BLOCK_DEVICE_STATE_FILE_NODE_ADDED = 1u << 3, /* blockdev-add(file) succeeded; teardown must del it */ } BlockDeviceStateFlags; /* Ref-counted; each of the four add-stage QMP slots holds one ref. @@ -105,7 +106,10 @@ typedef struct DriveInfo { 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 *qmp_file_node_name; /* "vmspawn--file-" — current file-level node */ + uint64_t file_generation; /* monotonically bumped per replace ATTEMPT */ char *fdset_path; /* "/dev/fdset/N" */ + uint64_t fdset_id; /* numeric id matching fdset_path */ 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 */ From 3063448b51b4fcfcdd51f015db9260292ce70fab Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:42:55 +0200 Subject: [PATCH 1630/2155] shared/varlink-io.systemd.MachineInstance: add ReplaceStorage method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define the IDL for io.systemd.MachineInstance.ReplaceStorage, a runtime hot-swap of an already-attached storage volume's backing file. The signature mirrors AddStorage minus the 'config' field because the device frontend (virtio-blk, virtio-scsi, nvme, scsi-cd) does not change — only the backing file behind it. The implementation lives in vmspawn (next commit) and uses QMP blockdev-reopen to swap the file child of the existing format node. The reused error vocabulary (NoSuchStorage, StorageImmutable, NotConnected, plus the generic errno path) covers every failure mode; no new errors are added. Signed-off-by: Christian Brauner (Amutable) --- src/shared/varlink-io.systemd.MachineInstance.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/shared/varlink-io.systemd.MachineInstance.c b/src/shared/varlink-io.systemd.MachineInstance.c index 6630402709870..b496ea565c481 100644 --- a/src/shared/varlink-io.systemd.MachineInstance.c +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -39,6 +39,13 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Unique storage name ':' to detach"), SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); +static SD_VARLINK_DEFINE_METHOD( + ReplaceStorage, + SD_VARLINK_FIELD_COMMENT("Index of the attached file descriptor for the new backing storage"), + SD_VARLINK_DEFINE_INPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Identifier of the existing binding whose backing is being replaced (as supplied to AddStorage)"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); + static SD_VARLINK_DEFINE_ERROR(NotConnected); static SD_VARLINK_DEFINE_ERROR(NotSupported); static SD_VARLINK_DEFINE_ERROR(NoSuchStorage); @@ -68,6 +75,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_AddStorage, SD_VARLINK_SYMBOL_COMMENT("Detach a previously-attached storage volume from the running machine"), &vl_method_RemoveStorage, + SD_VARLINK_SYMBOL_COMMENT("Replace the backing of a previously-attached storage volume in place"), + &vl_method_ReplaceStorage, 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"), From b9155f789c280b8ca19390fb742fde8f1776ef77 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:52:07 +0200 Subject: [PATCH 1631/2155] vmspawn: implement io.systemd.MachineInstance.ReplaceStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire up the runtime hot-swap Varlink method. The signature mirrors AddStorage minus 'config': the device frontend (virtio-blk, virtio-scsi, nvme, scsi-cd) doesn't change, only the backing file behind it. Read-only/read-write may flip based on the new fd's O_ACCMODE; scsi-cd is forced read-only to match the boot-time policy. QMP sequence (entry: vmspawn_qmp_replace_block_device): add-fd → on_replace_observe_stage blockdev-add (new file) → on_replace_blockdev_add_complete remove-fd (new fdset) → on_replace_observe_stage blockdev-reopen (format) → on_replace_blockdev_reopen_complete [commit + fire trailing del] blockdev-del (old file) → on_replace_old_blockdev_del_complete The reopen options must be a superset of every option that qmp_build_blockdev_add_format() may emit, otherwise reopen rejects 'Cannot reset option X to default'. The 'file' field is a string reference to the new file node — case 3 of the schema in qemu/qapi/block-core.json:5034-5040 ("the current child is replaced with that other node"). The format node's qmp_node_name is preserved so the device frontend's drive= binding does not move. ReplaceCtx tracks the per-call state with a refcount mirroring the add-stage drive-info pattern. On any pre-commit failure replace_fail tears down whatever new-side state we created on the wire and replies on drive->link via reply_qmp_error (disconnect → NotConnected). On post-commit del failure we log a warning, leak the orphan, and reply success — the swap itself succeeded and the leak resolves at VM exit. file_generation is bumped before issuing blockdev-add so failed attempts cannot collide on node-name when the user retries. Errors: NoSuchStorage - drive not in the registry StorageImmutable - drive lacks QMP_DRIVE_REMOVABLE (boot-time) EBUSY - add still pending or another replace/remove in flight NotConnected - QMP transport disconnect during the chain EIO - QEMU rejected blockdev-reopen Also gates RemoveStorage on REPLACE_PENDING so a device_del cannot race a mid-flight blockdev-reopen on the same drive. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 360 +++++++++++++++++++++++++++++++++- src/vmspawn/vmspawn-qmp.h | 9 + src/vmspawn/vmspawn-varlink.c | 63 +++++- 3 files changed, 428 insertions(+), 4 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index d2cbb67e79ebb..7dc58a8ccb1d2 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -906,7 +906,6 @@ int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { return log_oom(); if (asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0) return log_oom(); - drive->file_generation = 0; if (asprintf(&drive->qmp_file_node_name, "vmspawn-%" PRIu64 "-file-%" PRIu64, drive->counter, drive->file_generation) < 0) return log_oom(); @@ -1067,8 +1066,8 @@ int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, return sd_varlink_error(link, "io.systemd.MachineInstance.StorageImmutable", NULL); 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); + if (drive->state & (BLOCK_DEVICE_STATE_REMOVE_PENDING|BLOCK_DEVICE_STATE_REPLACE_PENDING)) + return reply_qmp_error(link, "Block device replace/remove 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)); @@ -1114,6 +1113,361 @@ int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_varian return 0; } +typedef enum ReplaceCtxStateFlags { + REPLACE_CTX_FAILED = 1u << 0, /* idempotency sentinel */ + REPLACE_CTX_NEW_BLOCKDEV_ADDED = 1u << 1, /* blockdev-add(new file) ack'd */ + REPLACE_CTX_REOPEN_COMMITTED = 1u << 2, /* blockdev-reopen ack'd; new state moved onto DriveInfo */ +} ReplaceCtxStateFlags; + +/* Bits ReplaceStorage may change; others are preserved across replace. */ +#define QMP_DRIVE_REPLACE_MUTABLE_MASK \ + (QMP_DRIVE_BLOCK_DEVICE | QMP_DRIVE_READ_ONLY | QMP_DRIVE_IO_URING) + +/* Per-ReplaceStorage state. One ref per outstanding QMP callback plus one for + * the entry-point local (until TAKE_PTR'd into the chain). On commit the + * contents are folded into DriveInfo. */ +typedef struct ReplaceCtx { + unsigned n_ref; + + DriveInfo *drive; /* ref'd */ + char *new_file_node_name; + char *new_fdset_path; + uint64_t new_fdset_id; + QmpDriveFlags new_flags; + + char *old_file_node_name; + + ReplaceCtxStateFlags state; +} ReplaceCtx; + +static ReplaceCtx* replace_ctx_free(ReplaceCtx *ctx) { + if (!ctx) + return NULL; + + drive_info_unref(ctx->drive); + free(ctx->new_file_node_name); + free(ctx->new_fdset_path); + free(ctx->old_file_node_name); + return mfree(ctx); +} + +DEFINE_PRIVATE_TRIVIAL_REF_FUNC(ReplaceCtx, replace_ctx); +DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(ReplaceCtx, replace_ctx, replace_ctx_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(ReplaceCtx*, replace_ctx_unref); + +/* First-error handler for the replace pipeline. Idempotent. Best-effort tears + * down whatever new-side state we created on the wire, clears REPLACE_PENDING, + * and replies on drive->link (if any). */ +static int replace_fail(ReplaceCtx *ctx, int error, const char *error_desc) { + assert(ctx); + + if (FLAGS_SET(ctx->state, REPLACE_CTX_FAILED)) + return 0; + ctx->state |= REPLACE_CTX_FAILED; + + DriveInfo *drive = ctx->drive; + assert(drive); + + /* If the new file node was added, del it; that also drops the fdset's + * dup so the new fdset auto-frees. If add-fd succeeded but blockdev-add + * never did, an explicit remove-fd is needed instead. */ + if (FLAGS_SET(ctx->state, REPLACE_CTX_NEW_BLOCKDEV_ADDED)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->new_file_node_name)) >= 0) + (void) qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, "blockdev-del", + QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "replace rollback blockdev-del"); + } else if (ctx->new_fdset_path) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", ctx->new_fdset_id)) >= 0) + (void) qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, "remove-fd", + QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "replace rollback remove-fd"); + } + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + return reply_qmp_error(link, error_desc, error); + return 0; +} + +static int on_replace_observe_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + return 0; +} + +static int on_replace_blockdev_add_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + + /* If a sync error elsewhere has already marked the chain failed, the + * just-added file node is orphaned — tear it down retroactively. */ + if (FLAGS_SET(ctx->state, REPLACE_CTX_FAILED)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->new_file_node_name)) >= 0) + (void) qmp_client_invoke(ctx->drive->bridge->qmp, /* ret_slot= */ NULL, + "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, + (void*) "replace retroactive blockdev-del"); + return 0; + } + + ctx->state |= REPLACE_CTX_NEW_BLOCKDEV_ADDED; + return 0; +} + +static int on_replace_old_blockdev_del_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + DriveInfo *drive = ctx->drive; + + /* The swap itself succeeded at reopen-commit time. If del of the old + * file node failed, the orphan persists until VM exit — log and reply + * success. The fdset auto-freed when the dup was released regardless. */ + if (error < 0) + log_warning("Failed to delete orphaned file node '%s' after replace: %s", + ctx->old_file_node_name, strna(error_desc)); + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + (void) sd_varlink_reply(link, NULL); + + log_info("Block device '%s' backing replaced", drive->id); + return 0; +} + +static int on_replace_blockdev_reopen_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + + DriveInfo *drive = ctx->drive; + + /* Atomic commit: the format graph now references the new file node. + * Move the new-side state from ctx onto DriveInfo so subsequent + * teardowns find the right names. */ + ctx->state |= REPLACE_CTX_REOPEN_COMMITTED; + free_and_replace(drive->qmp_file_node_name, ctx->new_file_node_name); + free_and_replace(drive->fdset_path, ctx->new_fdset_path); + drive->fdset_id = ctx->new_fdset_id; + /* Only commit the mutable bits so unrelated future flags aren't silently flipped. */ + drive->flags = (drive->flags & ~QMP_DRIVE_REPLACE_MUTABLE_MASK) | + (ctx->new_flags & QMP_DRIVE_REPLACE_MUTABLE_MASK); + + /* Trailing blockdev-del of the OLD file node. The format no longer + * references it, so it's an orphan; deleting it also drops the dup + * that kept the old fdset alive. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *del_args = NULL; + int r = sd_json_buildo(&del_args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->old_file_node_name)); + if (r >= 0) { + _cleanup_(replace_ctx_unrefp) ReplaceCtx *slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, + "blockdev-del", QMP_CLIENT_ARGS(del_args), + on_replace_old_blockdev_del_complete, slot_ref); + if (r >= 0) { + TAKE_PTR(slot_ref); + return 0; + } + } + + /* Couldn't even queue blockdev-del. The swap succeeded; reply success + * and leave the orphan to clean up at VM exit. */ + log_warning_errno(r, "Failed to queue blockdev-del for orphaned file node '%s': %m", + ctx->old_file_node_name); + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + (void) sd_varlink_reply(link, NULL); + return 0; +} + +int vmspawn_qmp_replace_block_device( + VmspawnQmpBridge *bridge, + sd_varlink *link, + const char *id, + int fd, + QmpDriveFlags fd_flags) { + + _cleanup_close_ int owned_fd = fd; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *file_args = NULL, *reopen_args = NULL; + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = NULL; + /* Not _cleanup_'d: aliasing it with ctx tripped a gcc-12 + * -Wuse-after-free false positive on the cleanup chain. Error paths + * unref it explicitly; success leaks the ref to the callback. */ + ReplaceCtx *slot_ref; + int r; + + assert(bridge); + assert(link); + assert(id); + assert(fd >= 0); + + DriveInfo *drive = hashmap_get(bridge->block_devices, id); + if (!drive) + return sd_varlink_error(link, "io.systemd.MachineInstance.NoSuchStorage", NULL); + if (!FLAGS_SET(drive->flags, QMP_DRIVE_REMOVABLE)) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageImmutable", NULL); + /* QEMU's blockdev-reopen rejects RW->RO on a node with attached writers + * (the guest device). For an RW drive the new backing must be writable. */ + if (!FLAGS_SET(drive->flags, QMP_DRIVE_READ_ONLY) && FLAGS_SET(fd_flags, QMP_DRIVE_READ_ONLY)) + return sd_varlink_error_errno(link, -EROFS); + if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return reply_qmp_error(link, "Block device add pending", -EBUSY); + if (drive->state & (BLOCK_DEVICE_STATE_REMOVE_PENDING|BLOCK_DEVICE_STATE_REPLACE_PENDING)) + return reply_qmp_error(link, "Block device replace/remove pending", -EBUSY); + assert(!drive->link); + assert(drive->qmp_file_node_name); + + /* Bump generation EARLY so failed attempts don't collide on retry. */ + uint64_t new_gen = ++drive->file_generation; + + _cleanup_free_ char *new_file_node_name = NULL; + if (asprintf(&new_file_node_name, "vmspawn-%" PRIu64 "-file-%" PRIu64, + drive->counter, new_gen) < 0) + return sd_varlink_error_errno(link, -ENOMEM); + + /* Compute new flags: keep the existing drive flags, swap in the + * caller-derived bits (only RO and BLOCK_DEVICE are caller-controlled), + * fold scsi-cd into RO, and fold in the bridge's io_uring feature. */ + const QmpDriveFlags FD_DERIVED_MASK = QMP_DRIVE_READ_ONLY | QMP_DRIVE_BLOCK_DEVICE; + QmpDriveFlags new_flags = (drive->flags & ~QMP_DRIVE_REPLACE_MUTABLE_MASK) | + (fd_flags & FD_DERIVED_MASK); + if (drive->disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) + new_flags |= QMP_DRIVE_READ_ONLY; + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + new_flags |= QMP_DRIVE_IO_URING; + + ctx = new0(ReplaceCtx, 1); + if (!ctx) + return sd_varlink_error_errno(link, -ENOMEM); + ctx->n_ref = 1; + ctx->drive = drive_info_ref(drive); + ctx->new_file_node_name = TAKE_PTR(new_file_node_name); + ctx->new_flags = new_flags; + ctx->old_file_node_name = strdup(drive->qmp_file_node_name); + if (!ctx->old_file_node_name) + return sd_varlink_error_errno(link, -ENOMEM); + + drive->link = sd_varlink_ref(link); + drive->state |= BLOCK_DEVICE_STATE_REPLACE_PENDING; + + /* 1. add-fd → new fdset */ + slot_ref = replace_ctx_ref(ctx); + r = qmp_fdset_add(bridge->qmp, TAKE_FD(owned_fd), + on_replace_observe_stage, slot_ref, + &ctx->new_fdset_path, &ctx->new_fdset_id); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 2. blockdev-add (new file node, new fdset) */ + QmpFileNodeParams file_params = { + .node_name = ctx->new_file_node_name, + .filename = ctx->new_fdset_path, + .driver = FLAGS_SET(new_flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = new_flags, + }; + r = qmp_build_blockdev_add_file(&file_params, &file_args); + if (r < 0) + goto rollback_sync; + + slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(file_args), + on_replace_blockdev_add_complete, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 3. remove-fd (new fdset; blockdev-add holds the dup) */ + slot_ref = replace_ctx_ref(ctx); + r = qmp_fdset_remove(bridge->qmp, ctx->new_fdset_id, + on_replace_observe_stage, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 4. blockdev-reopen the format node, file → new + * NB: the option set must be a superset of every field + * qmp_build_blockdev_add_format() may emit; otherwise reopen rejects + * "Cannot reset option X to default" or silently flips a flag. + * No "backing" field: only ephemeral overlays carry backing, and + * those are never REMOVABLE. */ + r = sd_json_buildo(&reopen_args, + SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("node-name", drive->qmp_node_name), + SD_JSON_BUILD_PAIR_STRING("driver", drive->format), + SD_JSON_BUILD_PAIR_STRING("file", ctx->new_file_node_name), + /* blockdev-reopen resets unspecified options to driver defaults, + * so emit the format-agnostic options unconditionally. */ + SD_JSON_BUILD_PAIR_BOOLEAN("read-only", FLAGS_SET(new_flags, QMP_DRIVE_READ_ONLY)), + SD_JSON_BUILD_PAIR_STRING("discard", + FLAGS_SET(new_flags, QMP_DRIVE_DISCARD) ? "unmap" : "ignore"), + /* qcow2-only option; raw rejects it as an unknown property. */ + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(new_flags, QMP_DRIVE_DISCARD_NO_UNREF), + "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)))))); + if (r < 0) + goto rollback_sync; + + slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-reopen", QMP_CLIENT_ARGS(reopen_args), + on_replace_blockdev_reopen_complete, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + return 0; + +rollback_sync: + /* Mark failed so any in-flight callbacks observe the failure and + * rollback their just-added state retroactively. */ + ctx->state |= REPLACE_CTX_FAILED; + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + drive->link = sd_varlink_unref(drive->link); + return sd_varlink_error_errno(link, r); +} + 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 dca0b03b10a82..f627c663757fe 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -80,6 +80,7 @@ typedef enum BlockDeviceStateFlags { 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 */ BLOCK_DEVICE_STATE_FILE_NODE_ADDED = 1u << 3, /* blockdev-add(file) succeeded; teardown must del it */ + BLOCK_DEVICE_STATE_REPLACE_PENDING = 1u << 4, /* blockdev-reopen pipeline in flight */ } BlockDeviceStateFlags; /* Ref-counted; each of the four add-stage QMP slots holds one ref. @@ -184,4 +185,12 @@ int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *vi int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock); int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive); int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id); +/* fd_flags encodes the new fd's properties: only QMP_DRIVE_READ_ONLY and + * QMP_DRIVE_BLOCK_DEVICE are caller-controlled; other bits are ignored. */ +int vmspawn_qmp_replace_block_device( + VmspawnQmpBridge *bridge, + sd_varlink *link, + const char *id, + int fd, + QmpDriveFlags fd_flags); 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 a782a5c9c1a4f..cb56a5a597996 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include + #include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" @@ -7,6 +10,7 @@ #include "log.h" #include "path-util.h" #include "qmp-client.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "varlink-io.systemd.MachineInstance.h" @@ -241,6 +245,62 @@ static int vl_method_remove_storage(sd_varlink *link, sd_json_variant *parameter return vmspawn_qmp_remove_block_device(ctx->bridge, link, p.name); } +static int vl_method_replace_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + int fd_index; + const char *name; + } p = { + .fd_index = -1, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd_index), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (isempty(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (p.fd_index < 0) + return sd_varlink_error_invalid_parameter_name(link, "fileDescriptorIndex"); + + _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_index); + if (fd < 0) + return sd_varlink_error_errno(link, fd); + + struct stat st; + if (fstat(fd, &st) < 0) + return sd_varlink_error_errno(link, -errno); + r = stat_verify_regular_or_block(&st); + if (r < 0) + return sd_varlink_error_errno(link, r); + + int oflags = fcntl(fd, F_GETFL); + if (oflags < 0) + return sd_varlink_error_errno(link, -errno); + if (FLAGS_SET(oflags, O_PATH)) + return sd_varlink_error_errno(link, -EBADF); + if ((oflags & O_ACCMODE_STRICT) == O_WRONLY) + return sd_varlink_error_errno(link, -EBADF); + + QmpDriveFlags fd_flags = 0; + if (S_ISBLK(st.st_mode)) + fd_flags |= QMP_DRIVE_BLOCK_DEVICE; + if ((oflags & O_ACCMODE_STRICT) == O_RDONLY) + fd_flags |= QMP_DRIVE_READ_ONLY; + + return vmspawn_qmp_replace_block_device(ctx->bridge, link, p.name, TAKE_FD(fd), fd_flags); + /* Async reply via on_replace_old_blockdev_del_complete or replace_fail. */ +} + 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; @@ -478,7 +538,8 @@ int vmspawn_varlink_setup( "io.systemd.MachineInstance.Describe", vl_method_describe, "io.systemd.MachineInstance.SubscribeEvents", vl_method_subscribe_events, "io.systemd.MachineInstance.AddStorage", vl_method_add_storage, - "io.systemd.MachineInstance.RemoveStorage", vl_method_remove_storage); + "io.systemd.MachineInstance.RemoveStorage", vl_method_remove_storage, + "io.systemd.MachineInstance.ReplaceStorage", vl_method_replace_storage); if (r < 0) return log_error_errno(r, "Failed to bind varlink methods: %m"); From 1360117a6497b7b9301a81be2070f645bc8f4bc9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:55:11 +0200 Subject: [PATCH 1632/2155] test: integration test for io.systemd.MachineInstance.ReplaceStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modelled on TEST-87-AUX-UTILS-VM.bind-volume.sh. Boots vmspawn with one boot-time bind-volume, hot-adds a runtime volume via machinectl bind-volume, then exercises ReplaceStorage: 1. happy-path replace of a runtime drive 2. successive replace (verify file_generation rotation — no node-name collisions on the second swap) 3. replace of the boot-time drive must fail with StorageImmutable 4. replace of an unknown name must fail with NoSuchStorage 5. invalid name (no provider:volume separator) must fail with InvalidParameter 6. unbind-volume after replace must succeed — proves the new file node is monitor-owned and the format-then-file teardown order in vmspawn_qmp_block_device_teardown() correctly cleans up both blockdev nodes Pushes the new backing file via varlinkctl --push-fd; the file is a plain truncate'd image. Auto-discovered by run_subtests in TEST-87-AUX-UTILS-VM.sh. Signed-off-by: Christian Brauner (Amutable) --- .../TEST-87-AUX-UTILS-VM.replace-storage.sh | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh diff --git a/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh b/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh new file mode 100755 index 0000000000000..22ff57adf8338 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test io.systemd.MachineInstance.ReplaceStorage — runtime hot-swap of an +# attached storage volume's backing file via QMP blockdev-reopen. +# +# Exercises: +# - happy-path replace of a runtime-attached drive +# - successive replaces (file_generation rotation, no node-name collisions) +# - StorageImmutable rejection for boot-time attached volumes +# - NoSuchStorage rejection for unknown names +# - clean RemoveStorage after a replace (proves both old and new file nodes +# are monitor-owned and properly cleaned up) +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 ! command -v storagectl >/dev/null 2>&1; then + echo "storagectl 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 + +if ! test -S /run/systemd/io.systemd.StorageProvider/fs; then + echo "StorageProvider fs socket not found, skipping" + exit 0 +fi + +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 + +WORKDIR="$(mktemp -d /tmp/test-replace-storage.XXXXXXXXXX)" + +at_exit() { + set +e + if [[ -n "${MACHINE:-}" ]]; then + if machinectl status "$MACHINE" &>/dev/null; then + machinectl terminate "$MACHINE" 2>/dev/null + timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + fi + [[ -n "${VMSPAWN_PID:-}" ]] && { kill "$VMSPAWN_PID" 2>/dev/null; wait "$VMSPAWN_PID" 2>/dev/null; } + rm -rf "$WORKDIR" + rm -f /var/lib/storage/test-replace-storage-*.volume +} +trap at_exit EXIT + +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" + +BOOT_VOL="test-replace-storage-boot-$$" +RUNTIME_VOL="test-replace-storage-runtime-$$" + +# Backing files for ReplaceStorage. Regular files; --push-fd opens read-only. +truncate -s 32M "$WORKDIR/new-backing-1.raw" +truncate -s 32M "$WORKDIR/new-backing-2.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 77 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +MACHINE="test-replace-storage-$$" +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --bind-volume="fs:${BOOT_VOL}::create=new,size=64M,template=sparse-file" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered" + +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# --- Hot-add a runtime volume (target for ReplaceStorage) --- +# virtio-scsi: vmspawn's hot-add path only allocates a PCIe root port for the +# scsi controller; bare virtio-blk hot-add fails on QEMU builds that don't +# auto-pick a free slot. Same backend code path either way. +machinectl bind-volume "$MACHINE" \ + "fs:${RUNTIME_VOL}:virtio-scsi:create=new,size=32M,template=sparse-file" +echo "Hot-added runtime bind-volume" + +# varlinkctl --push-fd= opens O_RDONLY; the runtime drive is RW so the +# server rejects an RO fd with EROFS. Open RW via bash and pass the numeric fd. + +# --- Test 1: happy-path replace --- +exec {NEW_FD}<>"$WORKDIR/new-backing-1.raw" +varlinkctl --push-fd="$NEW_FD" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" +exec {NEW_FD}<&- +echo "Replace #1 succeeded" + +# --- Test 2: replace again (verify file_generation rotation) --- +exec {NEW_FD}<>"$WORKDIR/new-backing-2.raw" +varlinkctl --push-fd="$NEW_FD" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" +exec {NEW_FD}<&- +echo "Replace #2 succeeded" + +# --- Test 3: replace boot-time drive must fail with StorageImmutable --- +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${BOOT_VOL}\"}" 2>"$WORKDIR/replace-immutable.err"; then + echo "ERROR: ReplaceStorage of boot-time drive should have failed" + cat "$WORKDIR/replace-immutable.err" + exit 1 +fi +grep StorageImmutable "$WORKDIR/replace-immutable.err" >/dev/null +echo "Boot-time drive correctly rejected with StorageImmutable" + +# --- Test 4: replace non-existent name must fail with NoSuchStorage --- +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:does-not-exist-$$\"}" 2>"$WORKDIR/replace-nosuch.err"; then + echo "ERROR: ReplaceStorage of non-existent drive should have failed" + cat "$WORKDIR/replace-nosuch.err" + exit 1 +fi +grep NoSuchStorage "$WORKDIR/replace-nosuch.err" >/dev/null +echo "Non-existent drive correctly rejected with NoSuchStorage" + +# --- Test 5: RO fd to RW drive must fail with EROFS --- +# varlinkctl --push-fd= opens RO; runtime drive is RW. +# Capture both stdout and stderr: errnoName "EROFS" is in the JSON reply on +# stdout; stderr only carries the human-readable strerror. +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" &>"$WORKDIR/replace-rofs.err"; then + echo "ERROR: ReplaceStorage with RO fd should have failed" + cat "$WORKDIR/replace-rofs.err" + exit 1 +fi +grep EROFS "$WORKDIR/replace-rofs.err" >/dev/null +echo "RO fd to RW drive correctly rejected with EROFS" + +# --- Test 6: unbind after replace (proves new file node is monitor-owned and +# the format-then-file teardown order correctly cleans up both nodes) --- +machinectl unbind-volume "$MACHINE" "fs:${RUNTIME_VOL}" +echo "Unbind after replace succeeded (cleanup of both nodes works)" + +machinectl terminate "$MACHINE" +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +echo "All ReplaceStorage tests passed" From 968d189414b706e0515962e5a1b57d6c954d190e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 12 May 2026 12:42:10 +0200 Subject: [PATCH 1633/2155] vmspawn: reject O_PATH and O_WRONLY fds in AddStorage An fd opened O_PATH cannot be read, and an O_WRONLY fd cannot serve as a backing file for a virtual disk image. Reject both at the bind-volume entry point with -EBADF instead of letting the request proceed to QMP where QEMU's file backend would fail to read from the fd. The ReplaceStorage entry point grew the same checks in parallel. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-bind-volume.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vmspawn/vmspawn-bind-volume.c b/src/vmspawn/vmspawn-bind-volume.c index d67fc61de02e5..8466cd199e976 100644 --- a/src/vmspawn/vmspawn-bind-volume.c +++ b/src/vmspawn/vmspawn-bind-volume.c @@ -124,6 +124,14 @@ int vmspawn_bind_volume_attach_fd( if (r < 0) return r; + int oflags = fcntl(owned_fd, F_GETFL); + if (oflags < 0) + return -errno; + if (FLAGS_SET(oflags, O_PATH)) + return -EBADF; + if ((oflags & O_ACCMODE_STRICT) == O_WRONLY) + return -EBADF; + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); if (!d) return -ENOMEM; @@ -135,10 +143,6 @@ int vmspawn_bind_volume_attach_fd( if (!d->id || !d->disk_driver || !d->format || !d->path) return -ENOMEM; - int oflags = fcntl(owned_fd, F_GETFL); - if (oflags < 0) - return -errno; - d->disk_type = dt; d->fd = TAKE_FD(owned_fd); if (S_ISBLK(st.st_mode)) From 91575e3a5ad4fe107d2e73e5b790d4b8d0069d6d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 12 May 2026 12:54:13 +0200 Subject: [PATCH 1634/2155] varlink-io.systemd.MachineInstance,vmspawn: treat AddStorage/RemoveStorage name as opaque The 'name' field on AddStorage and RemoveStorage was documented as ':' and enforced via machine_storage_name_split() at the varlink boundary. That form is only the convention machinectl inherits from the StorageProvider routing path; the API itself only needs a unique identifier the caller can re-use to detach the binding. Drop the strict format check, require only a non-empty string, and update the IDL docs to describe the field as a caller-supplied identifier with machinectl's convention as a non-normative example. Signed-off-by: Christian Brauner (Amutable) --- src/shared/varlink-io.systemd.MachineInstance.c | 4 ++-- src/vmspawn/vmspawn-varlink.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/varlink-io.systemd.MachineInstance.c b/src/shared/varlink-io.systemd.MachineInstance.c index b496ea565c481..8aad8babb98f7 100644 --- a/src/shared/varlink-io.systemd.MachineInstance.c +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -29,14 +29,14 @@ static SD_VARLINK_DEFINE_METHOD( AddStorage, SD_VARLINK_FIELD_COMMENT("Index of the attached file descriptor for the storage volume"), SD_VARLINK_DEFINE_INPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), - SD_VARLINK_FIELD_COMMENT("Unique storage name of the form ':' identifying this binding for later removal"), + SD_VARLINK_FIELD_COMMENT("Caller-supplied identifier for this binding (any non-empty string; machinectl uses ':' from the StorageProvider, but the form is not required)"), SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("Backend-specific configuration"), SD_VARLINK_DEFINE_INPUT(config, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( RemoveStorage, - SD_VARLINK_FIELD_COMMENT("Unique storage name ':' to detach"), + SD_VARLINK_FIELD_COMMENT("Identifier of the binding to detach (as supplied to AddStorage)"), SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_METHOD( diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index cb56a5a597996..ebfdd878761bd 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -197,7 +197,7 @@ static int vl_method_add_storage(sd_varlink *link, sd_json_variant *parameters, if (r != 0) return r; - if (machine_storage_name_split(p.name, /* ret_provider= */ NULL, /* ret_volume= */ NULL) < 0) + if (isempty(p.name)) return sd_varlink_error_invalid_parameter_name(link, "name"); if (disk_type_from_bind_volume_config(p.config) < 0) @@ -239,7 +239,7 @@ static int vl_method_remove_storage(sd_varlink *link, sd_json_variant *parameter if (r != 0) return r; - if (machine_storage_name_split(p.name, /* ret_provider= */ NULL, /* ret_volume= */ NULL) < 0) + if (isempty(p.name)) return sd_varlink_error_invalid_parameter_name(link, "name"); return vmspawn_qmp_remove_block_device(ctx->bridge, link, p.name); From 5cba9704ad4c1ee372fa6ab3f62f85670162151b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 11 May 2026 20:51:47 +0200 Subject: [PATCH 1635/2155] report: drop MetricsFamilyContext, CGroupContext, CGroupInfo Previously, we passed around information about the MetricFamily'ies and the varlink connection in a helper structure. Having a hybrid of const static and runtime stuff is iffy. Let's simplify things by passing two separate parameters. Also, in report-cgroup.c we built a cache of parsed values. This requires additional storage requirements and introduces complexity when dealing with population of the cache at the appropriate time. This cache is not useful: for each cgroup, we generate a list of metrics, and we have all the information at hand. The only reason why we'd create the cache and not generate all the relevant replies at once was that the helper functions called the .generate function for each MetricFamily separately. The MetricFamily interface is changed, so that metrics can be defined without a .generate function. This is understood to mean that the preceding metric family's .generate function will also genarate this family. This allows us to define related metrics nicely in a table: { METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage", generate_func }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", NULL }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", NULL }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "SomethingElse", generate_func2 }, ... When implementing .Describe, we list all the families. When implementing .List, we only call those with .generate, and we get the same results as before. This allows the .generate functions to be simplified: instead of keeping state, they just spit out all the metrics for a given object in a tight loop. --- src/core/varlink-metrics.c | 118 +++-- src/network/networkd-varlink-metrics.c | 53 ++- src/report/report-basic.c | 88 ++-- src/report/report-cgroup-server.c | 8 +- src/report/report-cgroup.c | 633 +++++++++---------------- src/report/report-cgroup.h | 11 - src/shared/metrics.c | 73 +-- src/shared/metrics.h | 15 +- 8 files changed, 430 insertions(+), 569 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index f1ac0791bc914..a470341c2b182 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -11,13 +11,14 @@ #include "unit.h" #include "varlink-metrics.h" -static int active_timestamp_build_json(MetricFamilyContext *context, void *userdata) { +static int active_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); _cleanup_(sd_json_variant_unrefp) sd_json_variant *enter_fields = NULL; r = sd_json_buildo(&enter_fields, SD_JSON_BUILD_PAIR_STRING("event", "enter")); @@ -35,7 +36,8 @@ static int active_timestamp_build_json(MetricFamilyContext *context, void *userd continue; r = metric_build_send_unsigned( - context, + mf, + vl, unit->id, unit->active_enter_timestamp.realtime, enter_fields); @@ -43,7 +45,8 @@ static int active_timestamp_build_json(MetricFamilyContext *context, void *userd return r; r = metric_build_send_unsigned( - context, + mf, + vl, unit->id, unit->active_exit_timestamp.realtime, exit_fields); @@ -54,13 +57,14 @@ static int active_timestamp_build_json(MetricFamilyContext *context, void *userd return 0; } -static int inactive_exit_timestamp_build_json(MetricFamilyContext *context, void *userdata) { +static int inactive_exit_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -68,7 +72,8 @@ static int inactive_exit_timestamp_build_json(MetricFamilyContext *context, void continue; r = metric_build_send_unsigned( - context, + mf, + vl, unit->id, unit->inactive_exit_timestamp.realtime, /* fields= */ NULL); @@ -79,13 +84,14 @@ static int inactive_exit_timestamp_build_json(MetricFamilyContext *context, void return 0; } -static int state_change_timestamp_build_json(MetricFamilyContext *context, void *userdata) { +static int state_change_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -93,7 +99,8 @@ static int state_change_timestamp_build_json(MetricFamilyContext *context, void continue; r = metric_build_send_unsigned( - context, + mf, + vl, unit->id, unit->state_change_timestamp.realtime, /* fields= */ NULL); @@ -104,15 +111,17 @@ static int state_change_timestamp_build_json(MetricFamilyContext *context, void return 0; } -static int status_errno_build_json(MetricFamilyContext *context, void *userdata) { +static int status_errno_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; - assert(context); + assert(mf && mf->name); + assert(vl); LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { r = metric_build_send_unsigned( - context, + mf, + vl, unit->id, (uint64_t) SERVICE(unit)->status_errno, /* fields= */ NULL); @@ -123,13 +132,14 @@ static int status_errno_build_json(MetricFamilyContext *context, void *userdata) return 0; } -static int unit_active_state_build_json(MetricFamilyContext *context, void *userdata) { +static int unit_active_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -137,7 +147,8 @@ static int unit_active_state_build_json(MetricFamilyContext *context, void *user continue; r = metric_build_send_string( - context, + mf, + vl, unit->id, unit_active_state_to_string(unit_active_state(unit)), /* fields= */ NULL); @@ -148,13 +159,14 @@ static int unit_active_state_build_json(MetricFamilyContext *context, void *user return 0; } -static int unit_load_state_build_json(MetricFamilyContext *context, void *userdata) { +static int unit_load_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -162,7 +174,8 @@ static int unit_load_state_build_json(MetricFamilyContext *context, void *userda continue; r = metric_build_send_string( - context, + mf, + vl, unit->id, unit_load_state_to_string(unit->load_state), /* fields= */ NULL); @@ -173,15 +186,20 @@ static int unit_load_state_build_json(MetricFamilyContext *context, void *userda return 0; } -static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) { +static int nrestarts_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; - assert(context); + assert(mf && mf->name); + assert(vl); LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { r = metric_build_send_unsigned( - context, unit->id, SERVICE(unit)->n_restarts, /* fields= */ NULL); + mf, + vl, + unit->id, + SERVICE(unit)->n_restarts, + /* fields= */ NULL); if (r < 0) return r; } @@ -189,23 +207,26 @@ static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) { return 0; } -static int reload_count_build_json(MetricFamilyContext *context, void *userdata) { +static int reload_count_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); - assert(context); + assert(mf && mf->name); + assert(vl); return metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, manager->reload_count, /* fields= */ NULL); } -static int units_by_type_total_build_json(MetricFamilyContext *context, void *userdata) { +static int units_by_type_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; - assert(context); + assert(mf && mf->name); + assert(vl); for (UnitType type = 0; type < _UNIT_TYPE_MAX; type++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; @@ -219,7 +240,8 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us return r; r = metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, counter, fields); @@ -230,14 +252,15 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us return 0; } -static int units_by_state_total_build_json(MetricFamilyContext *context, void *userdata) { +static int units_by_state_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); uint64_t counters[_UNIT_ACTIVE_STATE_MAX] = {}; Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); /* TODO need a rework probably with state counter */ HASHMAP_FOREACH_KEY(unit, key, manager->units) { @@ -256,7 +279,8 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return r; r = metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, counters[state], fields); @@ -267,38 +291,43 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return 0; } -static int jobs_queued_build_json(MetricFamilyContext *context, void *userdata) { +static int jobs_queued_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); - assert(context); + assert(mf && mf->name); + assert(vl); return metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, hashmap_size(manager->jobs), /* fields= */ NULL); } -static int system_state_build_json(MetricFamilyContext *context, void *userdata) { +static int system_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); - assert(context); + assert(mf && mf->name); + assert(vl); return metric_build_send_string( - context, + mf, + vl, /* object= */ NULL, manager_state_to_string(manager_state(manager)), /* fields= */ NULL); } -static int units_by_load_state_total_build_json(MetricFamilyContext *context, void *userdata) { +static int units_by_load_state_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); uint64_t counters[_UNIT_LOAD_STATE_MAX] = {}; Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -316,7 +345,8 @@ static int units_by_load_state_total_build_json(MetricFamilyContext *context, vo return r; r = metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, counters[state], fields); @@ -327,13 +357,14 @@ static int units_by_load_state_total_build_json(MetricFamilyContext *context, vo return 0; } -static int units_total_build_json(MetricFamilyContext *context, void *userdata) { +static int units_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); uint64_t count = 0; Unit *unit; char *key; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -344,7 +375,8 @@ static int units_total_build_json(MetricFamilyContext *context, void *userdata) } return metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, count, /* fields= */ NULL); diff --git a/src/network/networkd-varlink-metrics.c b/src/network/networkd-varlink-metrics.c index 50aacebbf8077..ee5645314cdd2 100644 --- a/src/network/networkd-varlink-metrics.c +++ b/src/network/networkd-varlink-metrics.c @@ -19,7 +19,8 @@ typedef const char* (*link_metric_extractor_t)(const Link *link); static int link_metric_build_json( - MetricFamilyContext *context, + const MetricFamily *mf, + sd_varlink *vl, link_metric_extractor_t extractor, void *userdata) { @@ -27,11 +28,12 @@ static int link_metric_build_json( Link *link; int r; - assert(context); + assert(mf && mf->name); + assert(vl); assert(extractor); HASHMAP_FOREACH(link, manager->links_by_index) { - r = metric_build_send_string(context, link->ifname, extractor(link), /* fields= */ NULL); + r = metric_build_send_string(mf, vl, link->ifname, extractor(link), /* fields= */ NULL); if (r < 0) return r; } @@ -63,50 +65,52 @@ static const char* link_get_oper_state(const Link *l) { return link_operstate_to_string(ASSERT_PTR(l)->operstate); } -static int link_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_address_state, userdata); +static int link_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_address_state, userdata); } -static int link_admin_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_admin_state, userdata); +static int link_admin_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_admin_state, userdata); } -static int link_carrier_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_carrier_state, userdata); +static int link_carrier_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_carrier_state, userdata); } -static int link_ipv4_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_ipv4_address_state, userdata); +static int link_ipv4_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_ipv4_address_state, userdata); } -static int link_ipv6_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_ipv6_address_state, userdata); +static int link_ipv6_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_ipv6_address_state, userdata); } -static int link_oper_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_oper_state, userdata); +static int link_oper_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_oper_state, userdata); } -static int managed_interfaces_build_json(MetricFamilyContext *context, void *userdata) { +static int managed_interfaces_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Link *link; uint64_t count = 0; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH(link, manager->links_by_index) if (link->network) count++; - return metric_build_send_unsigned(context, /* object= */ NULL, count, /* fields= */ NULL); + return metric_build_send_unsigned(mf, vl, /* object= */ NULL, count, /* fields= */ NULL); } -static int required_for_online_build_json(MetricFamilyContext *context, void *userdata) { +static int required_for_online_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Link *link; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH(link, manager->links_by_index) { if (!link->network) @@ -114,7 +118,8 @@ static int required_for_online_build_json(MetricFamilyContext *context, void *us if (link->network->required_for_online == 0) { r = metric_build_send_string( - context, + mf, + vl, link->ifname, "no", /* fields= */ NULL); @@ -127,7 +132,8 @@ static int required_for_online_build_json(MetricFamilyContext *context, void *us if (range.min == range.max) r = metric_build_send_string( - context, + mf, + vl, link->ifname, min_str, /* fields= */ NULL); @@ -137,7 +143,8 @@ static int required_for_online_build_json(MetricFamilyContext *context, void *us return -ENOMEM; r = metric_build_send_string( - context, + mf, + vl, link->ifname, value, /* fields= */ NULL); diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 50a4dfaaf1301..11ecb7b45c678 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -13,92 +13,102 @@ #include "report-basic.h" #include "virt.h" -static int architecture_generate(MetricFamilyContext *context, void *userdata) { - assert(context); +static int architecture_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, architecture_to_string(uname_architecture()), /* fields= */ NULL); } -static int boot_id_generate(MetricFamilyContext *context, void *userdata) { +static int boot_id_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { sd_id128_t id; int r; - assert(context); + assert(mf && mf->name); + assert(link); r = sd_id128_get_boot(&id); if (r < 0) return r; return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, SD_ID128_TO_STRING(id), /* fields= */ NULL); } -static int hostname_generate(MetricFamilyContext *context, void *userdata) { +static int hostname_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { _cleanup_free_ char *hostname = NULL; int r; - assert(context); + assert(mf && mf->name); + assert(link); r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname); if (r < 0) return r; return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, hostname, /* fields= */ NULL); } -static int kernel_version_generate(MetricFamilyContext *context, void *userdata) { +static int kernel_version_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { struct utsname u; - assert(context); + assert(mf && mf->name); + assert(link); assert_se(uname(&u) >= 0); return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, u.release, /* fields= */ NULL); } -static int machine_id_generate(MetricFamilyContext *context, void *userdata) { +static int machine_id_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { sd_id128_t id; int r; - assert(context); + assert(mf && mf->name); + assert(link); r = sd_id128_get_machine(&id); if (r < 0) return r; return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, SD_ID128_TO_STRING(id), /* fields= */ NULL); } -static int virtualization_generate(MetricFamilyContext *context, void *userdata) { - Virtualization v; +static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); - assert(context); - - v = detect_virtualization(); + Virtualization v = detect_virtualization(); if (v < 0) return v; return metric_build_send_string( - context, + mf, + link, /* object= */ NULL, virtualization_to_string(v), /* fields= */ NULL); @@ -107,39 +117,39 @@ static int virtualization_generate(MetricFamilyContext *context, void *userdata) static const MetricFamily metric_family_table[] = { /* Keep entries ordered alphabetically */ { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Architecture", - .description = "CPU architecture", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "Architecture", + "CPU architecture", + METRIC_FAMILY_TYPE_STRING, .generate = architecture_generate, }, { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "BootID", - .description = "Current boot ID", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "BootID", + "Current boot ID", + METRIC_FAMILY_TYPE_STRING, .generate = boot_id_generate, }, { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", - .description = "System hostname", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", + "System hostname", + METRIC_FAMILY_TYPE_STRING, .generate = hostname_generate, }, { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "KernelVersion", - .description = "Kernel version", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "KernelVersion", + "Kernel version", + METRIC_FAMILY_TYPE_STRING, .generate = kernel_version_generate, }, { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID", - .description = "Machine ID", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID", + "Machine ID", + METRIC_FAMILY_TYPE_STRING, .generate = machine_id_generate, }, { - .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", - .description = "Virtualization type", - .type = METRIC_FAMILY_TYPE_STRING, + METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", + "Virtualization type", + METRIC_FAMILY_TYPE_STRING, .generate = virtualization_generate, }, {} diff --git a/src/report/report-cgroup-server.c b/src/report/report-cgroup-server.c index 9f5ac9370694a..279edbb8d13ed 100644 --- a/src/report/report-cgroup-server.c +++ b/src/report/report-cgroup-server.c @@ -2,7 +2,6 @@ #include "sd-varlink.h" -#include "alloc-util.h" #include "build.h" #include "format-table.h" #include "help-util.h" @@ -15,14 +14,9 @@ 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); + r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c index 9a52c03d17741..48fcbb5a647be 100644 --- a/src/report/report-cgroup.c +++ b/src/report/report-cgroup.c @@ -16,256 +16,65 @@ #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 */ - uint64_t cpu_total_nsec; - uint64_t cpu_user_nsec; - uint64_t cpu_system_nsec; - int cpu_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; -} - /* Parse cpu.stat for a cgroup once, extracting usage_usec, user_usec and system_usec * in a single read so each scrape only opens the file once per cgroup. */ -static int cpu_stat_parse( - const char *cgroup_path, - uint64_t *ret_total_nsec, - uint64_t *ret_user_nsec, - uint64_t *ret_system_nsec) { - - char *values[3] = {}; - uint64_t total_us, user_us, system_us; +static int cpu_stat_parse(const char *cgroup_path, uint64_t ret[static 3]) { + char* strings[3] = {}; + CLEANUP_ELEMENTS(strings, free_many_charp); + uint64_t values[3]; int r; assert(cgroup_path); - assert(ret_total_nsec); - assert(ret_user_nsec); - assert(ret_system_nsec); r = cg_get_keyed_attribute( cgroup_path, "cpu.stat", STRV_MAKE("usage_usec", "user_usec", "system_usec"), - values); - if (r < 0) - return r; - - r = safe_atou64(values[0], &total_us); - if (r >= 0) - r = safe_atou64(values[1], &user_us); - if (r >= 0) - r = safe_atou64(values[2], &system_us); - - free_many_charp(values, ELEMENTSOF(values)); + strings); if (r < 0) return r; - *ret_total_nsec = total_us * NSEC_PER_USEC; - *ret_user_nsec = user_us * NSEC_PER_USEC; - *ret_system_nsec = system_us * NSEC_PER_USEC; - return 0; -} - -static int ensure_cpu_stat_cached(CGroupInfo *info) { - int r; - - assert(info); - - if (info->cpu_stat_cached > 0) - return 0; - if (info->cpu_stat_cached < 0) - return info->cpu_stat_cached; - - r = cpu_stat_parse(info->path, &info->cpu_total_nsec, &info->cpu_user_nsec, &info->cpu_system_nsec); - if (r < 0) { - if (r != -ENOENT) - log_debug_errno(r, "Failed to parse cpu.stat for '%s': %m", info->path); - info->cpu_stat_cached = r; - return r; + for (unsigned i = 0; i < 3; i++) { + r = safe_atou64(strings[i], &values[i]); + if (r < 0) + return r; } - info->cpu_stat_cached = 1; + for (unsigned i = 0; i < 3; i++) + ret[i] = values[i] * NSEC_PER_USEC; return 0; } -static int cpu_usage_send_one( - MetricFamilyContext *context, - const char *unit, - uint64_t value_nsec, - const char *type) { +static int cpu_usage_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + static const char* const types[] = { "total", "user", "system" }; + uint64_t values[3]; int r; - assert(context); + assert(mf && mf->name); + assert(link); + assert(path); assert(unit); - assert(type); - r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", type)); - if (r < 0) - return r; - - return metric_build_send_unsigned(context, unit, value_nsec, fields); -} - -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) { - if (ensure_cpu_stat_cached(*c) < 0) - continue; + r = cpu_stat_parse(path, values); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "cpu.stat"); + return 0; + } - r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_total_nsec, "total"); - if (r < 0) - return r; + for (unsigned i = 0; i < 3; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; - r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_user_nsec, "user"); + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", types[i])); if (r < 0) return r; - r = cpu_usage_send_one(context, (*c)->unit, (*c)->cpu_system_nsec, "system"); + r = metric_build_send_unsigned(mf, link, unit, values[i], fields); if (r < 0) return r; } @@ -273,113 +82,20 @@ static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) { 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) { +static int io_stat_parse(const char *cgroup_path, uint64_t ret[static 2]) { _cleanup_free_ char *path = NULL; - _cleanup_fclose_ FILE *f = NULL; uint64_t rbytes = 0, rios = 0; int r; - assert(ret_rbytes); - assert(ret_rios); + assert(ret); r = cg_get_path(cgroup_path, "io.stat", &path); if (r < 0) return r; - f = fopen(path, "re"); + _cleanup_fclose_ FILE *f = fopen(path, "re"); if (!f) return -errno; @@ -421,54 +137,111 @@ static int io_stat_parse(const char *cgroup_path, uint64_t *ret_rbytes, uint64_t } } - *ret_rbytes = rbytes; - *ret_rios = rios; + ret[0] = rbytes; + ret[1] = rios; return 0; } -static int ensure_io_stat_cached(CGroupInfo *info) { - int r; +static int io_read_send( + const MetricFamily mf[static 2], + sd_varlink *link, + const char *path, + const char *unit) { - assert(info); + uint64_t values[2]; + int r; - if (info->io_stat_cached > 0) - return 0; - if (info->io_stat_cached < 0) - return info->io_stat_cached; + assert(mf && mf[0].name && mf[1].name); + assert(link); + assert(path); + assert(unit); - r = io_stat_parse(info->path, &info->io_rbytes, &info->io_rios); + r = io_stat_parse(path, values); 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; + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "io.stat"); + return 0; + } + + for (unsigned i = 0; i < 2; i++) { + r = metric_build_send_unsigned(mf + i, link, unit, values[i], /* fields= */ NULL); + if (r < 0) + 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; +static int memory_usage_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { + + static const char* const types[] = { "current", "available", "peak" }; + bool bad[ELEMENTSOF(types)] = {}; + uint64_t current = 0, limit = UINT64_MAX, peak = 0; int r; - assert(context); + assert(mf && mf->name); + assert(link); + assert(path); + assert(unit); - r = walk_cgroups(ctx, &cgroups, &n_cgroups); - if (r < 0) - return 0; + r = cg_get_attribute_as_uint64(path, "memory.current", ¤t); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "memory.current"); + + bad[0] = bad[1] = true; + + } else { + /* Walk up the cgroup tree to find the tightest memory limit */ + _cleanup_free_ char *path_buf = strdup(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; - FOREACH_ARRAY(c, cgroups, n_cgroups) { - if (ensure_io_stat_cached(*c) < 0) + /* 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) + bad[1] = true; + } + + r = cg_get_attribute_as_uint64(path, "memory.peak", &peak); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "memory.peak"); + bad[2] = true; + } + + uint64_t values[] = { current, limit - current, peak }; + for (unsigned i = 0; i < ELEMENTSOF(values); i++) { + if (bad[i]) continue; - r = metric_build_send_unsigned( - context, - (*c)->unit, - (*c)->io_rbytes, - /* fields= */ NULL); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", types[i])); + if (r < 0) + return r; + + r = metric_build_send_unsigned(mf, link, unit, values[i], fields); if (r < 0) return r; } @@ -476,58 +249,95 @@ static int io_read_bytes_build_json(MetricFamilyContext *context, void *userdata return 0; } -static int io_read_operations_build_json(MetricFamilyContext *context, void *userdata) { - CGroupContext *ctx = ASSERT_PTR(userdata); - CGroupInfo **cgroups; - size_t n_cgroups; +static int tasks_current_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { + + uint64_t val; int r; - assert(context); + assert(mf && mf->name); + assert(link); + assert(path); + assert(unit); - r = walk_cgroups(ctx, &cgroups, &n_cgroups); - if (r < 0) + r = cg_get_attribute_as_uint64(path, "pids.current", &val); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "pids.current"); return 0; + } - FOREACH_ARRAY(c, cgroups, n_cgroups) { - if (ensure_io_stat_cached(*c) < 0) - continue; + return metric_build_send_unsigned(mf, link, unit, val, /* fields= */ NULL); +} - r = metric_build_send_unsigned( - context, - (*c)->unit, - (*c)->io_rios, - /* fields= */ NULL); +static int walk_cgroups( + const MetricFamily mf[static 5], + sd_varlink *link, + const char *path) { + + int r; + + assert(mf && mf[0].name && mf[1].name && mf[2].name && mf[3].name && mf[4].name); + assert(mf[0].generate && !mf[1].generate && !mf[2].generate && !mf[3].generate && !mf[4].generate); + assert(path); + + _cleanup_free_ char *unit = NULL; + r = cg_path_get_unit(path, &unit); + if (r >= 0) { + r = cpu_usage_send(mf + 0, link, path, unit); if (r < 0) return r; - } - return 0; -} + r = io_read_send(mf + 1, link, path, unit); + if (r < 0) + return r; -static int tasks_current_build_json(MetricFamilyContext *context, void *userdata) { - CGroupContext *ctx = ASSERT_PTR(userdata); - CGroupInfo **cgroups; - size_t n_cgroups; - int r; + r = memory_usage_send(mf + 3, link, path, unit); + if (r < 0) + return r; - assert(context); + r = tasks_current_send(mf + 4, link, path, unit); + if (r < 0) + return r; - r = walk_cgroups(ctx, &cgroups, &n_cgroups); + 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; - FOREACH_ARRAY(c, cgroups, n_cgroups) { - uint64_t val; + _cleanup_closedir_ DIR *d = NULL; + 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); - r = cg_get_attribute_as_uint64((*c)->path, "pids.current", &val); + for (;;) { + _cleanup_free_ char *fn = NULL, *child = NULL; + + r = cg_read_subgroup(d, &fn); if (r < 0) - continue; + return log_debug_errno(r, "Failed to read subgroup from '%s': %m", path); + if (r == 0) + break; - r = metric_build_send_unsigned( - context, - (*c)->unit, - val, - /* fields= */ NULL); + child = path_join(empty_to_root(path), fn); + if (!child) + return log_oom(); + + path_simplify(child); + + r = walk_cgroups(mf, link, child); if (r < 0) return r; } @@ -535,37 +345,49 @@ static int tasks_current_build_json(MetricFamilyContext *context, void *userdata return 0; } +static int cgroup_stats_send( + const MetricFamily mf[static 5], + sd_varlink *link, + void *userdata) { + + assert(mf); + assert(link); + assert(!userdata); + + return walk_cgroups(mf, link, ""); +} + 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=total|user|system)", - .type = METRIC_FAMILY_TYPE_COUNTER, - .generate = cpu_usage_build_json, + METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage", + "Per unit metric: CPU usage in nanoseconds (type=total|user|system)", + METRIC_FAMILY_TYPE_COUNTER, + .generate = cgroup_stats_send, }, { - .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, + METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", + "Per unit metric: IO bytes read", + METRIC_FAMILY_TYPE_COUNTER, + .generate = NULL, }, { - .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, + METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", + "Per unit metric: IO read operations", + METRIC_FAMILY_TYPE_COUNTER, + .generate = NULL, }, { - .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, + METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage", + "Per unit metric: memory usage in bytes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, }, { - .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, + METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent", + "Per unit metric: current number of tasks", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, }, {} }; @@ -575,12 +397,5 @@ int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd } 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; + return metrics_method_list(cgroup_metric_family_table, link, parameters, flags, userdata); } diff --git a/src/report/report-cgroup.h b/src/report/report-cgroup.h index dae8411df58b0..ce1e55b280bd3 100644 --- a/src/report/report-cgroup.h +++ b/src/report/report-cgroup.h @@ -5,16 +5,5 @@ #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/src/shared/metrics.c b/src/shared/metrics.c index 6c1490cbab8c1..7c50f8ab34ee6 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -73,15 +73,14 @@ static int metric_family_build_json(const MetricFamily *mf, sd_json_variant **re } int metrics_method_describe( - const MetricFamily metric_family_table[], + const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - int r; - assert(metric_family_table); + assert(mfs); assert(link); assert(parameters); assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); @@ -94,7 +93,7 @@ int metrics_method_describe( if (r < 0) return r; - for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { + for (const MetricFamily *mf = mfs; mf->name; mf++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; r = metric_family_build_json(mf, &v); @@ -110,15 +109,14 @@ int metrics_method_describe( } int metrics_method_list( - const MetricFamily metric_family_table[], + const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - int r; - assert(metric_family_table); + assert(mfs); assert(link); assert(parameters); assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); @@ -131,25 +129,28 @@ int metrics_method_list( if (r < 0) return r; - MetricFamilyContext ctx = { .link = link }; - for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { - assert(mf->generate); - - ctx.metric_family = mf; - r = mf->generate(&ctx, userdata); - if (r < 0) - return log_debug_errno( - r, "Failed to list metrics for metric family '%s': %m", mf->name); - } + for (const MetricFamily *mf = mfs; mf->name; mf++) + /* Metrics without .generate are handled by the preceding entry with .generate. + * Call the generating functions that are defined. */ + if (mf->generate) { + r = mf->generate(mf, link, userdata); + if (r < 0) + return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", mf->name); + } return 0; } -static int metric_build_send(MetricFamilyContext *context, const char *object, sd_json_variant *value, sd_json_variant *fields) { - assert(context); +static int metric_build_send( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + sd_json_variant *value, + sd_json_variant *fields) { + + assert(mf); + assert(link); assert(value); - assert(context->link); - assert(context->metric_family); if (fields) { assert(sd_json_variant_is_object(fields)); @@ -160,33 +161,51 @@ static int metric_build_send(MetricFamilyContext *context, const char *object, s assert(sd_json_variant_is_string(e)); } - return sd_varlink_replybo(context->link, - SD_JSON_BUILD_PAIR_STRING("name", context->metric_family->name), + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", mf->name), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), SD_JSON_BUILD_PAIR_VARIANT("value", value), JSON_BUILD_PAIR_VARIANT_NON_NULL("fields", fields)); } -int metric_build_send_string(MetricFamilyContext *context, const char *object, const char *value, sd_json_variant *fields) { +int metric_build_send_string( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + const char *value, + sd_json_variant *fields) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; + assert(mf); + assert(link); 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 metric_build_send(context, object, v, fields); + return metric_build_send(mf, link, object, v, fields); } -int metric_build_send_unsigned(MetricFamilyContext *context, const char *object, uint64_t value, sd_json_variant *fields) { +int metric_build_send_unsigned( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + uint64_t value, + sd_json_variant *fields) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; + assert(mf); + assert(link); + r = sd_json_variant_new_unsigned(&v, value); if (r < 0) return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); - return metric_build_send(context, object, v, fields); + return metric_build_send(mf, link, object, v, fields); } diff --git a/src/shared/metrics.h b/src/shared/metrics.h index ffc28cb0a9d8c..7c4afb5de77db 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -13,12 +13,7 @@ typedef enum MetricFamilyType { typedef struct MetricFamily MetricFamily; -typedef struct MetricFamilyContext { - const MetricFamily* metric_family; - sd_varlink *link; -} MetricFamilyContext; - -typedef int (*metric_family_generate_func_t) (MetricFamilyContext *mfc, void *userdata); +typedef int (*metric_family_generate_func_t) (const MetricFamily *mf, sd_varlink *link, void *userdata); typedef struct MetricFamily { const char *name; @@ -38,8 +33,8 @@ int metrics_setup_varlink_server( DECLARE_STRING_TABLE_LOOKUP_TO_STRING(metric_family_type, MetricFamilyType); -int metrics_method_describe(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int metrics_method_list(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int metrics_method_describe(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int metrics_method_list(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int metric_build_send_string(MetricFamilyContext* context, const char *object, const char *value, sd_json_variant *fields); -int metric_build_send_unsigned(MetricFamilyContext* context, const char *object, uint64_t value, sd_json_variant *fields); +int metric_build_send_string(const MetricFamily* mf, sd_varlink *link, const char *object, const char *value, sd_json_variant *fields); +int metric_build_send_unsigned(const MetricFamily* mf, sd_varlink *link, const char *object, uint64_t value, sd_json_variant *fields); From e27c382bf0c840bb0c7c3ea9cfe4c69034a57fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 8 May 2026 09:23:09 +0200 Subject: [PATCH 1636/2155] report-basic: expose os-release fields as metrics Add io.systemd.Basic.OSRelease metric families that reports select fields from os-release. $ cat /etc/os-release NAME="Fedora Linux" VERSION="45 (Cloud Edition Prerelease)" RELEASE_TYPE=development ID=fedora VERSION_ID=45 VERSION_CODENAME="" PRETTY_NAME="Fedora Linux 45 (Cloud Edition Prerelease)" ANSI_COLOR="0;38;2;60;110;180" LOGO=fedora-logo-icon CPE_NAME="cpe:/o:fedoraproject:fedora:45" HOME_URL="https://fedoraproject.org/" DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/rawhide/" SUPPORT_URL="https://ask.fedoraproject.org/" BUG_REPORT_URL="https://bugzilla.redhat.com/" REDHAT_BUGZILLA_PRODUCT="Fedora" REDHAT_BUGZILLA_PRODUCT_VERSION=rawhide REDHAT_SUPPORT_PRODUCT="Fedora" REDHAT_SUPPORT_PRODUCT_VERSION=rawhide SUPPORT_END=2027-11-24 VARIANT="Cloud Edition" VARIANT_ID=cloud $ varlinkctl call --more ./build/systemd-report-basic io.systemd.Metrics.List {} | jq --seq -c ... {"name":"io.systemd.Basic.OSRelease.NAME","value":"Fedora Linux 44 (Workstation Edition)"} {"name":"io.systemd.Basic.OSRelease.ID","value":"fedora"} {"name":"io.systemd.Basic.OSRelease.CPE_NAME","value":"cpe:/o:fedoraproject:fedora:44"} {"name":"io.systemd.Basic.OSRelease.VARIANT_ID","value":"workstation"} {"name":"io.systemd.Basic.OSRelease.VERSION_ID","value":"44"} {"name":"io.systemd.Basic.OSRelease.SUPPORT_END","value":"2027-05-19"} I picked the fields that contain useful information about the specific version/image/variant/experiment/flavour of the system. Also, either NAME or PRETTY_NAME is included. This one is intended for human readers to be able to identify the OS version easily. --- src/report/report-basic.c | 93 ++++++++++++++++++++++++++ test/units/TEST-74-AUX-UTILS.report.sh | 4 ++ 2 files changed, 97 insertions(+) diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 11ecb7b45c678..772a5d9f74222 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -9,7 +9,9 @@ #include "alloc-util.h" #include "architecture.h" #include "hostname-setup.h" +#include "log.h" #include "metrics.h" +#include "os-util.h" #include "report-basic.h" #include "virt.h" @@ -98,6 +100,71 @@ static int machine_id_generate(const MetricFamily *mf, sd_varlink *link, void *u /* fields= */ NULL); } +enum { + FIELD_PRETTY_NAME, + FIELD_NAME, + FIELD_ID, + FIELD_CPE_NAME, + FIELD_VARIANT_ID, + FIELD_VERSION_ID, + FIELD_BUILD_ID, + FIELD_IMAGE_VERSION, + FIELD_IMAGE_ID, + FIELD_SUPPORT_END, + FIELD_EXPERIMENT, + FIELD_SYSEXT_LEVEL, + FIELD_CONFEXT_LEVEL, + _FIELD_MAX, +}; + +static int os_release_generate(const MetricFamily mf[static _FIELD_MAX - 1], sd_varlink *link, void *userdata) { + char* values[_FIELD_MAX] = {}; + CLEANUP_ELEMENTS(values, free_many_charp); + int r; + + assert(mf && mf->name); + assert(link); + + r = parse_os_release(NULL, + "PRETTY_NAME", &values[FIELD_PRETTY_NAME], + "NAME", &values[FIELD_NAME], + "ID", &values[FIELD_ID], + "CPE_NAME", &values[FIELD_CPE_NAME], + "VARIANT_ID", &values[FIELD_VARIANT_ID], + "VERSION_ID", &values[FIELD_VERSION_ID], + "BUILD_ID", &values[FIELD_BUILD_ID], + "IMAGE_VERSION", &values[FIELD_IMAGE_VERSION], + "IMAGE_ID", &values[FIELD_IMAGE_ID], + "SUPPORT_END", &values[FIELD_SUPPORT_END], + "EXPERIMENT", &values[FIELD_EXPERIMENT], + "SYSEXT_LEVEL", &values[FIELD_SYSEXT_LEVEL], + "CONFEXT_LEVEL", &values[FIELD_CONFEXT_LEVEL]); + if (r < 0) { + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + return 0; + } + + for (size_t i = 1; i < _FIELD_MAX; i++) { + const char *v = values[i]; + if (i == FIELD_NAME && values[FIELD_PRETTY_NAME]) + v = values[FIELD_PRETTY_NAME]; /* Prefer PRETTY_NAME to NAME */ + + if (v) { + r = metric_build_send_string( + mf + i - 1, + link, + /* object= */ NULL, + v, + /* fields= */ NULL); + if (r < 0) + return r; + } + } + + return 0; +} + static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { assert(mf && mf->name); assert(link); @@ -114,6 +181,14 @@ static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, voi /* fields= */ NULL); } +#define OS_RELEASE_STANDARD_FIELD(name) \ + { \ + METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease." name, \ + "Operating system identification (" name "= field from os-release)", \ + METRIC_FAMILY_TYPE_STRING, \ + .generate = NULL, \ + } + static const MetricFamily metric_family_table[] = { /* Keep entries ordered alphabetically */ { @@ -146,6 +221,24 @@ static const MetricFamily metric_family_table[] = { METRIC_FAMILY_TYPE_STRING, .generate = machine_id_generate, }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease.NAME", + "Operating system human-readable name (PRETTY_NAME= or NAME= field from os-release)", + METRIC_FAMILY_TYPE_STRING, + .generate = os_release_generate, + }, + OS_RELEASE_STANDARD_FIELD("ID"), + OS_RELEASE_STANDARD_FIELD("CPE_NAME"), + OS_RELEASE_STANDARD_FIELD("VARIANT_ID"), + OS_RELEASE_STANDARD_FIELD("VERSION_ID"), + OS_RELEASE_STANDARD_FIELD("BUILD_ID"), + OS_RELEASE_STANDARD_FIELD("IMAGE_VERSION"), + OS_RELEASE_STANDARD_FIELD("IMAGE_ID"), + OS_RELEASE_STANDARD_FIELD("SUPPORT_END"), + OS_RELEASE_STANDARD_FIELD("EXPERIMENT"), + OS_RELEASE_STANDARD_FIELD("SYSEXT_LEVEL"), + OS_RELEASE_STANDARD_FIELD("CONFEXT_LEVEL"), + /* Keep those ↑ in sync with os_release_generate(). */ { METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", "Virtualization type", diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 61d2b10d0b7c6..0669f5587f080 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -57,6 +57,10 @@ varlinkctl list-methods /run/systemd/report/io.systemd.Basic varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.Describe {} +id1="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} | jq --seq -r 'select(.name == "io.systemd.Basic.OSRelease.ID") | .value')" +id2="$(. /etc/os-release; echo "$ID")" +[ "$id1" = "$id2" ] + # 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) From a117488d35988722384c114e2b1ca842e0d77e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 12:38:22 +0200 Subject: [PATCH 1637/2155] report-cgroup: use errno_or_else in one more place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old gcc is confused about initialization: In function ‘io_read_send’, inlined from ‘walk_cgroups’ at ../src/report/report-cgroup.c:288:24: ../src/report/report-cgroup.c:167:21: error: ‘values[0]’ may be used uninitialized [-Werror=maybe-uninitialized] 167 | r = metric_build_send_unsigned(mf + i, link, unit, values[i], /* fields= */ NULL); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Maybe this helps. --- src/report/report-cgroup.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c index 48fcbb5a647be..240f09f9f2a4f 100644 --- a/src/report/report-cgroup.c +++ b/src/report/report-cgroup.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "cgroup-util.h" +#include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -97,7 +98,7 @@ static int io_stat_parse(const char *cgroup_path, uint64_t ret[static 2]) { _cleanup_fclose_ FILE *f = fopen(path, "re"); if (!f) - return -errno; + return errno_or_else(EIO); for (;;) { _cleanup_free_ char *line = NULL; From 4f7089f0c740b9b0b5e55913282b61d851991562 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 7 May 2026 20:02:57 +0100 Subject: [PATCH 1638/2155] core: do not leak resources when handling stale alias state on reload The fix for the corrupted state when units become aliased on reload leaks the now-aliased unit's resources, which become untracked and essentially lost. While fixing the state corruption is of course necessary, leaking processes/etc. is not ideal for a system and service manager, so instead attempt to keep track of them by creating stub units on-the-fly as replacements. This way resources are not leaked, there are clear indications of where they moved, and all state can be tracked as expected. Ignore timers/sockets/paths as those are internal resources/triggers. Follow-up for a77c7a8224447890a304bd857f412c8103f217f1 --- man/systemctl.xml | 11 - man/systemd.unit.xml | 10 + src/core/manager-serialize.c | 110 ++++++++- test/units/TEST-07-PID1.alias-corruption.sh | 236 +++++++++++++++++--- 4 files changed, 310 insertions(+), 57 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 8b8d1065556f2..b8ba0f945e97b 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1499,17 +1499,6 @@ 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/man/systemd.unit.xml b/man/systemd.unit.xml index 47c30d869fcbe..2f1467b720af4 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -281,6 +281,16 @@ generally ignored. This includes names that start with a . or end with a .ignore. + When unit aliasing is introduced during reload/reexec (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 migrated to a new stub orphaned unit 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 resources such as running processes, they will now be tracked + under the new orphaned unit. Once all resources are gone (e.g. all processes have exited) the orphaned + unit will be garbage collected automatically. + The unit file format is covered by the Interface Portability and Stability Promise. diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index c794888164874..7c557b2f3ff20 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-id128.h" + #include "alloc-util.h" #include "dbus.h" #include "dynamic-user.h" @@ -10,6 +12,7 @@ #include "glyph-util.h" #include "hashmap.h" #include "initrd-util.h" +#include "job.h" #include "manager.h" #include "manager-serialize.h" #include "parse-util.h" @@ -18,6 +21,8 @@ #include "string-util.h" #include "strv.h" #include "syslog-util.h" +#include "unit.h" +#include "unit-name.h" #include "unit-serialize.h" #include "user-util.h" #include "varlink.h" @@ -235,6 +240,87 @@ static int manager_collect_serialized_unit_names(FILE *f, Set **ret) { return 0; } +static int manager_synthesize_orphaned_unit( + Manager *m, + const char *original_name, + const char *canonical_name, + FILE *f, + FDSet *fds) { + + _cleanup_(unit_freep) Unit *orphan = NULL; + _cleanup_free_ char *orphaned_name = NULL; + sd_id128_t rnd; + UnitType t; + int r; + + assert(m); + assert(original_name); + assert(canonical_name); + assert(f); + assert(fds); + + t = unit_name_to_type(original_name); + if (t < 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot synthesize unit for '%s' (overridden by alias to '%s'): invalid unit type. Skipping stale state.", + original_name, canonical_name); + + /* Only transition units that track external resources, forget internal ones (eg: timers) */ + if (!IN_SET(t, UNIT_SERVICE, UNIT_SCOPE, UNIT_MOUNT, UNIT_AUTOMOUNT)) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Cannot synthesize unit for '%s' (overridden by alias to '%s'): unsupported unit type. Skipping stale state.", + original_name, canonical_name); + + /* Use a naming convention with an "orphaned-" prefix to make it clear at a glance that these units + * were synthesized to adopt resources from a now-aliased unit */ + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_warning_errno(r, "Failed to generate random ID for orphan unit: %m"); + + if (asprintf(&orphaned_name, "orphaned-r" SD_ID128_FORMAT_STR ".%s", + SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) + return log_oom(); + + r = unit_new_for_name(m, unit_vtable[t]->object_size, orphaned_name, &orphan); + if (r < 0) + return log_warning_errno(r, "Failed to allocate orphan unit '%s': %m", orphaned_name); + + /* Force the state as we know the unit file is gone, so it will hang around as long as resources are + * active, and then it will be automatically collected */ + orphan->load_state = UNIT_NOT_FOUND; + /* Ensure the orphan also gets garbage-collected when it ends up in 'failed' state. */ + orphan->collect_mode = COLLECT_INACTIVE_OR_FAILED; + + _cleanup_free_ char *description = strjoin("Orphaned resources adopted from aliased unit ", original_name); + if (!description) + return log_oom(); + + r = unit_deserialize_state(orphan, f, fds); + if (r < 0) + return log_notice_errno(r, "Failed to deserialize state into orphan unit '%s': %m", orphaned_name); + + /* From this point on the serialized stream for this unit has been fully consumed, so avoid failing. */ + + log_warning("Unit file for '%s' was overridden by a symlink to '%s'. Synthesized orphan unit '%s' to retain tracking of the previous unit's processes.", + original_name, canonical_name, orphaned_name); + + /* Override Description= so it's clear what this is for and can be traced back to the original. */ + free_and_replace(orphan->description, description); + + /* Any jobs reinstalled from the deserialized state targeted the original unit. Most of them + * (start, restart, reload, ...) make no sense for the synthesized orphan, especially after a + * service-to-scope conversion, so cancel them. JOB_STOP is the exception: a stop request that was + * already pending on the original unit is exactly what we want to keep around, since it'll cause + * the synthesized unit to just go away immediately. */ + if (orphan->job && orphan->job->type != JOB_STOP) + job_finish_and_invalidate(orphan->job, JOB_CANCELED, /* recursive= */ false, /* already= */ false); + if (orphan->nop_job) + job_finish_and_invalidate(orphan->nop_job, JOB_CANCELED, /* recursive= */ false, /* already= */ false); + + TAKE_PTR(orphan); + return 0; +} + static int manager_deserialize_one_unit( Manager *m, const char *name, @@ -274,21 +360,23 @@ static int manager_deserialize_one_unit( * 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. + * onto the canonical unit would corrupt its live state. Instead, we synthesize a new unloaded + * unit with a unique name and migrate the cgroup/PID/etc. tracking from the stale state + * into it, so that the resources from the previously independent unit remain tracked. * * 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. + * On first reload, the state file still has b.service as an independent unit (from before + * the symlink existed), but b.service now resolves to a.service. We retain a.service's + * running state, and synthesize an unloaded unit to keep tracking the processes that + * b.service used to own. * - * 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. + * Note: Log messages from this code path are checked in TEST-07-PID1.alias-corruption.sh, + * so the test case may need adjustment if they are 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 = manager_synthesize_orphaned_unit(m, name, u->id, f, fds); + if (r < 0) /* If we fail to orphan for any reason, then discard the unit */ + r = unit_deserialize_state_skip(f); + return r; } r = unit_deserialize_state(u, f, fds); diff --git a/test/units/TEST-07-PID1.alias-corruption.sh b/test/units/TEST-07-PID1.alias-corruption.sh index 009ee484a0250..dea08f0404154 100755 --- a/test/units/TEST-07-PID1.alias-corruption.sh +++ b/test/units/TEST-07-PID1.alias-corruption.sh @@ -56,14 +56,37 @@ reap_abandoned_pids() { abandoned_pids=() } +# Wait until at least one job for a unit name matching the given prefix is +# queued. Used to make sure the serialized state we're about to dump contains +# embedded 'job\n...\n\n' subsections so the deserialization path is fully +# exercised. +wait_for_pending_job() { + local prefix="${1:?}" + local i unit + + for i in {1..100}; do + for unit in $(systemctl list-jobs --no-legend | awk '{print $2}'); do + if [[ "$unit" == "$prefix"* ]]; then + return 0 + fi + done + sleep 0.1 + done + + echo "ERROR: no $prefix* jobs are pending" + systemctl list-jobs || true + return 1 +} + run_test() { local reload_cmd="${1:?}" - # If "with_pending_jobs", also create many Type=oneshot units that hang in - # "activating" state with a pending job, to ensure that the serialized state - # contains embedded "job" subsections to fully exercise the deserialization + # If "with_pending_jobs", also queue hanging reload jobs on the running sus + # units, so that the serialized state contains embedded 'job' subsections + # to fully exercise the deserialization local pending_jobs="${2:-}" local n_sus=20 - local current_pid journal_warnings new_pid orig_pid pid reload_start unit warning_count + local current_pid journal_warnings new_pid orig_pid p pid reload_start orphan orphan_cgroup unit warning_count + local orphan_units if [[ "$pending_jobs" == "with_pending_jobs" ]]; then n_sus=100 @@ -84,8 +107,8 @@ EOF # collect them. If they are dead/stopped, systemd can remove them from # memory before serialization. # - # In with_pending_jobs mode they additionally get a pending restart job - # queued via 'systemctl --no-block restart' AFTER they are running, so the + # In with_pending_jobs mode they additionally get a pending reload job + # queued via 'systemctl --no-block reload' AFTER they are running, so the # serialized stream contains 'job\n...\n\n' subsections AND the units have # a real MainPID. The skip-desync regression in unit_deserialize_state_skip() # stops at the job subsection's empty line marker, leaving the rest of the @@ -124,15 +147,7 @@ EOF done # Make sure at least one reload job is actually queued, otherwise the # serialized stream might not contain any job subsections yet. - for i in {1..100}; do - [[ -n "$(systemctl list-jobs --no-legend | grep -E '^[[:space:]]*[0-9]+ sus-' || true)" ]] && break - if (( i == 100 )); then - echo "ERROR: no sus-*.service reload jobs are pending" - systemctl list-jobs || true - return 1 - fi - sleep 0.1 - done + wait_for_pending_job sus- fi echo "Setup complete: 1 running legit unit, $n_sus ${pending_jobs:+job-bearing }sus units" @@ -188,32 +203,76 @@ EOF 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..." + # Verify that all sus unit processes were tracked in a synthesized + # orphan unit, not abandoned. + echo "Verifying sus unit processes were migrated into synthesized orphan units..." 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!" + echo "ERROR: $unit process (PID $pid) was killed instead of being preserved!" return 1 fi - # But the alias should now either be inactive (MainPID=0) or resolve to legit's PID. + # 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)" + echo "$unit process (PID $pid) is still running and the alias correctly does not claim it" 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) + # Verify that synthesized orphan units exist that cover all the + # previously-tracked PIDs. Each preserved process must belong to a + # orphaned-r* unit. Orphan units retain the original unit's type + # (.service here) and are marked load-state=not-found, so list-units + # with --all is required to see them. + echo "Verifying synthesized orphan units are present and own the PIDs..." + orphan_units=$(systemctl list-units --all --no-legend --plain 'orphaned-r*' | awk '{print $1}') + if [[ -z "$orphan_units" ]]; then + echo "ERROR: No orphaned-r* units were synthesized!" + systemctl list-units --all --no-legend --plain || true + return 1 + fi + echo "Found orphan units:" + echo "$orphan_units" + + # Build a set of all PIDs reported by any orphan unit (via Tasks/cgroup membership). + unset tracked_pids + declare -A tracked_pids + for orphan in $orphan_units; do + orphan_cgroup=$(systemctl show -P ControlGroup "$orphan") + if [[ -z "$orphan_cgroup" || ! -r "/sys/fs/cgroup${orphan_cgroup}/cgroup.procs" ]]; then + # Orphan unit may have been recorded with the original unit's cgroup path which still exists + echo "ERROR: Cannot read cgroup.procs for $orphan at expected path /sys/fs/cgroup${orphan_cgroup}/cgroup.procs" + return 1 + fi + while read -r p; do + [[ -n "$p" ]] && tracked_pids[$p]=1 + done < "/sys/fs/cgroup${orphan_cgroup}/cgroup.procs" + done - echo "Found $warning_count 'Skipping stale state' warnings" + # Cross-check: every original sus PID must appear under exactly one orphan unit cgroup. + for unit in "${!sus_pids[@]}"; do + pid=${sus_pids[$unit]} + if [[ -z "${tracked_pids[$pid]:-}" ]]; then + echo "ERROR: PID $pid (from $unit) is not tracked by any synthesized orphan unit!" + echo "Orphan unit contents:" + for orphan in $orphan_units; do + echo " $orphan -> $(systemctl show -P ControlGroup "$orphan")" + done + return 1 + fi + done + echo "All ${#sus_pids[@]} sus PIDs are tracked by synthesized orphan units." + + # Check consistency between journal warnings and synthesized orphan units. + echo "Checking journal for 'Synthesized orphan unit' warnings..." + journal_warnings=$(journalctl --since "$reload_start" --no-pager | grep "Synthesized orphan unit" || true) + warning_count=$(echo "$journal_warnings" | grep -c "Synthesized orphan unit" || true) + + echo "Found $warning_count 'Synthesized orphan unit' 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 @@ -223,6 +282,12 @@ EOF done fi + # Stop synthesized orphan units (which terminates their tracked + # processes) so we get a clean slate for the next iteration. + for orphan in $orphan_units; do + systemctl stop "$orphan" 2>/dev/null || true + done + reap_abandoned_pids if (( attempt < 3 )); then @@ -252,15 +317,7 @@ EOF for i in $(seq -f "%03g" 1 "$n_sus"); do systemctl --no-block reload sus-"${i}".service done - for i in {1..100}; do - [[ -n "$(systemctl list-jobs --no-legend | grep -E '^[[:space:]]*[0-9]+ sus-' || true)" ]] && break - if (( i == 100 )); then - echo "ERROR: no sus-*.service reload jobs are pending after reset" - systemctl list-jobs || true - return 1 - fi - sleep 0.1 - done + wait_for_pending_job sus- fi echo "Reset complete." @@ -274,13 +331,118 @@ EOF cleanup_test_units() { reap_abandoned_pids || true + # Stop any leftover synthesized orphan units from previous iterations. + # Do this in two passes since a queued stop job from the deserialized + # state may take a moment to dispatch and tear the unit down. + for orphan in $(systemctl list-units --all --no-legend --plain 'orphaned-r*' 2>/dev/null | awk '{print $1}'); do + systemctl stop "$orphan" 2>/dev/null || true + done + for orphan in $(systemctl list-units --all --no-legend --plain 'orphaned-r*' 2>/dev/null | awk '{print $1}'); do + systemctl kill --signal=SIGKILL "$orphan" 2>/dev/null || true + systemctl reset-failed "$orphan" 2>/dev/null || true + done systemctl stop legit.service 2>/dev/null || true + systemctl stop hung-stop.service 2>/dev/null || true for i in $(seq -f "%03g" 1 100); 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 + rm -f /run/systemd/system/hung-stop.service + systemctl daemon-reload +} + +# Verify that a JOB_STOP that was pending on the original unit at the moment of +# serialization is preserved on the synthesized orphan unit, so the stop is +# eventually carried out (the orphan is torn down) instead of sitting around +# forever. +test_stop_job_preserved() { + local reload_cmd="${1:?}" + local hung_pid orphan stop_job substate + + echo "" + echo "=========================================" + echo "Testing pending-stop preservation with: systemctl $reload_cmd" + echo "=========================================" + + # Service whose main process traps SIGTERM and never exits, so a "stop" + # request stays pending in the queue and remains serialized. + cat >/run/systemd/system/legit.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + cat >/run/systemd/system/hung-stop.service <<'EOF' +[Service] +Type=notify +NotifyAccess=all +ExecStart=/bin/bash -c 'trap "" TERM; systemd-notify --ready; while :; do sleep infinity & wait $!; done' +TimeoutStopSec=infinity +SendSIGKILL=no +EOF + systemctl daemon-reload + systemctl start legit.service + systemctl start hung-stop.service + + hung_pid=$(systemctl show -P MainPID hung-stop.service) + if (( hung_pid == 0 )); then + echo "ERROR: hung-stop.service did not start" + return 1 + fi + + # Queue a stop that will hang because the process traps SIGTERM and + # SendSIGKILL=no prevents escalation, so the job stays in the queue and + # is therefore present in the serialized state at reload time. + systemctl --no-block stop hung-stop.service + wait_for_pending_job hung-stop.service + + # Convert hung-stop.service into a symlink to legit.service: on the next + # reload the original unit becomes an alias of legit.service, and its + # serialized state (including the pending stop job) is fed into the + # synthesized orphaned-r* orphan unit. + rm -f /run/systemd/system/hung-stop.service + ln -sf /run/systemd/system/legit.service /run/systemd/system/hung-stop.service + + systemctl "$reload_cmd" + + orphan=$(systemctl list-units --all --no-legend --plain 'orphaned-r*' | awk '{print $1}' | head -n1) + if [[ -z "$orphan" ]]; then + echo "ERROR: no synthesized orphan unit was created" + systemctl list-units --all --no-legend --plain || true + return 1 + fi + echo "Synthesized orphan unit: $orphan" + + # The pending stop job from the original unit must have been carried over + # to the synthesized orphan, otherwise the orphan (and its tracked + # process) is leaked across the reload. Check if: + # - the stop job is still queued, + # - the orphan is in a stop-* sub-state, + # - the orphan has already finished stopping (dead/failed), + # - the orphan was already garbage-collected (no SubState reported). + stop_job=$(systemctl show -P Job "$orphan" 2>/dev/null || true) + substate=$(systemctl show -P SubState "$orphan" 2>/dev/null || true) + if [[ -z "$stop_job" ]] && [[ -n "$substate" ]] && ! [[ "$substate" =~ ^(stop-|dead|failed) ]]; then + echo "ERROR: stop job for original hung-stop.service was not preserved on $orphan!" + echo "Current substate: $substate" + systemctl list-jobs || true + return 1 + fi + + echo "Stop job for original hung-stop.service was correctly preserved on synthesized orphan $orphan" + + # Tear the hung orphan down for the next iteration. + systemctl kill --signal=SIGKILL "$orphan" 2>/dev/null || true + systemctl reset-failed "$orphan" 2>/dev/null || true + if kill -0 "$hung_pid" 2>/dev/null; then + kill -KILL "$hung_pid" 2>/dev/null || true + fi + + rm -f /run/systemd/system/hung-stop.service /run/systemd/system/legit.service + systemctl daemon-reload + + echo "$reload_cmd stop-job preservation test passed" } trap cleanup_test_units EXIT @@ -292,3 +454,7 @@ cleanup_test_units run_test daemon-reload with_pending_jobs cleanup_test_units run_test daemon-reexec with_pending_jobs +cleanup_test_units +test_stop_job_preserved daemon-reload +cleanup_test_units +test_stop_job_preserved daemon-reexec From eda10260a05f19b7507ffd531fd435fc48c2c9b4 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Sun, 10 May 2026 14:50:13 +0000 Subject: [PATCH 1639/2155] logind: zero-initialize dispatch struct in vl_method_release_session() The local struct passed to sd_varlink_dispatch() was not zero-initialized. Since sd_json_dispatch_full() does not call handlers for absent optional fields, p.id could be left indeterminate when the client omits the Id parameter, leading to use of uninitialized memory. --- 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 56b02b4eb2eee..71a445e90032e 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -310,7 +310,7 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete struct { const char *id; - } p; + } p = {}; static const sd_json_dispatch_field dispatch_table[] = { { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 }, From 304899fa57d0ad5ee57a8c7ee854ad74affe2eda Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 3 May 2026 15:16:17 +0900 Subject: [PATCH 1640/2155] sd-dhcp-client: always set default broadcast hardware address when unspecified The default value for InfiniBand is copied from dhcp-network.c. --- src/basic/ether-addr-util.c | 28 +++++++++++++ src/basic/ether-addr-util.h | 2 + src/libsystemd-network/sd-dhcp-client.c | 56 +++++++++++++++---------- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c index 2919422bd2e1a..4ea4be594e10e 100644 --- a/src/basic/ether-addr-util.c +++ b/src/basic/ether-addr-util.c @@ -301,3 +301,31 @@ void ether_addr_mark_random(struct ether_addr *addr) { addr->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ addr->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ } + +int hw_addr_ensure_broadcast(struct hw_addr_data *bcast_addr, uint16_t arp_type) { + assert(bcast_addr); + + if (!hw_addr_is_null(bcast_addr)) + return 0; + + switch (arp_type) { + case ARPHRD_ETHER: + *bcast_addr = (struct hw_addr_data) { + .length = ETH_ALEN, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, + }; + return 0; + case ARPHRD_INFINIBAND: + *bcast_addr = (struct hw_addr_data) { + .length = INFINIBAND_ALEN, + .infiniband = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + }, + }; + return 0; + default: + return -EAFNOSUPPORT; + } +} diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index d342b5621e07e..1d6e4cda7f016 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -106,3 +106,5 @@ extern const struct hash_ops ether_addr_hash_ops; extern const struct hash_ops ether_addr_hash_ops_free; void ether_addr_mark_random(struct ether_addr *addr); + +int hw_addr_ensure_broadcast(struct hw_addr_data *bcast_addr, uint16_t arp_type); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 8b517982b65cd..463f95ea29bf1 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -196,44 +196,55 @@ int sd_dhcp_client_set_mac( assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND, ARPHRD_RAWIP, ARPHRD_NONE), -EINVAL); - static const uint8_t default_eth_bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - default_eth_hwaddr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uint8_t default_eth_hwaddr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; switch (arp_type) { + case ARPHRD_ETHER: + assert_return(addr_len == ETH_ALEN, -EINVAL); + assert_return(hw_addr, -EINVAL); + break; + + case ARPHRD_INFINIBAND: + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + assert_return(hw_addr, -EINVAL); + break; + case ARPHRD_RAWIP: case ARPHRD_NONE: - /* Linux cellular modem drivers (e.g. qmi_wwan) present a - * network interface of type ARPHRD_RAWIP(519) or - * ARPHRD_NONE(65534) when in point-to-point mode, but these - * are not valid DHCP hardware-type values. + /* Linux cellular modem drivers (e.g. qmi_wwan) present a network interface of type + * ARPHRD_RAWIP(519) or ARPHRD_NONE(65534) when in point-to-point mode, but these are not + * valid DHCP hardware-type values. * - * Apparently, it's best to just pretend that these are ethernet - * devices. Other approaches have been tried, but resulted in - * incompatibilities with some server software. See - * https://lore.kernel.org/netdev/cover.1228948072.git.inaky@linux.intel.com/ - */ + * Apparently, it's best to just pretend that these are ethernet devices. Other approaches + * have been tried, but resulted in incompatibilities with some server software. See + * https://lore.kernel.org/netdev/cover.1228948072.git.inaky@linux.intel.com/ */ arp_type = ARPHRD_ETHER; if (addr_len == 0) { - assert_cc(sizeof(default_eth_hwaddr) == ETH_ALEN); - assert_cc(sizeof(default_eth_bcast) == ETH_ALEN); - hw_addr = default_eth_hwaddr; - bcast_addr = default_eth_bcast; + /* If the specified hardware address length is 0, always use the default ones. */ addr_len = ETH_ALEN; + hw_addr = default_eth_hwaddr; + bcast_addr = NULL; + } else if (addr_len == ETH_ALEN) { + /* If the specified hardware address length is ETH_ALEN, use the default ones when + * unspecified. */ + if (!hw_addr) + hw_addr = default_eth_hwaddr; + } else { + /* Otherwise, user must specify valid addresses. */ + assert_return(hw_addr, -EINVAL); + assert_return(bcast_addr, -EINVAL); } break; - } - assert_return(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND), -EINVAL); - assert_return(hw_addr, -EINVAL); - assert_return(addr_len == (arp_type == ARPHRD_ETHER ? ETH_ALEN : INFINIBAND_ALEN), -EINVAL); + default: + return -EINVAL; + } client->arp_type = arp_type; hw_addr_set(&client->hw_addr, hw_addr, addr_len); hw_addr_set(&client->bcast_addr, bcast_addr, bcast_addr ? addr_len : 0); - - return 0; + return hw_addr_ensure_broadcast(&client->bcast_addr, arp_type); } int sd_dhcp_client_get_client_id(sd_dhcp_client *client, const sd_dhcp_client_id **ret) { @@ -2069,6 +2080,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { assert_return(client, -EINVAL); assert_return(client->event, -EINVAL); assert_return(client->ifindex > 0, -EINVAL); + assert_return(!hw_addr_is_null(&client->bcast_addr), -EINVAL); /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { From 7cecff8c9949d4bfd85b06c6dba3691e82ec86d4 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 12 May 2026 17:09:41 +0200 Subject: [PATCH 1641/2155] sd-bus: handle non-string keys in dictionaries in JSON dump JSON only supports string keys in objects, but D-Bus specification is a bit more lenient and allows dict entries to have any basic type as key. Let's stringify allowed non-string keys so that we can represent them as JSON objects. Relevant snippet from the D-Bus specification: A DICT_ENTRY works exactly like a struct, but rather than parentheses it uses curly braces, and it has more restrictions. The restrictions are: it occurs only as an array element type; it has exactly two single complete types inside the curly braces; the first single complete type (the "key") must be a basic type rather than a container type. Implementations must not accept dict entries outside of arrays, must not accept dict entries with zero, one, or more than two fields, and must not accept dict entries with non-basic-typed keys. A dict entry is always a key-value pair. Resolves: #32904 --- src/libsystemd/sd-bus/bus-dump-json.c | 29 ++++++++++++++++++++++++-- test/units/TEST-74-AUX-UTILS.busctl.sh | 6 ++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-dump-json.c b/src/libsystemd/sd-bus/bus-dump-json.c index 8238ac587e908..ae3e8061946b5 100644 --- a/src/libsystemd/sd-bus/bus-dump-json.c +++ b/src/libsystemd/sd-bus/bus-dump-json.c @@ -72,6 +72,7 @@ static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many); for (;;) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *key = NULL; const char *contents; char type; @@ -94,11 +95,35 @@ static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { if (r < 0) return r; - r = json_transform_one(m, elements + n_elements); + r = json_transform_one(m, &key); if (r < 0) return r; - n_elements++; + /* JSON only supports string keys in objects, but D-Bus specification is a bit more lenient + * and allows dict entries to have any basic type as key. Let's stringify allowed non-string + * keys so that we can represent them as JSON objects. */ + if (!sd_json_variant_is_string(key)) { + _cleanup_free_ char *s = NULL; + + if (!IN_SET(sd_json_variant_type(key), + SD_JSON_VARIANT_BOOLEAN, + SD_JSON_VARIANT_INTEGER, + SD_JSON_VARIANT_REAL, + SD_JSON_VARIANT_UNSIGNED)) + return -EINVAL; + + r = sd_json_variant_format(key, /* flags= */ 0, &s); + if (r < 0) + return r; + + key = sd_json_variant_unref(key); + + r = sd_json_variant_new_string(&key, s); + if (r < 0) + return r; + } + + elements[n_elements++] = TAKE_PTR(key); r = json_transform_one(m, elements + n_elements); if (r < 0) diff --git a/test/units/TEST-74-AUX-UTILS.busctl.sh b/test/units/TEST-74-AUX-UTILS.busctl.sh index 875f353b00ea4..54170cf5b5507 100755 --- a/test/units/TEST-74-AUX-UTILS.busctl.sh +++ b/test/units/TEST-74-AUX-UTILS.busctl.sh @@ -43,6 +43,12 @@ busctl call --json=short \ busctl call -j \ org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.DBus.Properties \ GetAll s "org.freedesktop.systemd1.Manager" | jq -c +# Do the same with D-Bus' org.freedesktop.DBus.Debug.Stats interface which also provides some +# complex signatures that caused issues during the JSON conversion in the past +# See: https://github.com/systemd/systemd/issues/32904 +busctl call --json=pretty \ + org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.Debug.Stats \ + GetStats | jq -c busctl call --verbose --timeout=60 --expect-reply=yes \ org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager \ ListUnitsByPatterns asas 1 "active" 2 "systemd-*.socket" "*.mount" From a43dfaa31bae5c2f95cf84736a81d6d18b1a3577 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:34:52 +0900 Subject: [PATCH 1642/2155] dhcp-message: introduce dhcp_message_{append,get}_option_sub_tlv() This is for e.g. Vendor-Specific Information option. --- src/libsystemd-network/dhcp-message.c | 46 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 27 +++++++++++++ 3 files changed, 75 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 92f7d5ed8c0e5..e1bea1cd2ce59 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -306,6 +306,25 @@ int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, return dhcp_message_append_option_fqdn(message, flags, is_client, hostname); } +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv) { + int r; + + assert(message); + + if (tlv_isempty(tlv)) + return 0; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(tlv, &iov); + if (r < 0) + return r; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -579,6 +598,33 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); } +int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) { + int r; + + assert(message); + assert(!FLAGS_SET(flags, TLV_TEMPORARY)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(flags); + if (!tlv) + return -ENOMEM; + + r = tlv_parse(tlv, &iov); + if (r < 0) + return r; + + if (tlv_isempty(tlv)) + return -ENODATA; + + if (ret) + *ret = TAKE_PTR(tlv); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index ad1688389288e..e4d98ad3e40a0 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -42,6 +42,7 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -58,6 +59,7 @@ int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); +int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 816edc9fc69e7..9989e502e2413 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -114,6 +114,20 @@ static void verify_hostname(sd_dhcp_message *m, const char *expected) { ASSERT_STREQ(s, expected); } +static void verify_sub_tlv(sd_dhcp_message *m, TLV *expected) { + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + TLV_DHCP4_SUBOPTION, + &tlv)); + + _cleanup_(iovec_done) struct iovec iov = {}, iov_expected = {}; + ASSERT_OK(tlv_build(tlv, &iov)); + ASSERT_OK(tlv_build(expected, &iov_expected)); + ASSERT_TRUE(iovec_equal(&iov, &iov_expected)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -153,6 +167,13 @@ TEST(dhcp_message) { const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + _cleanup_(tlv_done) TLV vendor = TLV_INIT(TLV_DHCP4_SUBOPTION); + for (unsigned i = 0; i < 3; i++) { + uint8_t buf[255]; + memset(buf, 42 + i, sizeof(buf)); + ASSERT_OK(tlv_append(&vendor, i + 1, 255, buf)); + } + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -243,6 +264,11 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname)); verify_hostname(m, hostname); + /* vendor specific */ + ASSERT_OK(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor)); + ASSERT_ERROR(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor), EEXIST); + verify_sub_tlv(m, &vendor); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -274,6 +300,7 @@ TEST(dhcp_message) { verify_client_id(m2, &id); verify_prl(m2, prl); verify_hostname(m2, hostname); + verify_sub_tlv(m2, &vendor); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 295a4dd6edf4a407a687e418ce97b9d5a6b033e2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:27:45 +0900 Subject: [PATCH 1643/2155] dhcp-message: introduce dhcp_message_{append,get}_option_length_prefixed_data() This is for e.g. User Class option. --- src/libsystemd-network/dhcp-message.c | 49 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 22 ++++++++++ 3 files changed, 73 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index e1bea1cd2ce59..c2c25ed6ff665 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -325,6 +325,27 @@ int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, c return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); } +int dhcp_message_append_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + const struct iovec_wrapper *iovw) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = iovw_merge(iovw, length_size, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return 0; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -625,6 +646,34 @@ int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVF return 0; } +int dhcp_message_get_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + struct iovec_wrapper *ret) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovec_split(&iov, length_size, &iovw); + if (r < 0) + return r; + + if (iovw_isempty(&iovw)) + return -ENODATA; + + if (ret) + *ret = TAKE_STRUCT(iovw); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e4d98ad3e40a0..4b85a26e92213 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -43,6 +43,7 @@ int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv); +int dhcp_message_append_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, const struct iovec_wrapper *iovw); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -60,6 +61,7 @@ int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, c int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); +int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 9989e502e2413..effdf92dc709b 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -128,6 +128,12 @@ static void verify_sub_tlv(sd_dhcp_message *m, TLV *expected) { ASSERT_TRUE(iovec_equal(&iov, &iov_expected)); } +static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_wrapper *expected) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &iovw)); + ASSERT_TRUE(iovw_equal(&iovw, expected)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -167,6 +173,16 @@ TEST(dhcp_message) { const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + _cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}, user_class_1 = {}, user_class_2 = {}; + FOREACH_STRING(s, "hoge", "foo", "bar") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_1, s, strlen(s))); + } + FOREACH_STRING(s, "aaa", "bbb", "ccc") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_2, s, strlen(s))); + } + _cleanup_(tlv_done) TLV vendor = TLV_INIT(TLV_DHCP4_SUBOPTION); for (unsigned i = 0; i < 3; i++) { uint8_t buf[255]; @@ -269,6 +285,11 @@ TEST(dhcp_message) { ASSERT_ERROR(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor), EEXIST); verify_sub_tlv(m, &vendor); + /* user class */ + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_1)); + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_2)); + verify_length_prefixed_data(m, &user_class); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -301,6 +322,7 @@ TEST(dhcp_message) { verify_prl(m2, prl); verify_hostname(m2, hostname); verify_sub_tlv(m2, &vendor); + verify_length_prefixed_data(m2, &user_class); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From c588c311445a23b9ddc6b9647918e35be3b31991 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 12 Apr 2026 02:59:16 +0900 Subject: [PATCH 1644/2155] dhcp-message: introduce dhcp_message_get_option_domains() This is for e.g. DHCP option 119 (domain search). --- src/libsystemd-network/dhcp-message.c | 103 ++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 1 + src/libsystemd-network/test-dhcp-message.c | 118 +++++++++++++++++++++ 3 files changed, 222 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index c2c25ed6ff665..00e38ea9eeca5 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -6,6 +6,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#include "dns-def.h" #include "dns-domain.h" #include "errno-util.h" #include "ether-addr-util.h" @@ -619,6 +620,108 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); } +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) { + int r; + + assert(message); + + /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + const uint8_t *buf = iov.iov_base; + size_t len = iov.iov_len; + + _cleanup_strv_free_ char **names = NULL; + size_t n_names = 0; + + _cleanup_free_ char *name = NULL; + size_t n = 0; + + for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) { + uint8_t c = buf[pos++]; + + if (c == 0) { + /* End of name */ + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized)); + if (r < 0) + return r; + + if (next_chunk != 0) + pos = next_chunk; + + next_chunk = 0; + jump_barrier = pos; + + name = mfree(name); + n = 0; + + } else if (c <= 63) { + /* Literal label */ + + const char *label = (const char*) (buf + pos); + pos += c; + + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */ + return -EBADMSG; + + /* Save the current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos + 1; + + pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]); + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (pos >= jump_barrier) + return -EBADMSG; + + jump_barrier = pos; + + } else + return -EBADMSG; + } + + if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */ + return -EBADMSG; + + if (strv_isempty(names)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(names); + return 0; +} + int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) { int r; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 4b85a26e92213..e9e4551cd80bc 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -60,6 +60,7 @@ int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret); int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index effdf92dc709b..ddb3823f25435 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -330,4 +330,122 @@ TEST(dhcp_message) { ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); } +static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); +} + +static void test_domains_fail(size_t len, const uint8_t *data) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG); +} + +TEST(domains) { + /* simple */ + static uint8_t simple[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 4, 'h', 'o', 'g', 'e', + 3, 'f', 'o', 'o', + 0, + }; + test_domains_one(ELEMENTSOF(simple), simple, + STRV_MAKE("example.com", "hoge.foo")); + + /* compressed */ + static uint8_t compressed[] = { + 4, 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 5, + 3, 'b', 'a', 'r', + 0xc0, 18, + 3, 'b', 'a', 'z', + 0xc0, 0, + }; + test_domains_one(ELEMENTSOF(compressed), compressed, + STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com")); + + /* invalid pointer */ + static uint8_t invalid[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 0xff, + }; + test_domains_fail(ELEMENTSOF(invalid), invalid); + + /* forward pointer */ + static uint8_t forward[] = { + 4, 'h', 'o', 'g', 'e', + 0xc0, 11, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + }; + test_domains_fail(ELEMENTSOF(forward), forward); + + /* infinite loop */ + static uint8_t loop1[] = { + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop1), loop1); + + static uint8_t loop2[] = { + 0xc0, 0x02, + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop2), loop2); + + static uint8_t loop3[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop3), loop3); + + /* unterminated */ + static uint8_t unterminated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + }; + test_domains_fail(ELEMENTSOF(unterminated), unterminated); + + /* truncated label */ + static uint8_t truncated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + }; + test_domains_fail(ELEMENTSOF(truncated), truncated); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From e178adc6e167e4079adb35f41bf32d3aa0ebf69f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 16:09:10 +0900 Subject: [PATCH 1645/2155] dhcp-message: add SIP server option support The DHCP option 120 (SIP server) option takes a list of addresses or domain names, and the first byte in the data classifies which type is stored. Let's extend _addresses() and _domains() to make them support the SIP server option. --- src/libsystemd-network/dhcp-message.c | 59 ++++++++++++++++++++-- src/libsystemd-network/test-dhcp-message.c | 44 ++++++++++++++-- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 00e38ea9eeca5..50cc25aded68d 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -191,6 +191,24 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, if (size_multiply_overflow(sizeof(struct in_addr), n_addr)) return -ENOBUFS; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (dhcp_message_has_option(message, SD_DHCP_OPTION_SIP_SERVER)) + return -EEXIST; + + size_t len = size_add(1, sizeof(struct in_addr) * n_addr); + if (len == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + buf[0] = 1; /* 'enc' field, 0: domains, 1: addresses */ + memcpy(buf + 1, addr, sizeof(struct in_addr) * n_addr); + + return dhcp_message_append_option(message, code, len, buf); + } + return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); } @@ -423,11 +441,22 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si assert(message); assert(ret_n_addr || !ret_addr); - _cleanup_(iovec_done) struct iovec iov = {}; - r = dhcp_message_get_option_alloc(message, code, &iov); + _cleanup_(iovec_done) struct iovec iov_free = {}; + r = dhcp_message_get_option_alloc(message, code, &iov_free); if (r < 0) return r; + struct iovec iov = iov_free; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (!iovec_is_set(&iov)) + return -EBADMSG; + + if (*(uint8_t*) iov.iov_base != 1) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + iovec_inc(&iov, 1); + } + if (iov.iov_len % sizeof(struct in_addr) != 0) return -EBADMSG; @@ -435,8 +464,17 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si if (n == 0) return -ENODATA; - if (ret_addr) - *ret_addr = (struct in_addr*) TAKE_PTR(iov.iov_base); + if (ret_addr) { + if (code == SD_DHCP_OPTION_SIP_SERVER) { + struct in_addr *addr = newdup(struct in_addr, iov.iov_base, n); + if (!addr) + return -ENOMEM; + *ret_addr = addr; + } else { + *ret_addr = iov.iov_base; + TAKE_STRUCT(iov_free); + } + } if (ret_n_addr) *ret_n_addr = n; return 0; @@ -625,7 +663,7 @@ int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char assert(message); - /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH. */ + /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH and SD_DHCP_OPTION_SIP_SERVER. */ _cleanup_(iovec_done) struct iovec iov = {}; r = dhcp_message_get_option_alloc(message, code, &iov); @@ -635,6 +673,17 @@ int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char const uint8_t *buf = iov.iov_base; size_t len = iov.iov_len; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (len == 0) + return -EBADMSG; + + if (buf[0] != 0) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + len--; + buf++; + } + _cleanup_strv_free_ char **names = NULL; size_t n_names = 0; diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index ddb3823f25435..1aa5830008323 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -68,7 +68,8 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { static void verify_addresses( sd_dhcp_message *m, - size_t n_ntp, const struct in_addr *ntp) { + size_t n_ntp, const struct in_addr *ntp, + size_t n_sip, const struct in_addr *sip) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr)); @@ -81,6 +82,14 @@ static void verify_addresses( ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs)); ASSERT_EQ(n, n_ntp); ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); + + ASSERT_ERROR(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + ASSERT_ERROR(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + + addrs = mfree(addrs); + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_sip); + ASSERT_EQ(memcmp(addrs, sip, sizeof(struct in_addr) * n), 0); } static void verify_string(sd_dhcp_message *m, const char *expected) { @@ -160,6 +169,14 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + /* 192.0.2.17 - 20 */ + struct in_addr sip[4] = { + { .s_addr = htobe32(0xC0000211) }, + { .s_addr = htobe32(0xC0000212) }, + { .s_addr = htobe32(0xC0000213) }, + { .s_addr = htobe32(0xC0000214) }, + }; + sd_dhcp_client_id id = { .raw = { 1, 3, 3, 3, 3, 3, 3, }, .size = 7, @@ -242,7 +259,10 @@ TEST(dhcp_message) { /* multiple addresses */ ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0])); ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); - verify_addresses(m, ELEMENTSOF(ntp), ntp); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip)); + ASSERT_ERROR(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, 0, NULL)); + verify_addresses(m, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); /* string */ ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); @@ -316,7 +336,7 @@ TEST(dhcp_message) { verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); - verify_addresses(m2, ELEMENTSOF(ntp), ntp); + verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); verify_string(m2, vendor_class); verify_client_id(m2, &id); verify_prl(m2, prl); @@ -346,6 +366,24 @@ static void test_domains_one(size_t len, const uint8_t *data, char * const *expe ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2)); ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); ASSERT_TRUE(strv_equal(strv, expected)); + + strv = strv_free(strv); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_SIP_SERVER); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, (len + 1) / 2, sip)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1 - (len + 1) / 2, sip + (len + 1) / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); } static void test_domains_fail(size_t len, const uint8_t *data) { From 08249b359bf697fe09330ea3c506ecdff2e78e6c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 12 Apr 2026 04:50:05 +0900 Subject: [PATCH 1646/2155] dhcp: move definition of sd_dhcp_route and related functions to dhcp-route.[ch] This also renames arguments for storing results. No functional change, just refactoring and preparation for later commits. --- src/libsystemd-network/dhcp-lease-internal.h | 6 ---- src/libsystemd-network/dhcp-route.c | 29 ++++++++++++++++++++ src/libsystemd-network/dhcp-route.h | 12 ++++++++ src/libsystemd-network/meson.build | 1 + src/libsystemd-network/network-internal.c | 4 ++- src/libsystemd-network/sd-dhcp-lease.c | 25 +---------------- src/network/test-network.c | 2 +- src/systemd/sd-dhcp-lease.h | 6 ++-- 8 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 src/libsystemd-network/dhcp-route.c create mode 100644 src/libsystemd-network/dhcp-route.h diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index b436287aeed04..bc411a4f36cd3 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -12,12 +12,6 @@ #include "sd-forward.h" #include "list.h" -struct sd_dhcp_route { - struct in_addr dst_addr; - struct in_addr gw_addr; - unsigned char dst_prefixlen; -}; - struct sd_dhcp_raw_option { LIST_FIELDS(struct sd_dhcp_raw_option, options); diff --git a/src/libsystemd-network/dhcp-route.c b/src/libsystemd-network/dhcp-route.c new file mode 100644 index 0000000000000..33828276ace43 --- /dev/null +++ b/src/libsystemd-network/dhcp-route.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-lease.h" + +#include "dhcp-route.h" /* IWYU pragma: keep */ + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->gw_addr; + return 0; +} diff --git a/src/libsystemd-network/dhcp-route.h b/src/libsystemd-network/dhcp-route.h new file mode 100644 index 0000000000000..e313c08a586ba --- /dev/null +++ b/src/libsystemd-network/dhcp-route.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "in-addr-util.h" /* IWYU pragma: keep */ + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + uint8_t dst_prefixlen; +}; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 500882319e63e..74e09fac84cb1 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -8,6 +8,7 @@ libsystemd_network_sources = files( 'dhcp-option.c', 'dhcp-packet.c', 'dhcp-protocol.c', + 'dhcp-route.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index c1eaa361afa0d..cc29d9fd1b14e 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -3,8 +3,10 @@ #include #include +#include "sd-dhcp-lease.h" + #include "alloc-util.h" -#include "dhcp-lease-internal.h" +#include "dhcp-route.h" #include "dns-resolver-internal.h" #include "extract-word.h" #include "hexdecoct.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index bfe4874be5a5f..8003f37fd3122 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "dhcp-lease-internal.h" #include "dhcp-option.h" +#include "dhcp-route.h" #include "dns-def.h" #include "dns-domain.h" #include "dns-resolver-internal.h" @@ -1802,27 +1803,3 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { *ret = lease->timezone; return 0; } - -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { - assert_return(route, -EINVAL); - assert_return(destination, -EINVAL); - - *destination = route->dst_addr; - return 0; -} - -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { - assert_return(route, -EINVAL); - assert_return(length, -EINVAL); - - *length = route->dst_prefixlen; - return 0; -} - -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { - assert_return(route, -EINVAL); - assert_return(gateway, -EINVAL); - - *gateway = route->gw_addr; - return 0; -} diff --git a/src/network/test-network.c b/src/network/test-network.c index ab3a86e10b193..c78c50e3a7573 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -3,7 +3,7 @@ #include #include "alloc-util.h" -#include "dhcp-lease-internal.h" +#include "dhcp-route.h" #include "hashmap.h" #include "hostname-setup.h" #include "network-internal.h" diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 3aa67c3fa540b..2dbee5450d5eb 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -86,9 +86,9 @@ int sd_dhcp_lease_get_6rd( size_t *ret_n_br_addresses); int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease); -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); From ff9d98423c6101ce7e1bd8787ba03e220fd78451 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 12 Apr 2026 06:18:50 +0900 Subject: [PATCH 1647/2155] dhcp-message: introduce dhcp_message_{append,get}_option_routes() These are for DHCP options 33 (static route), 121 (classless static route), and 249 (private classless static route). --- src/libsystemd-network/dhcp-message.c | 216 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 45 +++++ 3 files changed, 263 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 50cc25aded68d..58d3664a07a75 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -6,11 +6,13 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#include "dhcp-route.h" #include "dns-def.h" #include "dns-domain.h" #include "errno-util.h" #include "ether-addr-util.h" #include "hostname-util.h" +#include "in-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" @@ -227,6 +229,88 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co return dhcp_message_append_option(message, code, strlen(data), data); } +static int dhcp_message_append_option_static_routes(sd_dhcp_message *message, size_t n_routes, const sd_dhcp_route *routes) { + int r; + + assert(message); + assert(routes || n_routes == 0); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ struct in_addr *buf = new(struct in_addr, 2 * n_routes); + if (!buf) + return -ENOMEM; + + size_t count = 0; + FOREACH_ARRAY(route, routes, n_routes) { + uint8_t prefixlen; + r = in4_addr_default_prefixlen(&route->dst_addr, &prefixlen); + if (r < 0) + return r; + + if (prefixlen != route->dst_prefixlen) + return -EINVAL; + + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, prefixlen); + + buf[count++] = dst; + buf[count++] = route->gw_addr; + } + + assert(count == 2 * n_routes); + + return dhcp_message_append_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, 2 * n_routes, buf); +} + +static int dhcp_message_append_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + assert(message); + assert(routes || n_routes == 0); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(1 + 2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, (1 + 2 * sizeof(struct in_addr)) * n_routes); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(route, routes, n_routes) { + if (route->dst_prefixlen > sizeof(struct in_addr) * 8) + return -EINVAL; + + *p++ = route->dst_prefixlen; + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, route->dst_prefixlen); + p = mempcpy(p, &dst, DIV_ROUND_UP(route->dst_prefixlen, 8)); + p = mempcpy(p, &route->gw_addr, sizeof(struct in_addr)); + } + + return dhcp_message_append_option(message, code, p - buf, buf); +} + +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_append_option_static_routes(message, n_routes, routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_append_option_classless_static_routes(message, code, n_routes, routes); + default: + return -EINVAL; + } +} + int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { assert(message); assert(id); @@ -512,6 +596,138 @@ int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char return 0; } +static int dhcp_message_get_option_static_routes(sd_dhcp_message *message, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + r = dhcp_message_get_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, &n, &addrs); + if (r < 0) + return r; + + if (n % 2 != 0) + return -EBADMSG; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (size_t i = 0; i < n; i += 2) { + struct in_addr dst = addrs[i]; + + uint8_t prefixlen; + if (in4_addr_default_prefixlen(&dst, &prefixlen) < 0) + continue; + + (void) in4_addr_mask(&dst, prefixlen); + + /* RFC 2132 section 5.8: + * The default route (0.0.0.0) is an illegal destination for a static route.*/ + if (in4_addr_is_null(&dst)) + continue; + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = addrs[i + 1], + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +static int dhcp_message_get_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (struct iovec i = iov; iovec_is_set(&i);) { + uint8_t prefixlen = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + + if (prefixlen > 32) + return -EBADMSG; + + size_t n = DIV_ROUND_UP(prefixlen, 8); + if (n > i.iov_len) + return -EBADMSG; + + struct in_addr dst = {}; + memcpy_safe(&dst, i.iov_base, n); + (void) in4_addr_mask(&dst, prefixlen); + iovec_inc(&i, n); + + if (i.iov_len < sizeof(struct in_addr)) + return -EBADMSG; + + struct in_addr gw; + memcpy(&gw, i.iov_base, sizeof(struct in_addr)); + iovec_inc(&i, sizeof(struct in_addr)); + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = gw, + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_get_option_static_routes(message, ret_n_routes, ret_routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_get_option_classless_static_routes(message, code, ret_n_routes, ret_routes); + default: + return -EINVAL; + } +} + int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { int r; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e9e4551cd80bc..ad6572b272e62 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -39,6 +39,7 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes); int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); @@ -55,6 +56,7 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes); int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 1aa5830008323..70d8f09554df9 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -6,6 +6,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#include "dhcp-route.h" #include "ether-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" @@ -105,6 +106,25 @@ static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) ASSERT_STREQ(s, joined); } +static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_route *expected) { + uint8_t code; + FOREACH_ARGUMENT(code, + SD_DHCP_OPTION_STATIC_ROUTE, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE) { + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n; + ASSERT_OK(dhcp_message_get_option_routes(m, code, &n, &routes)); + ASSERT_EQ(n, n_expected); + for (size_t i = 0; i < n; i++) { + ASSERT_EQ(routes[i].dst_addr.s_addr, expected[i].dst_addr.s_addr); + ASSERT_EQ(routes[i].gw_addr.s_addr, expected[i].gw_addr.s_addr); + ASSERT_EQ(routes[i].dst_prefixlen, expected[i].dst_prefixlen); + } + } +} + static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) { sd_dhcp_client_id id = {}; ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); @@ -177,6 +197,24 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000214) }, }; + struct sd_dhcp_route routes[3] = { + { /* class A: 10.0.0.0/8 -> 192.0.2.33 */ + .dst_addr = { .s_addr = htobe32(0x0A000000) }, + .gw_addr = { .s_addr = htobe32(0xC0000221) }, + .dst_prefixlen = 8, + }, + { /* class B: 172.16.0.0/16 -> 192.0.2.34 */ + .dst_addr = { .s_addr = htobe32(0xAC100000) }, + .gw_addr = { .s_addr = htobe32(0xC0000222) }, + .dst_prefixlen = 16, + }, + { /* class C: 192.168.0.0/24 -> 192.0.2.35 */ + .dst_addr = { .s_addr = htobe32(0xC0A80000) }, + .gw_addr = { .s_addr = htobe32(0xC0000223) }, + .dst_prefixlen = 24, + }, + }; + sd_dhcp_client_id id = { .raw = { 1, 3, 3, 3, 3, 3, 3, }, .size = 7, @@ -277,6 +315,12 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); verify_string(m, vendor_class); + /* routes */ + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + verify_routes(m, ELEMENTSOF(routes), routes); + /* client ID */ ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); verify_client_id(m, &id); @@ -338,6 +382,7 @@ TEST(dhcp_message) { verify_address(m2, &addr); verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); verify_string(m2, vendor_class); + verify_routes(m2, ELEMENTSOF(routes), routes); verify_client_id(m2, &id); verify_prl(m2, prl); verify_hostname(m2, hostname); From 64846d9d8a1d15cc8b1629272eeb711fef684be6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 13 Apr 2026 04:02:39 +0900 Subject: [PATCH 1648/2155] dhcp-message: introduce dhcp_message_{append,get}_option_6rd() These are for DHCP option 212 (6rd). --- src/libsystemd-network/dhcp-message.c | 122 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 14 +++ src/libsystemd-network/test-dhcp-message.c | 44 ++++++++ 3 files changed, 180 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 58d3664a07a75..676e87d6779a5 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -311,6 +311,56 @@ int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, si } } +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses) { + + assert(message); + assert(prefix); + assert(n_br_addresses == 0 || br_addresses); + + /* See RFC 5969 Section 7.1.1 and dhcp_message_get_option_6rd() below. */ + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_6RD)) + return -EEXIST; + + if (ipv4masklen > 32) + return -EINVAL; + + if (32 - ipv4masklen + prefixlen > 128) + return -EINVAL; + + if (n_br_addresses == 0) + return -EINVAL; + + if (size_multiply_overflow(sizeof(struct in_addr), n_br_addresses)) + return -ENOBUFS; + + size_t buflen = size_add(2 + sizeof(struct in6_addr), sizeof(struct in_addr) * n_br_addresses); + if (buflen == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, buflen); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + *p++ = ipv4masklen; + *p++ = prefixlen; + + struct in6_addr masked = *prefix; + (void) in6_addr_mask(&masked, prefixlen); + p = mempcpy(p, &masked, sizeof(struct in6_addr)); + + memcpy(p, br_addresses, n_br_addresses * sizeof(struct in_addr)); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_6RD, buflen, buf); +} + int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { assert(message); assert(id); @@ -728,6 +778,78 @@ int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_ } } +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses) { + + int r; + + assert(message); + assert(ret_n_br_addresses || !ret_br_addresses); + + /* See RFC 5969 Section 7.1.1 */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_6RD, &iov); + if (r < 0) + return r; + + /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ + if (iov.iov_len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || + (iov.iov_len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n_br_addresses = (iov.iov_len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); + assert(n_br_addresses > 0); /* We have already checked that in the above. */ + + const uint8_t *p = iov.iov_base; + + /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses + * within a given 6rd domain. This may be any value between 0 and 32. Any value + * greater than 32 is invalid. */ + uint8_t ipv4masklen = *p++; + if (ipv4masklen > 32) + return -EBADMSG; + + /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the + * purpose of bounds checking by DHCP option processing, the sum of + * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ + uint8_t prefixlen = *p++; + if (32 - ipv4masklen + prefixlen > 128) + return -EBADMSG; + + /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. + * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and + * MUST be initialized to zero by the sender and ignored by the receiver. */ + struct in6_addr prefix; + memcpy(&prefix, p, sizeof(struct in6_addr)); + (void) in6_addr_mask(&prefix, prefixlen); + p += sizeof(struct in6_addr); + + /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */ + if (ret_br_addresses) { + struct in_addr *br_addresses = newdup(struct in_addr, p, n_br_addresses); + if (!br_addresses) + return -ENOMEM; + + *ret_br_addresses = br_addresses; + } + + if (ret_ipv4masklen) + *ret_ipv4masklen = ipv4masklen; + if (ret_prefixlen) + *ret_prefixlen = prefixlen; + if (ret_prefix) + *ret_prefix = prefix; + if (ret_n_br_addresses) + *ret_n_br_addresses = n_br_addresses; + return 0; +} + int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { int r; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index ad6572b272e62..f754bde05ad72 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -40,6 +40,13 @@ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, c int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes); +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses); int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); @@ -57,6 +64,13 @@ int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, stru int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes); +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses); int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 70d8f09554df9..dd4f49f1fb16e 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -125,6 +125,28 @@ static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_r } } +static void verify_6rd( + sd_dhcp_message *m, + uint8_t expected_ipv4masklen, + uint8_t expected_prefixlen, + const struct in6_addr *expected_prefix, + size_t expected_n_br_addresses, + const struct in_addr *expected_br_addresses) { + + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + size_t n_br_addresses; + _cleanup_free_ struct in_addr *br_addresses = NULL; + + ASSERT_OK(dhcp_message_get_option_6rd(m, NULL, NULL, NULL, NULL, NULL)); + ASSERT_OK(dhcp_message_get_option_6rd(m, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses)); + ASSERT_EQ(ipv4masklen, expected_ipv4masklen); + ASSERT_EQ(prefixlen, expected_prefixlen); + ASSERT_TRUE(in6_addr_equal(&prefix, expected_prefix)); + ASSERT_EQ(n_br_addresses, expected_n_br_addresses); + ASSERT_EQ(memcmp(br_addresses, expected_br_addresses, sizeof(struct in_addr) * n_br_addresses), 0); +} + static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) { sd_dhcp_client_id id = {}; ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); @@ -215,6 +237,17 @@ TEST(dhcp_message) { }, }; + uint8_t sixrd_ipv4masklen = 24; + uint8_t sixrd_prefixlen = 64; + struct in6_addr sixrd_prefix = { + .s6_addr = { 0x20, 0x01, 0x0d, 0xb8, }, + }; + struct in_addr sixrd_br_addresses[3] = { + { .s_addr = htobe32(0xC0000231) }, + { .s_addr = htobe32(0xC0000232) }, + { .s_addr = htobe32(0xC0000233) }, + }; + sd_dhcp_client_id id = { .raw = { 1, 3, 3, 3, 3, 3, 3, }, .size = 7, @@ -321,6 +354,16 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); verify_routes(m, ELEMENTSOF(routes), routes); + /* 6rd */ + ASSERT_ERROR(dhcp_message_append_option_6rd(m, 33, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, 127, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 0, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, SIZE_MAX, sixrd_br_addresses), ENOBUFS); + ASSERT_OK(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses)); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_6RD, ELEMENTSOF(sixrd_br_addresses) - 1, sixrd_br_addresses + 1)); + verify_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); + /* client ID */ ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); verify_client_id(m, &id); @@ -383,6 +426,7 @@ TEST(dhcp_message) { verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); verify_string(m2, vendor_class); verify_routes(m2, ELEMENTSOF(routes), routes); + verify_6rd(m2, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); verify_client_id(m2, &id); verify_prl(m2, prl); verify_hostname(m2, hostname); From 7e612d5c15e61497b3aeb64fabe7dd8fe6f4aa40 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 13 Apr 2026 08:54:38 +0900 Subject: [PATCH 1649/2155] dhcp-message: introduce dhcp_message_get_option_dnr() This is for DHCP option 162 (DNR). --- src/libsystemd-network/dhcp-message.c | 159 +++++++++++++++++ src/libsystemd-network/dhcp-message.h | 1 + src/libsystemd-network/test-dhcp-message.c | 194 +++++++++++++++++++++ 3 files changed, 354 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 676e87d6779a5..e0ef4f06fb481 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -9,6 +9,7 @@ #include "dhcp-route.h" #include "dns-def.h" #include "dns-domain.h" +#include "dns-resolver-internal.h" #include "errno-util.h" #include "ether-addr-util.h" #include "hostname-util.h" @@ -20,6 +21,7 @@ #include "set.h" #include "sort-util.h" #include "string-util.h" +#include "unaligned.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { if (!message) @@ -1164,6 +1166,163 @@ int dhcp_message_get_option_length_prefixed_data( return 0; } +static int parse_dnr_one(const struct iovec *iov, sd_dns_resolver *ret) { + int r; + + assert(iovec_is_set(iov)); + assert(ret); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver resolver = {}; + struct iovec i = *iov; + + /* service priority */ + if (i.iov_len < sizeof(be16_t)) + return -EBADMSG; + + resolver.priority = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(be16_t)); + + /* RFC 9460 section 2.4.1: + * When SvcPriority is 0, the SVCB record is in AliasMode. + * + * We do not support the alias mode. But the entry itself is not invalid. */ + if (resolver.priority == 0) { + *ret = (sd_dns_resolver) {}; + return 0; + } + + /* authentication domain name */ + if (!iovec_is_set(&i)) + return -EBADMSG; + + size_t name_len = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + if (i.iov_len < name_len) + return -EBADMSG; + + const uint8_t *name_buf = i.iov_base; + iovec_inc(&i, name_len); + + r = dns_name_from_wire_format(&name_buf, &name_len, &resolver.auth_name); + if (r < 0) + return r; + if (r == 0 || name_len != 0) + return -EBADMSG; + + r = dns_name_is_valid_ldh(resolver.auth_name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (dns_name_is_root(resolver.auth_name)) + return -EBADMSG; + + /* RFC9463 section 3.1.6: In ADN-only mode, server omits everything after the ADN. + * + * We don't support these, but they are not invalid. */ + if (!iovec_is_set(&i)) { + *ret = (sd_dns_resolver) {}; + return 0; + } + + /* IPv4 addresses */ + size_t n = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + + if (n % sizeof(struct in_addr) != 0) + return -EBADMSG; + + n /= sizeof(struct in_addr); + + /* RFC9463 section 3.1.8: option MUST include at least one valid IP addr */ + if (n == 0) + return -EBADMSG; + + resolver.family = AF_INET; + resolver.n_addrs = n; + resolver.addrs = new(union in_addr_union, n); + if (!resolver.addrs) + return -ENOMEM; + + for (size_t j = 0; j < n; j++) { + if (i.iov_len < sizeof(struct in_addr)) + return -EBADMSG; + + struct in_addr a; + memcpy(&a, i.iov_base, sizeof(struct in_addr)); + iovec_inc(&i, sizeof(struct in_addr)); + + /* RFC9463 section 5.2: client MUST discard multicast and host loopback addresses */ + if (in4_addr_is_multicast(&a) || in4_addr_is_localhost(&a)) + return -EBADMSG; + + resolver.addrs[j] = (union in_addr_union) { .in = a }; + } + + /* service params */ + r = dnr_parse_svc_params(i.iov_base, i.iov_len, &resolver); + if (r < 0) + return r; + if (r == 0) { + /* We can't use this record, but it is not invalid. */ + *ret = (sd_dns_resolver) {}; + return 0; + } + + *ret = TAKE_STRUCT(resolver); + return 1; +} + +int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers) { + int r; + + assert(message); + assert(ret_n_resolvers || !ret_resolvers); + + /* See RFC 9463 section 5.1 */ + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = dhcp_message_get_option_length_prefixed_data(message, SD_DHCP_OPTION_V4_DNR, /* length_size= */ 2, &iovw); + if (r < 0) + return r; + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + FOREACH_ARRAY(i, iovw.iovec, iovw.count) { + _cleanup_(sd_dns_resolver_done) sd_dns_resolver dnr = {}; + r = parse_dnr_one(i, &dnr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_resolvers) { + n_resolvers++; + continue; + } + + if (!GREEDY_REALLOC(resolvers, n_resolvers + 1)) + return -ENOMEM; + + resolvers[n_resolvers++] = TAKE_STRUCT(dnr); + } + + if (n_resolvers == 0) /* no supported resolver */ + return -ENODATA; + + if (ret_resolvers) { + /* Sort the resolvers with their priorities. */ + typesafe_qsort(resolvers, n_resolvers, dns_resolver_prio_compare); + *ret_resolvers = TAKE_PTR(resolvers); + } + if (ret_n_resolvers) + *ret_n_resolvers = n_resolvers; + + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index f754bde05ad72..e003d9cc5bf20 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -79,6 +79,7 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret); int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret); +int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers); int dhcp_message_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index dd4f49f1fb16e..7f5d6669a9495 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -7,6 +7,8 @@ #include "dhcp-message.h" #include "dhcp-protocol.h" #include "dhcp-route.h" +#include "dns-packet.h" +#include "dns-resolver-internal.h" #include "ether-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" @@ -575,4 +577,196 @@ TEST(domains) { test_domains_fail(ELEMENTSOF(truncated), truncated); } +TEST(dnr) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + + static uint8_t data[] = { + /* Instance 1 */ + /* length */ + 0, 78, + /* priority */ + 0, 1, + /* authentication domain name */ + 22, + 8, 'r', 'e', 's', 'o', 'l', 'v', 'e', 'r', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 8, + 192, 0, 2, 1, + 192, 0, 2, 2, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 14, + 2, 'h', '2', + 2, 'h', '3', + 3, 'd', 'o', 't', + 3, 'd', 'o', 'q', + /* port */ + 0, DNS_SVC_PARAM_KEY_PORT, + 0, 2, + 0, 42, + /* DoH path*/ + 0, DNS_SVC_PARAM_KEY_DOHPATH, + 0, 16, + '/', 'd', 'n', 's', '-', 'q', 'u', 'e', 'r', 'y', '{', '?', 'd', 'n', 's', '}', + + /* Instance 2 */ + /* length */ + 0, 44, + /* priority */ + 0, 2, + /* authentication domain name */ + 22, + 8, 'h', 'o', 'g', 'e', 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 3, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 4, + 3, 'd', 'o', 't', + /* port */ + 0, DNS_SVC_PARAM_KEY_PORT, + 0, 2, + 0, 33, + + /* Instance 3 (no address, ignored) */ + /* length */ + 0, 20, + /* priority */ + 0, 3, + /* authentication domain name */ + 17, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + + /* Instance 4 (unknown alpn, ignored) */ + /* length */ + 0, 37, + /* priority */ + 0, 4, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 4, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 5, + 4, 'h', 'o', 'g', 'e', + }; + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(data), data)); + ASSERT_OK(dhcp_message_get_option_dnr(m, &n_resolvers, &resolvers)); + ASSERT_EQ(n_resolvers, 2u); + + ASSERT_EQ(resolvers[0].priority, 1u); + ASSERT_STREQ(resolvers[0].auth_name, "resolver.example.com"); + ASSERT_EQ(resolvers[0].family, AF_INET); + ASSERT_EQ(resolvers[0].n_addrs, 2u); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[0]), "192.0.2.1"); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[1]), "192.0.2.2"); + ASSERT_EQ(resolvers[0].transports, SD_DNS_ALPN_HTTP_2_TLS | SD_DNS_ALPN_HTTP_3 | SD_DNS_ALPN_DOT | SD_DNS_ALPN_DOQ); + ASSERT_EQ(resolvers[0].port, 42u); + ASSERT_STREQ(resolvers[0].dohpath, "/dns-query{?dns}"); + + ASSERT_EQ(resolvers[1].priority, 2u); + ASSERT_STREQ(resolvers[1].auth_name, "hogehoge.example.com"); + ASSERT_EQ(resolvers[1].family, AF_INET); + ASSERT_EQ(resolvers[1].n_addrs, 1u); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[1].family, &resolvers[1].addrs[0]), "192.0.2.3"); + ASSERT_EQ(resolvers[1].transports, SD_DNS_ALPN_DOT); + ASSERT_EQ(resolvers[1].port, 33u); + ASSERT_NULL(resolvers[1].dohpath); + + /* missing DoH path */ + static uint8_t invalid[] = { + /* length */ + 0, 35, + /* priority */ + 0, 5, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 5, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 3, + 2, 'h', '2', + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid), invalid)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR); + + /* missing ALPN */ + static uint8_t invalid2[] = { + /* length */ + 0, 28, + /* priority */ + 0, 6, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 6, + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid2), invalid2)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR); + + /* truncated domain name */ + static uint8_t invalid3[] = { + /* length */ + 0, 34, + /* priority */ + 0, 7, + /* authentication domain name */ + 18, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + /* addresses */ + 4, + 192, 0, 2, 7, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 4, + 3, 'd', 'o', 't', + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid3), invalid3)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EMSGSIZE); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 5d60cd8539b8c931ac157cc4c94e71df094610cd Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 21:54:06 +0200 Subject: [PATCH 1650/2155] syscall: add kexec_file_load to the generated override header This makes __NR_kexec_file_load available on architectures where the kernel UAPI headers don't define it, matching the runtime fallback path in src/libc/kexec.c which is gated on #ifdef __NR_kexec_file_load. --- src/include/override/sys/generate-syscall.py | 1 + src/include/override/sys/syscall.h | 70 ++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 1c90ad0e38402..4391df6da349b 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -7,6 +7,7 @@ # We only generate numbers for a dozen or so syscalls SYSCALLS = [ 'fchmodat2', # defined in glibc header since glibc-2.39 + 'kexec_file_load', 'open_tree_attr', 'quotactl_fd', # defined in glibc header since glibc-2.35 'removexattrat', diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index 0233f254b421c..108885b725a31 100644 --- a/src/include/override/sys/syscall.h +++ b/src/include/override/sys/syscall.h @@ -124,6 +124,76 @@ static_assert(__NR_fchmodat2 == systemd_NR_fchmodat2, ""); # endif #endif +#ifndef __IGNORE_kexec_file_load +# if defined(__aarch64__) +# define systemd_NR_kexec_file_load 294 +# elif defined(__alpha__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__arc__) || defined(__tilegx__) +# define systemd_NR_kexec_file_load 294 +# elif defined(__arm__) +# define systemd_NR_kexec_file_load 401 +# elif defined(__i386__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__ia64__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__loongarch_lp64) +# define systemd_NR_kexec_file_load 294 +# elif defined(__m68k__) +# define systemd_NR_kexec_file_load -1 +# elif defined(_MIPS_SIM) +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define systemd_NR_kexec_file_load -1 +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# define systemd_NR_kexec_file_load -1 +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# define systemd_NR_kexec_file_load -1 +# else +# error "Unknown MIPS ABI" +# endif +# elif defined(__hppa__) +# define systemd_NR_kexec_file_load 355 +# elif defined(__powerpc__) +# define systemd_NR_kexec_file_load 382 +# elif defined(__riscv) +# if __riscv_xlen == 32 +# define systemd_NR_kexec_file_load 294 +# elif __riscv_xlen == 64 +# define systemd_NR_kexec_file_load 294 +# else +# error "Unknown RISC-V ABI" +# endif +# elif defined(__s390__) +# define systemd_NR_kexec_file_load 381 +# elif defined(__sh__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__sparc__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__x86_64__) +# if defined(__ILP32__) +# define systemd_NR_kexec_file_load (320 | /* __X32_SYSCALL_BIT */ 0x40000000) +# else +# define systemd_NR_kexec_file_load 320 +# endif +# elif !defined(missing_arch_template) +# warning "kexec_file_load() syscall number is unknown for your architecture" +# endif + +/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ +# if defined __NR_kexec_file_load && __NR_kexec_file_load >= 0 +# if defined systemd_NR_kexec_file_load +static_assert(__NR_kexec_file_load == systemd_NR_kexec_file_load, ""); +# endif +# else +# if defined __NR_kexec_file_load +# undef __NR_kexec_file_load +# endif +# if defined systemd_NR_kexec_file_load && systemd_NR_kexec_file_load >= 0 +# define __NR_kexec_file_load systemd_NR_kexec_file_load +# endif +# endif +#endif + #ifndef __IGNORE_open_tree_attr # if defined(__aarch64__) # define systemd_NR_open_tree_attr 467 From e794b904daf89b28f98db7910edd32398e63f0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 07:47:40 +0200 Subject: [PATCH 1651/2155] shared/verbs: allow all groups to be named When verb groups were added, I assumed that the first group will always by the unnamed group, or in other words, that VERB_GROUP() line cannot appear first. This provides an additional check on the whether the verbs haven't been reordered by the compiler or linker. But that check is weak and we can do a better check anyway. And this limitation is unexpected, since we allow that for OPTIONs. The code should all work without an unnamed group, once this assertion is removed. --- src/shared/verbs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 274945e5c5977..e488af63fecd4 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -83,7 +83,6 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e assert(verbs); assert(verbs_end > verbs); - assert(verbs[0].dispatch); assert(verbs[0].verb); const char *name = args ? args[0] : NULL; @@ -101,6 +100,7 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e if (r < 0) return log_oom(); } + assert(!strv_isempty(verb_strv)); /* At least one verb should be defined… */ if (name) { /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ @@ -121,7 +121,7 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e "Command verb required (one of %s).", joined); } - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb '%s' required.", verbs[0].verb); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb '%s' required.", verb_strv[0]); } if (!name) From 5b4d4b41a909de80545bcd65eda74ea20df36678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 12 May 2026 15:27:52 +0200 Subject: [PATCH 1652/2155] loginctl: convert to OPTION and VERB macros --help output is the same, except for the expected formatting changes and moving of --no-pager/--no-legend/--no-ask-password to the end. Co-developed-by: Claude Opus 4.7 --- src/login/loginctl.c | 327 +++++++++++++++++++------------------------ 1 file changed, 143 insertions(+), 184 deletions(-) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 73dbc1f7163b7..5647621ba5460 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -8,6 +7,7 @@ #include "sd-journal.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -19,14 +19,15 @@ #include "cgroup-util.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "process-util.h" #include "runtime-scope.h" #include "string-table.h" @@ -266,6 +267,10 @@ static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, return 0; } +VERB_GROUP("Session Commands"); + +VERB(verb_list_sessions, "list-sessions", NULL, VERB_ANY, 1, VERB_DEFAULT, + "List sessions"); 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; @@ -918,6 +923,10 @@ static int get_bus_path_by_id( return strdup_to(ret, path); } +VERB(verb_show_session, "session-status", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Show session status"); +VERB(verb_show_session, "show-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Show properties of sessions or the manager"); static int verb_show_session(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; @@ -964,6 +973,12 @@ static int verb_show_session(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_activate, "activate", "[ID]", VERB_ANY, 2, 0, + "Activate a session"); +VERB(verb_activate, "lock-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Screen lock one or more sessions"); +VERB(verb_activate, "unlock-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Screen unlock one or more sessions"); 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); @@ -1007,6 +1022,8 @@ static int verb_activate(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB_NOARG(verb_lock_sessions, "lock-sessions", "Screen lock all current sessions"); +VERB_NOARG(verb_lock_sessions, "unlock-sessions", "Screen unlock all current sessions"); 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); @@ -1028,6 +1045,12 @@ static int verb_lock_sessions(int argc, char *argv[], uintptr_t _data, void *use return 0; } +/* The implementation is above, but we put this here to preserve the logical order in --help. */ +VERB(verb_activate, "terminate-session", "ID…", 2, VERB_ANY, 0, + "Terminate one or more sessions"); + +VERB(verb_kill_session, "kill-session", "ID…", 2, VERB_ANY, 0, + "Send signal to processes of a session"); 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); @@ -1054,6 +1077,9 @@ static int verb_kill_session(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB_GROUP("User Commands"); + +VERB_NOARG(verb_list_users, "list-users", "List users"); static int verb_list_users(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct bus_properties_map property_map[] = { @@ -1130,6 +1156,10 @@ static int verb_list_users(int argc, char *argv[], uintptr_t _data, void *userda return list_table_print(table, "users"); } +VERB(verb_show_user, "user-status", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show user status"); +VERB(verb_show_user, "show-user", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show properties of users or the manager"); static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; @@ -1181,6 +1211,10 @@ static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB(verb_enable_linger, "enable-linger", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Enable linger state of one or more users"); +VERB(verb_enable_linger, "disable-linger", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Disable linger state of one or more users"); 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); @@ -1229,6 +1263,8 @@ static int verb_enable_linger(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB(verb_terminate_user, "terminate-user", "USER…", 2, VERB_ANY, 0, + "Terminate all sessions of one or more users"); 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); @@ -1259,6 +1295,8 @@ static int verb_terminate_user(int argc, char *argv[], uintptr_t _data, void *us return 0; } +VERB(verb_kill_user, "kill-user", "USER…", 2, VERB_ANY, 0, + "Send signal to processes of a user"); 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); @@ -1297,6 +1335,9 @@ static int verb_kill_user(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB_GROUP("Seat Commands"); + +VERB_NOARG(verb_list_seats, "list-seats", "List seats"); 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; @@ -1341,6 +1382,10 @@ static int verb_list_seats(int argc, char *argv[], uintptr_t _data, void *userda return list_table_print(table, "seats"); } +VERB(verb_show_seat, "seat-status", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show seat status"); +VERB(verb_show_seat, "show-seat", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of seats or the manager"); static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; @@ -1387,6 +1432,8 @@ static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB(verb_attach, "attach", "NAME DEVICE…", 3, VERB_ANY, 0, + "Attach one or more devices to a seat"); 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); @@ -1411,6 +1458,7 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB_NOARG(verb_flush_devices, "flush-devices", "Flush all device associations"); 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); @@ -1427,6 +1475,8 @@ static int verb_flush_devices(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB(verb_terminate_seat, "terminate-seat", "NAME…", 2, VERB_ANY, 0, + "Terminate all sessions on one or more seats"); 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); @@ -1447,190 +1497,127 @@ static int verb_terminate_seat(int argc, char *argv[], uintptr_t _data, void *us } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char *const groups[] = { + "Session Commands", + "User Commands", + "Seat Commands", + }; + + Table *vtables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("loginctl", "1", &link); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = verbs_get_help_table_group(groups[i], &vtables[i]); + if (r < 0) + return r; + } + + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sSend control commands to or query the login manager.%6$s\n" - "\n%3$sSession Commands:%4$s\n" - " list-sessions List sessions\n" - " session-status [ID...] Show session status\n" - " show-session [ID...] Show properties of sessions or the manager\n" - " activate [ID] Activate a session\n" - " lock-session [ID...] Screen lock one or more sessions\n" - " unlock-session [ID...] Screen unlock one or more sessions\n" - " lock-sessions Screen lock all current sessions\n" - " unlock-sessions Screen unlock all current sessions\n" - " terminate-session ID... Terminate one or more sessions\n" - " kill-session ID... Send signal to processes of a session\n" - "\n%3$sUser Commands:%4$s\n" - " list-users List users\n" - " user-status [USER...] Show user status\n" - " show-user [USER...] Show properties of users or the manager\n" - " enable-linger [USER...] Enable linger state of one or more users\n" - " disable-linger [USER...] Disable linger state of one or more users\n" - " terminate-user USER... Terminate all sessions of one or more users\n" - " kill-user USER... Send signal to processes of a user\n" - "\n%3$sSeat Commands:%4$s\n" - " list-seats List seats\n" - " seat-status [NAME...] Show seat status\n" - " show-seat [NAME...] Show properties of seats or the manager\n" - " attach NAME DEVICE... Attach one or more devices to a seat\n" - " flush-devices Flush all device associations\n" - " terminate-seat NAME... Terminate all sessions on one or more seats\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -p --property=NAME Show only properties by this name\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" - " -l --full Do not ellipsize output\n" - " --kill-whom=WHOM Whom to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --json=MODE Generate JSON output for list-sessions/users/seats\n" - " (takes one of pretty, short, or off)\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " -o --output=MODE Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " verbose, export, with-unit)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Don't prompt for password\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + assert_cc(ELEMENTSOF(vtables) == 3); + (void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2], options); - return 0; -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Send control commands to or query the login manager."); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_VALUE, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_KILL_WHOM, - ARG_NO_ASK_PASSWORD, - }; + help_section("Options"); + 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 }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - {} - }; + help_man_page_reference("loginctl", "1"); + return 0; +} - int c, r; +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'P': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - _fallthrough_; - - 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, opts.arg); if (r < 0) return log_oom(); - /* If the user asked for a particular - * property, show it to them, even if it is + /* If the user asked for a particular property, show it to them, even if it is * empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + + if (opts.opt->short_code == 'P') + 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 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 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case 'o': - 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 '%s'.", optarg); - + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); + if (r <= 0) + return r; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - arg_legend = false; + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", opts.arg); break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; @@ -1639,78 +1626,50 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + arg_legend = false; break; - case 's': - r = parse_signal_argument(optarg, &arg_signal); - if (r <= 0) - return r; + OPTION('o', "output", "MODE", + "Change journal output mode (short, short-precise, short-iso, " + "short-iso-precise, short-full, short-monotonic, short-unix, short-delta, " + "json, json-pretty, json-sse, json-seq, cat, verbose, export, with-unit)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(opts.arg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output '%s'.", opts.arg); + 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_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } -static int loginctl_main(int argc, char *argv[], sd_bus *bus) { - static const Verb verbs[] = { - { "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 }, - {} - }; - - 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; @@ -1722,7 +1681,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return loginctl_main(argc, argv, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); From f795d5459151ad84acf77557cf47dddddb3b4bce Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 16:29:18 +0200 Subject: [PATCH 1653/2155] libc,shared: detect newer library symbols at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For libc syscall wrappers (pidfd_open, fsopen, openat2, etc.) we previously gated the calls behind build-time HAVE_* checks. Replace these with shim functions in src/libc/ that fall back to the raw syscall at runtime when the loaded glibc lacks the symbol. The infrastructure lives in src/libc/libc-shim.h: DEFINE_SYSCALL_SHIM falls back to a direct syscall, DEFINE_LIBC_SHIM returns ENOSYS (for posix_spawn-family helpers that have no corresponding syscall), and DEFINE_LIBC_ERRNO_SHIM sets errno=ENOSYS and returns -1 (for read/write-style helpers). The weak reference to the libc symbol is bound via __asm__(\"name\") rename so the bare libc identifier never appears as a C token — this avoids both #undef boilerplate against override-header redirects and the resulting -Wredundant-decls warning. Drop the corresponding cc.has_function() loop from meson.build. For optional libraries (libcryptsetup, libdw, libarchive), drop the per-symbol HAVE_* checks. Always declare the prototypes, suppressing the redundant-decl warnings via DISABLE_WARNING_REDUNDANT_DECLS and NOLINT, and resolve the symbols after the main dlopen via a new DLSYM_OPTIONAL() helper that only assigns on success. libcryptsetup's crypt_set_keyring_to_link / crypt_token_set_external_path and libarchive's *_is_set wrappers use fallback functions as their pointer initializers (returning -ENOSYS and 0 respectively), so call sites can invoke the symbol unconditionally and just check for -ENOSYS where the \"not supported\" distinction matters. The same shim treatment applies to pidfd_spawn / posix_spawnattr_setcgroup_np (src/libc/spawn.c) and epoll_pwait2 (src/libc/epoll.c), with corresponding override headers in src/include/override/spawn.h and src/include/override/sys/epoll.h. posix_spawn_wrapper() in process-util.c and epoll_wait_usec() in sd-event.c now detect ENOSYS in the return value instead of checking the function pointer, falling back to plain posix_spawn() and epoll_wait() respectively. coredump-config and coredump-submit get a dlopen_dw_has_dwfl_set_sysroot() helper. The kexec arch gate now uses defined(__NR_kexec_file_load) directly; pidfd.h uses __has_include_next() to decide whether to pull in glibc's header. This lets binaries built against newer glibc / libcryptsetup / libdw / libarchive headers still load and run on older targets where these symbols are absent. --- meson.build | 63 -------------------- src/basic/dlfcn-util.h | 13 ++++ src/basic/process-util.c | 27 +++++---- src/coredump/coredump-config.c | 5 +- src/coredump/coredump-submit.c | 8 +-- src/cryptsetup/cryptsetup.c | 12 ++-- src/include/musl/stdio.h | 8 +-- src/include/override/fcntl.h | 6 +- src/include/override/sched.h | 6 +- src/include/override/signal.h | 6 +- src/include/override/spawn.h | 21 +++++++ src/include/override/sys/bpf.h | 6 +- src/include/override/sys/epoll.h | 10 ++++ src/include/override/sys/ioprio.h | 12 ++-- src/include/override/sys/kcmp.h | 6 +- src/include/override/sys/kexec.h | 8 +-- src/include/override/sys/keyctl.h | 18 ++---- src/include/override/sys/mempolicy.h | 12 ++-- src/include/override/sys/mount.h | 56 +++++------------- src/include/override/sys/pidfd.h | 20 +++---- src/include/override/sys/quota.h | 6 +- src/include/override/sys/stat.h | 6 +- src/include/override/sys/xattr.h | 12 ++-- src/include/override/unistd.h | 6 +- src/libc/bpf.c | 13 ++-- src/libc/epoll.c | 12 ++++ src/libc/fcntl.c | 14 ++--- src/libc/ioprio.c | 21 +++---- src/libc/kcmp.c | 15 ++--- src/libc/kexec.c | 15 +++-- src/libc/keyctl.c | 36 ++++++------ src/libc/libc-shim.h | 76 ++++++++++++++++++++++++ src/libc/mempolicy.c | 24 ++++---- src/libc/meson.build | 2 + src/libc/mount.c | 88 ++++++++++++++-------------- src/libc/musl/stdio.c | 15 ++--- src/libc/pidfd.c | 22 ++++--- src/libc/quota.c | 14 ++--- src/libc/sched.c | 13 ++-- src/libc/signal.c | 14 ++--- src/libc/spawn.c | 17 ++++++ src/libc/stat.c | 14 ++--- src/libc/unistd.c | 11 ++-- src/libc/xattr.c | 26 ++++---- src/libsystemd/sd-event/sd-event.c | 15 +---- src/shared/cryptsetup-util.c | 39 ++++++------ src/shared/cryptsetup-util.h | 17 ++++-- src/shared/elf-util.c | 37 ++++++++---- src/shared/elf-util.h | 2 + src/shared/libarchive-util.c | 44 +++++++------- src/shared/libarchive-util.h | 24 ++++---- 51 files changed, 511 insertions(+), 482 deletions(-) create mode 100644 src/include/override/spawn.h create mode 100644 src/include/override/sys/epoll.h create mode 100644 src/libc/epoll.c create mode 100644 src/libc/libc-shim.h create mode 100644 src/libc/spawn.c diff --git a/meson.build b/meson.build index 2e78b359e6ea6..e84303c9cbab6 100644 --- a/meson.build +++ b/meson.build @@ -582,43 +582,6 @@ long_max = cc.compute_int( assert(long_max > 100000) conf.set_quoted('LONG_MAX_STR', f'@long_max@') -foreach ident : [ - ['renameat2', '''#include '''], # since musl-1.2.6 - ['set_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use - ['get_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use - ['epoll_pwait2', '''#include '''], # since glibc-2.35 - ['fsconfig', '''#include '''], # since glibc-2.36 - ['fsmount', '''#include '''], # since glibc-2.36 - ['fsopen', '''#include '''], # since glibc-2.36 - ['mount_setattr', '''#include '''], # since glibc-2.36 - ['move_mount', '''#include '''], # since glibc-2.36 - ['open_tree', '''#include '''], # since glibc-2.36 - ['openat2', '''#include '''], # since glibc-2.42 - ['pidfd_open', '''#include '''], # since glibc-2.36 - ['pidfd_send_signal', '''#include '''], # since glibc-2.36 - ['pidfd_spawn', '''#include '''], # since glibc-2.39 - ['sched_setattr', '''#include '''], # since glibc-2.41 - ['ioprio_get', '''#include '''], # no known header declares ioprio_get - ['ioprio_set', '''#include '''], # no known header declares ioprio_set - ['rt_tgsigqueueinfo', '''#include '''], # no known header declares rt_tgsigqueueinfo - ['open_tree_attr', '''#include '''], # no known header declares open_tree_attr - ['quotactl_fd', '''#include '''], # no known header declares quotactl_fd - ['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 - ['setxattrat', '''#include '''], # no known header declares setxattrat - ['removexattrat', '''#include '''], # no known header declares removexattrat - ['pivot_root', '''#include '''], # no known header declares pivot_root -] - - have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') - conf.set10('HAVE_' + ident[0].to_upper(), have) -endforeach - ##################################################################### awk = find_program('awk') @@ -1203,18 +1166,6 @@ conf.set10('HAVE_LIBCRYPTSETUP', have) conf.set10('HAVE_LIBCRYPTSETUP_PLUGINS', libcryptsetup_plugins.allowed() and have) -foreach ident : [ - 'crypt_set_keyring_to_link', # 2.7 - 'crypt_token_set_external_path', # 2.7 - ] - - have_ident = have and cc.has_function( - ident, - prefix : '#include ', - dependencies : libcryptsetup) - conf.set10('HAVE_' + ident.to_upper(), have_ident) -endforeach - libcurl = dependency('libcurl', version : '>= 7.32.0', required : get_option('libcurl')) @@ -1292,10 +1243,6 @@ libelf = dependency('libelf', libelf_cflags = libelf.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_ELFUTILS', libdw.found() and libelf.found()) -# New in elfutils 0.192 -conf.set10('HAVE_DWFL_SET_SYSROOT', - libdw.found() and cc.has_function('dwfl_set_sysroot', dependencies : libdw)) - libz = dependency('zlib', required : get_option('zlib')) conf.set10('HAVE_ZLIB', libz.found()) @@ -1365,16 +1312,6 @@ libarchive = dependency('libarchive', libarchive_cflags = libarchive.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBARCHIVE', libarchive.found()) -foreach ident : [ - 'archive_entry_gid_is_set', # since 3.7.3 - 'archive_entry_uid_is_set', # since 3.7.3 - 'archive_entry_hardlink_is_set', # since 3.7.5 - ] - - have = libarchive.found() and cc.has_function(ident, dependencies : libarchive) - conf.set10('HAVE_' + ident.to_upper(), have) -endforeach - libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : get_option('xkbcommon')) diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h index ccf0ec6be3b26..0fd78483f854c 100644 --- a/src/basic/dlfcn-util.h +++ b/src/basic/dlfcn-util.h @@ -33,6 +33,19 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l #define DLSYM_ARG_FORCE(arg) \ &sym_##arg, STRINGIFY(arg) +/* Resolve a single optional symbol from an already-opened library handle. The pointer variable is expected + * to be named sym_ (same convention as DLSYM_ARG). Only assigns on success, so the pointer keeps its + * pre-existing value if the symbol is not present — useful for fallback initialization. dlerror() is + * cleared first so callers can distinguish "symbol not found" from "symbol's value is NULL" by checking + * dlerror() after; for function symbols (which can never be NULL on success) the _v check below is + * sufficient. */ +#define DLSYM_OPTIONAL(dl, name) \ + ({ \ + (void) dlerror(); \ + typeof(sym_##name) _v = (typeof(sym_##name)) dlsym((dl), #name); \ + if (_v) sym_##name = _v; \ + }) + /* 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/process-util.c b/src/basic/process-util.c index 697498ea5d4b1..270c119ab72b3 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -2047,7 +2047,6 @@ int posix_spawn_wrapper( /* Initialization needs to succeed before we can set up a destructor. */ _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr; -#if HAVE_PIDFD_SPAWN static bool have_clone_into_cgroup = true; /* kernel 5.7+ */ _cleanup_close_ int cgroup_fd = -EBADF; @@ -2063,12 +2062,13 @@ int posix_spawn_wrapper( return -errno; r = posix_spawnattr_setcgroup_np(&attr, cgroup_fd); - if (r != 0) + if (r == 0) + flags |= POSIX_SPAWN_SETCGROUP; + else if (r != ENOSYS) return -r; - - flags |= POSIX_SPAWN_SETCGROUP; + /* If libc lacks posix_spawnattr_setcgroup_np we silently skip POSIX_SPAWN_SETCGROUP — the + * caller will then need to attach the child to the cgroup themselves. */ } -#endif r = posix_spawnattr_setflags(&attr, flags); if (r != 0) @@ -2077,7 +2077,6 @@ int posix_spawn_wrapper( if (r != 0) return -r; -#if HAVE_PIDFD_SPAWN _cleanup_close_ int pidfd = -EBADF; r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); @@ -2101,15 +2100,18 @@ int posix_spawn_wrapper( r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); } - if (r != 0) + if (r == 0) { + r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); + if (r < 0) + return r; + + return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); + } + if (!ERRNO_IS_NOT_SUPPORTED(r)) return -r; - r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); - if (r < 0) - return r; + /* pidfd_spawn unavailable (libc or kernel missing) — fall back to plain posix_spawn. */ - return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); -#else pid_t pid; r = posix_spawn(&pid, path, NULL, &attr, argv, envp); @@ -2121,7 +2123,6 @@ int posix_spawn_wrapper( return r; return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */ -#endif } int proc_dir_open(DIR **ret) { diff --git a/src/coredump/coredump-config.c b/src/coredump/coredump-config.c index cb7bb1adcc485..bcf38a03c58f5 100644 --- a/src/coredump/coredump-config.c +++ b/src/coredump/coredump-config.c @@ -2,6 +2,7 @@ #include "conf-parser.h" #include "coredump-config.h" +#include "elf-util.h" #include "format-util.h" #include "journal-def.h" #include "log.h" @@ -45,12 +46,10 @@ int coredump_parse_config(CoredumpConfig *config) { config->journal_size_max = JOURNAL_SIZE_MAX; } -#if !HAVE_DWFL_SET_SYSROOT - if (config->enter_namespace) { + if (config->enter_namespace && !dlopen_dw_has_dwfl_set_sysroot()) { log_warning("EnterNamespace= is enabled but libdw does not support dwfl_set_sysroot(), disabling."); config->enter_namespace = false; } -#endif log_debug("Selected storage '%s'.", coredump_storage_to_string(config->storage)); log_debug("Selected compression %s.", yes_no(config->compress)); diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 42d92a32e9c6d..f0c961ded7e5f 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -468,7 +468,9 @@ static int maybe_remove_external_coredump( } static int acquire_pid_mount_tree_fd(const CoredumpConfig *config, CoredumpContext *context) { -#if HAVE_DWFL_SET_SYSROOT + if (!dlopen_dw_has_dwfl_set_sysroot()) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "dwfl_set_sysroot() is not supported."); + _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, fd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; @@ -533,10 +535,6 @@ static int acquire_pid_mount_tree_fd(const CoredumpConfig *config, CoredumpConte context->mount_tree_fd = TAKE_FD(fd); return 0; -#else - /* Don't bother preparing environment if we can't pass it to libdwfl. */ - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "dwfl_set_sysroot() is not supported."); -#endif } static int attach_mount_tree(const CoredumpConfig *config, CoredumpContext *context) { diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index bf313340bf6c2..fffecd35738cb 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -608,7 +608,6 @@ static int parse_one_option(const char *option) { log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); } else if ((val = startswith(option, "link-volume-key="))) { -#if HAVE_CRYPT_SET_KEYRING_TO_LINK _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL; const char *sep; @@ -657,9 +656,6 @@ static int parse_one_option(const char *option) { free_and_replace(arg_link_keyring, keyring); free_and_replace(arg_link_key_type, key_type); free_and_replace(arg_link_key_description, key_description); -#else - log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option); -#endif } else if ((val = startswith(option, "fixate-volume-key="))) { r = free_and_strdup(&arg_fixate_volume_key, val); if (r < 0) @@ -2688,14 +2684,14 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) if (r < 0) 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 + /* since cryptsetup 2.7.0 (Jan 2024) */ if (arg_link_key_description) { r = sym_crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); - if (r < 0) + if (r == -ENOSYS) + log_warning("Loaded libcryptsetup does not support linking volume keys in user specified kernel keyrings upon device activation, ignoring."); + else 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 = sym_crypt_set_data_device(cd, source); diff --git a/src/include/musl/stdio.h b/src/include/musl/stdio.h index 2ff95e952a4c2..54ad9ed110432 100644 --- a/src/include/musl/stdio.h +++ b/src/include/musl/stdio.h @@ -3,15 +3,15 @@ #include_next -#if !HAVE_RENAMEAT2 +#ifndef RENAME_NOREPLACE # define RENAME_NOREPLACE (1 << 0) # define RENAME_EXCHANGE (1 << 1) # define RENAME_WHITEOUT (1 << 2) - -int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags); -# define renameat2 missing_renameat2 #endif +int renameat2_shim(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags); +#define renameat2 renameat2_shim + /* When a stream is opened read-only under glibc, fputs() and friends fail with EBADF. However, they * succeed under musl. We rely on the glibc behavior in the code base. The following _check_writable() * functions first check if the passed stream is writable, and refuse to write with EBADF if not. */ diff --git a/src/include/override/fcntl.h b/src/include/override/fcntl.h index a6f2afe53b810..875f112b009d1 100644 --- a/src/include/override/fcntl.h +++ b/src/include/override/fcntl.h @@ -27,7 +27,5 @@ /* Defined since glibc-2.42. * Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */ -#if !HAVE_OPENAT2 -int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize); -# define openat2 missing_openat2 -#endif +int openat2_shim(int dfd, const char *filename, const struct open_how *how, size_t usize); +#define openat2 openat2_shim diff --git a/src/include/override/sched.h b/src/include/override/sched.h index 08f4576eca1e3..3eed39311868e 100644 --- a/src/include/override/sched.h +++ b/src/include/override/sched.h @@ -51,10 +51,8 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, /* Defined since glibc-2.41. * Supported since kernel 3.14 (e6cfc0295c7d51b008999a8b13a44fb43f8685ea). */ -#if !HAVE_SCHED_SETATTR -int missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned flags); -# define sched_setattr missing_sched_setattr -#endif +int sched_setattr_shim(pid_t pid, struct sched_attr *attr, unsigned flags); +#define sched_setattr sched_setattr_shim /* f0e1a0643a59bf1f922fa209cec86a170b784f3f (6.12), * defined in sched.h in glibc since glibc-2.41. */ diff --git a/src/include/override/signal.h b/src/include/override/signal.h index 436b49a519993..6e596adaccef2 100644 --- a/src/include/override/signal.h +++ b/src/include/override/signal.h @@ -3,7 +3,5 @@ #include_next /* IWYU pragma: export */ -#if !HAVE_RT_TGSIGQUEUEINFO -int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info); -# define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo -#endif +int rt_tgsigqueueinfo_shim(pid_t tgid, pid_t tid, int sig, siginfo_t *info); +#define rt_tgsigqueueinfo rt_tgsigqueueinfo_shim diff --git a/src/include/override/spawn.h b/src/include/override/spawn.h new file mode 100644 index 0000000000000..463825856c511 --- /dev/null +++ b/src/include/override/spawn.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +/* pidfd_spawn() and posix_spawnattr_setcgroup_np() were added in glibc 2.39. Redirect to shims that + * return ENOSYS at runtime when the libc symbols aren't available, so callers don't need to worry + * about the libc version. */ +int pidfd_spawn_shim(pid_t *restrict pidfd, const char *restrict path, + const posix_spawn_file_actions_t *restrict file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], char *const envp[restrict]); +#define pidfd_spawn pidfd_spawn_shim + +int posix_spawnattr_setcgroup_np_shim(posix_spawnattr_t *attr, int cgroup); +#define posix_spawnattr_setcgroup_np posix_spawnattr_setcgroup_np_shim + +/* Defined in since glibc 2.39. */ +#ifndef POSIX_SPAWN_SETCGROUP +# define POSIX_SPAWN_SETCGROUP 0x100 +#endif diff --git a/src/include/override/sys/bpf.h b/src/include/override/sys/bpf.h index 56e1466b89006..eaeb89a5a8fb6 100644 --- a/src/include/override/sys/bpf.h +++ b/src/include/override/sys/bpf.h @@ -5,7 +5,5 @@ #include /* Supported since kernel v3.18 (749730ce42a2121e1c88350d69478bff3994b10a). */ -#if !HAVE_BPF -int missing_bpf(int cmd, union bpf_attr *attr, size_t size); -# define bpf missing_bpf -#endif +int bpf_shim(int cmd, union bpf_attr *attr, size_t size); +#define bpf bpf_shim diff --git a/src/include/override/sys/epoll.h b/src/include/override/sys/epoll.h new file mode 100644 index 0000000000000..0ed9c27a4b2e7 --- /dev/null +++ b/src/include/override/sys/epoll.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +/* epoll_pwait2() was added to glibc 2.35. Redirect to a shim that sets errno=ENOSYS at runtime when + * the libc symbol isn't available, so callers don't need to worry about the libc version. */ +int epoll_pwait2_shim(int fd, struct epoll_event *events, int maxevents, + const struct timespec *timeout, const sigset_t *sigmask); +#define epoll_pwait2 epoll_pwait2_shim diff --git a/src/include/override/sys/ioprio.h b/src/include/override/sys/ioprio.h index c361eba6e6071..8ea4c8f8b2a86 100644 --- a/src/include/override/sys/ioprio.h +++ b/src/include/override/sys/ioprio.h @@ -3,12 +3,8 @@ #include /* IWYU pragma: export */ -#if !HAVE_IOPRIO_GET -int missing_ioprio_get(int which, int who); -# define ioprio_get missing_ioprio_get -#endif +int ioprio_get_shim(int which, int who); +#define ioprio_get ioprio_get_shim -#if !HAVE_IOPRIO_SET -int missing_ioprio_set(int which, int who, int ioprio); -# define ioprio_set missing_ioprio_set -#endif +int ioprio_set_shim(int which, int who, int ioprio); +#define ioprio_set ioprio_set_shim diff --git a/src/include/override/sys/kcmp.h b/src/include/override/sys/kcmp.h index 4e47825ac5b41..f0df5d402017b 100644 --- a/src/include/override/sys/kcmp.h +++ b/src/include/override/sys/kcmp.h @@ -5,7 +5,5 @@ #include /* Supported since kernel v3.5 (d97b46a64674a267bc41c9e16132ee2a98c3347d). */ -#if !HAVE_KCMP -int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2); -# define kcmp missing_kcmp -#endif +int kcmp_shim(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2); +#define kcmp kcmp_shim diff --git a/src/include/override/sys/kexec.h b/src/include/override/sys/kexec.h index 4e256bdb6ad38..be31c93520ef0 100644 --- a/src/include/override/sys/kexec.h +++ b/src/include/override/sys/kexec.h @@ -6,11 +6,9 @@ /* 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 +#ifdef __NR_kexec_file_load +int kexec_file_load_shim(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags); +# define kexec_file_load kexec_file_load_shim # define HAVE_KEXEC_FILE_LOAD_SYSCALL 1 #else # define HAVE_KEXEC_FILE_LOAD_SYSCALL 0 diff --git a/src/include/override/sys/keyctl.h b/src/include/override/sys/keyctl.h index 88c9c40ea78be..65be2fea2deee 100644 --- a/src/include/override/sys/keyctl.h +++ b/src/include/override/sys/keyctl.h @@ -4,17 +4,11 @@ #include /* IWYU pragma: export */ #include -#if !HAVE_KEYCTL -long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); -# define keyctl missing_keyctl -#endif +long keyctl_shim(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); +#define keyctl keyctl_shim -#if !HAVE_ADD_KEY -key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid); -# define add_key missing_add_key -#endif +key_serial_t add_key_shim(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid); +#define add_key add_key_shim -#if !HAVE_REQUEST_KEY -key_serial_t missing_request_key(const char *type, const char *description, const char *callout_info, key_serial_t destringid); -# define request_key missing_request_key -#endif +key_serial_t request_key_shim(const char *type, const char *description, const char *callout_info, key_serial_t destringid); +#define request_key request_key_shim diff --git a/src/include/override/sys/mempolicy.h b/src/include/override/sys/mempolicy.h index 9858f2812bef7..1ba6d95958bbe 100644 --- a/src/include/override/sys/mempolicy.h +++ b/src/include/override/sys/mempolicy.h @@ -3,12 +3,8 @@ #include /* IWYU pragma: export */ -#if !HAVE_SET_MEMPOLICY -int missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode); -# define set_mempolicy missing_set_mempolicy -#endif +int set_mempolicy_shim(int mode, const unsigned long *nodemask, unsigned long maxnode); +#define set_mempolicy set_mempolicy_shim -#if !HAVE_GET_MEMPOLICY -int missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags); -# define get_mempolicy missing_get_mempolicy -#endif +int get_mempolicy_shim(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags); +#define get_mempolicy get_mempolicy_shim diff --git a/src/include/override/sys/mount.h b/src/include/override/sys/mount.h index e6d7ad1b67c74..2fa5c902e5bcf 100644 --- a/src/include/override/sys/mount.h +++ b/src/include/override/sys/mount.h @@ -39,56 +39,36 @@ extern int umount2(const char *__special_file, int __flags); /* Open the filesystem referenced by FS_NAME so it can be configured for mounting. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (24dcb3d90a1f67fe08c68a004af37df059d74005). */ -#if HAVE_FSOPEN -extern int fsopen(const char *__fs_name, unsigned int __flags); -#else -int missing_fsopen(const char *fsname, unsigned flags); -# define fsopen missing_fsopen -#endif +int fsopen_shim(const char *fsname, unsigned flags); +#define fsopen fsopen_shim /* Create a mount representation for the FD created by fsopen using FLAGS with ATTR_FLAGS describing how the mount is to be performed. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (93766fbd2696c2c4453dd8e1070977e9cd4e6b6d). */ -#if HAVE_FSMOUNT -extern int fsmount(int __fd, unsigned int __flags, unsigned int __ms_flags); -#else -int missing_fsmount(int fd, unsigned flags, unsigned ms_flags); -# define fsmount missing_fsmount -#endif +int fsmount_shim(int fd, unsigned flags, unsigned ms_flags); +#define fsmount fsmount_shim /* Add the mounted FROM_DFD referenced by FROM_PATHNAME filesystem returned by fsmount in the hierarchy in the place TO_DFD reference by TO_PATHNAME using FLAGS. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (2db154b3ea8e14b04fee23e3fdfd5e9d17fbc6ae). */ -#if HAVE_MOVE_MOUNT -extern int move_mount(int __from_dfd, const char *__from_pathname, int __to_dfd, const char *__to_pathname, unsigned int flags); -#else -int missing_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags); -# define move_mount missing_move_mount -#endif +int move_mount_shim(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags); +#define move_mount move_mount_shim /* Set parameters and trigger CMD action on the FD context. KEY, VALUE, and AUX are used depending ng of the CMD. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (ecdab150fddb42fe6a739335257949220033b782). */ -#if HAVE_FSCONFIG -extern int fsconfig(int __fd, unsigned int __cmd, const char *__key, const void *__value, int __aux); -#else -int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux); -# define fsconfig missing_fsconfig -#endif +int fsconfig_shim(int fd, unsigned cmd, const char *key, const void *value, int aux); +#define fsconfig fsconfig_shim /* Open the mount point FILENAME in directory DFD using FLAGS. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (a07b20004793d8926f78d63eb5980559f7813404). */ -#if HAVE_OPEN_TREE -extern int open_tree(int __dfd, const char *__filename, unsigned int __flags); -#else -int missing_open_tree(int dfd, const char *filename, unsigned flags); -# define open_tree missing_open_tree -#endif +int open_tree_shim(int dfd, const char *filename, unsigned flags); +#define open_tree open_tree_shim /* Change the mount properties of the mount or an entire mount tree. If PATH is a relative pathname, then it is interpreted relative to the @@ -97,18 +77,10 @@ int missing_open_tree(int dfd, const char *filename, unsigned flags); working directory of the calling process. */ /* Defined since glibc-2.36. * Supported since kernel v5.12 (2a1867219c7b27f928e2545782b86daaf9ad50bd). */ -#if HAVE_MOUNT_SETATTR -extern int mount_setattr(int __dfd, const char *__path, unsigned int __flags, struct mount_attr *__uattr, size_t __usize); -#else -int missing_mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size); -# define mount_setattr missing_mount_setattr -#endif +int mount_setattr_shim(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size); +#define mount_setattr mount_setattr_shim /* Not defined in glibc yet as of glibc-2.41. * Supported since kernel v6.15 (c4a16820d90199409c9bf01c4f794e1e9e8d8fd8). */ -#if HAVE_OPEN_TREE_ATTR -extern int open_tree_attr(int __dfd, const char *__filename, unsigned int __flags, struct mount_attr *__uattr, size_t __usize); -#else -int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size); -# define open_tree_attr missing_open_tree_attr -#endif +int open_tree_attr_shim(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size); +#define open_tree_attr open_tree_attr_shim diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index 7f136eb62eeb7..0e9b2f39989d7 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -/* since glibc-2.36 */ -#if HAVE_PIDFD_OPEN +#include + +/* since glibc-2.36. Only descend into the next on glibc — musl ships no such header, + * and musl-gcc's -idirafter /usr/include would otherwise pull in glibc's copy (which depends on + * __THROW and other glibc-isms) from the fallback path. */ +#if defined(__GLIBC__) && __has_include_next() #include_next /* IWYU pragma: export */ #endif @@ -12,17 +16,13 @@ /* Defined since glibc-2.36. * Supported since kernel v5.3 (7615d9e1780e26e0178c93c55b73309a5dc093d7). */ -#if !HAVE_PIDFD_OPEN -int missing_pidfd_open(pid_t pid, unsigned flags); -# define pidfd_open missing_pidfd_open -#endif +int pidfd_open_shim(pid_t pid, unsigned flags); +#define pidfd_open pidfd_open_shim /* Defined since glibc-2.36. * Supported since kernel v5.1 (3eb39f47934f9d5a3027fe00d906a45fe3a15fad). */ -#if !HAVE_PIDFD_SEND_SIGNAL -int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags); -# define pidfd_send_signal missing_pidfd_send_signal -#endif +int pidfd_send_signal_shim(int fd, int sig, siginfo_t *info, unsigned flags); +#define pidfd_send_signal pidfd_send_signal_shim /* since glibc-2.41 */ #ifndef PIDFS_IOCTL_MAGIC diff --git a/src/include/override/sys/quota.h b/src/include/override/sys/quota.h index fcf6be988bb24..95aa3674460e1 100644 --- a/src/include/override/sys/quota.h +++ b/src/include/override/sys/quota.h @@ -4,7 +4,5 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v5.14 (64c2c2c62f92339b176ea24403d8db16db36f9e6). */ -#if !HAVE_QUOTACTL_FD -int missing_quotactl_fd(int fd, int cmd, int id, void *addr); -# define quotactl_fd missing_quotactl_fd -#endif +int quotactl_fd_shim(int fd, int cmd, int id, void *addr); +#define quotactl_fd quotactl_fd_shim diff --git a/src/include/override/sys/stat.h b/src/include/override/sys/stat.h index 5cef9f852ee0b..bfd9b54e7f90e 100644 --- a/src/include/override/sys/stat.h +++ b/src/include/override/sys/stat.h @@ -4,7 +4,5 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v6.6 (78252deb023cf0879256fcfbafe37022c390762b). */ -#if !HAVE_FCHMODAT2 -int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags); -# define fchmodat2 missing_fchmodat2 -#endif +int fchmodat2_shim(int dirfd, const char *path, mode_t mode, int flags); +#define fchmodat2 fchmodat2_shim diff --git a/src/include/override/sys/xattr.h b/src/include/override/sys/xattr.h index db31d0ba6f3e4..8823548d89d03 100644 --- a/src/include/override/sys/xattr.h +++ b/src/include/override/sys/xattr.h @@ -8,13 +8,9 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394). */ -#if !HAVE_SETXATTRAT -int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size); -# define setxattrat missing_setxattrat -#endif +int setxattrat_shim(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size); +#define setxattrat setxattrat_shim /* Supported since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394). */ -#if !HAVE_REMOVEXATTRAT -int missing_removexattrat(int fd, const char *path, int at_flags, const char *name); -# define removexattrat missing_removexattrat -#endif +int removexattrat_shim(int fd, const char *path, int at_flags, const char *name); +#define removexattrat removexattrat_shim diff --git a/src/include/override/unistd.h b/src/include/override/unistd.h index bd558694eebdd..7dd56aa06f6a1 100644 --- a/src/include/override/unistd.h +++ b/src/include/override/unistd.h @@ -3,7 +3,5 @@ #include_next /* IWYU pragma: export */ -#if !HAVE_PIVOT_ROOT -int missing_pivot_root(const char *new_root, const char *put_old); -# define pivot_root missing_pivot_root -#endif +int pivot_root_shim(const char *new_root, const char *put_old); +#define pivot_root pivot_root_shim diff --git a/src/libc/bpf.c b/src/libc/bpf.c index 4a7498e502aa7..d87dcb1b0c1cf 100644 --- a/src/libc/bpf.c +++ b/src/libc/bpf.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_BPF -int missing_bpf(int cmd, union bpf_attr *attr, size_t size) { - return syscall(__NR_bpf, cmd, attr, size); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(bpf, int, + int, cmd, + union bpf_attr *, attr, + size_t, size) diff --git a/src/libc/epoll.c b/src/libc/epoll.c new file mode 100644 index 0000000000000..0845cf38a875a --- /dev/null +++ b/src/libc/epoll.c @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +DEFINE_LIBC_ERRNO_SHIM(epoll_pwait2, int, + int, fd, + struct epoll_event *, events, + int, maxevents, + const struct timespec *, timeout, + const sigset_t *, sigmask) diff --git a/src/libc/fcntl.c b/src/libc/fcntl.c index ca5e6c7977c04..8fd78ca897e5e 100644 --- a/src/libc/fcntl.c +++ b/src/libc/fcntl.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_OPENAT2 -int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize) { - return syscall(__NR_openat2, dfd, filename, how, usize); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(openat2, int, + int, dfd, + const char *, filename, + const struct open_how *, how, + size_t, usize) diff --git a/src/libc/ioprio.c b/src/libc/ioprio.c index 5bb640457dc1f..053693139c3b5 100644 --- a/src/libc/ioprio.c +++ b/src/libc/ioprio.c @@ -1,17 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_IOPRIO_GET -int missing_ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} -#endif +#include "libc-shim.h" -#if !HAVE_IOPRIO_SET -int missing_ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} -#endif +DEFINE_SYSCALL_SHIM(ioprio_get, int, + int, which, + int, who) + +DEFINE_SYSCALL_SHIM(ioprio_set, int, + int, which, + int, who, + int, ioprio) diff --git a/src/libc/kcmp.c b/src/libc/kcmp.c index aafff999ef44a..0f73f8500dd62 100644 --- a/src/libc/kcmp.c +++ b/src/libc/kcmp.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_KCMP -int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { - return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(kcmp, int, + pid_t, pid1, + pid_t, pid2, + int, type, + unsigned long, idx1, + unsigned long, idx2) diff --git a/src/libc/kexec.c b/src/libc/kexec.c index 122acff956c07..051253d17bde8 100644 --- a/src/libc/kexec.c +++ b/src/libc/kexec.c @@ -1,11 +1,14 @@ /* 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); -} +#include "libc-shim.h" + +#ifdef __NR_kexec_file_load +DEFINE_SYSCALL_SHIM(kexec_file_load, int, + int, kernel_fd, + int, initrd_fd, + unsigned long, cmdline_len, + const char *, cmdline, + unsigned long, flags) #endif diff --git a/src/libc/keyctl.c b/src/libc/keyctl.c index af4bca87f513e..c86518454835b 100644 --- a/src/libc/keyctl.c +++ b/src/libc/keyctl.c @@ -1,23 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_KEYCTL -long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { - return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); -} -#endif +#include "libc-shim.h" -#if !HAVE_ADD_KEY -key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { - return syscall(__NR_add_key, type, description, payload, plen, ringid); -} -#endif +DEFINE_SYSCALL_SHIM(keyctl, long, + int, cmd, + unsigned long, arg2, + unsigned long, arg3, + unsigned long, arg4, + unsigned long, arg5) -#if !HAVE_REQUEST_KEY -key_serial_t missing_request_key(const char *type, const char *description, const char *callout_info, key_serial_t destringid) { - return syscall(__NR_request_key, type, description, callout_info, destringid); -} -#endif +DEFINE_SYSCALL_SHIM(add_key, key_serial_t, + const char *, type, + const char *, description, + const void *, payload, + size_t, plen, + key_serial_t, ringid) + +DEFINE_SYSCALL_SHIM(request_key, key_serial_t, + const char *, type, + const char *, description, + const char *, callout_info, + key_serial_t, destringid) diff --git a/src/libc/libc-shim.h b/src/libc/libc-shim.h new file mode 100644 index 0000000000000..c5cf9c8acdab5 --- /dev/null +++ b/src/libc/libc-shim.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include + +/* Each parameter is passed as a flat (type, name) pair: type1, name1, type2, name2, ... The + * _SHIM_DECL/_SHIM_NAME macros consume two args at a time and emit either "type name" pairs (for + * the function declarator) or just the names (for forwarding). _SHIM_PAIRS counts the number of + * pairs by indexing into a table that increments every two positions. */ +#define _SHIM_CAT(a, b) _SHIM_CAT_(a, b) +#define _SHIM_CAT_(a, b) a##b + +#define _SHIM_NTH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N +#define _SHIM_PAIRS(...) _SHIM_NTH(__VA_ARGS__, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0) + +#define _SHIM_DECL_1(t, n) t n +#define _SHIM_DECL_2(t, n, ...) t n, _SHIM_DECL_1(__VA_ARGS__) +#define _SHIM_DECL_3(t, n, ...) t n, _SHIM_DECL_2(__VA_ARGS__) +#define _SHIM_DECL_4(t, n, ...) t n, _SHIM_DECL_3(__VA_ARGS__) +#define _SHIM_DECL_5(t, n, ...) t n, _SHIM_DECL_4(__VA_ARGS__) +#define _SHIM_DECL_6(t, n, ...) t n, _SHIM_DECL_5(__VA_ARGS__) +#define _SHIM_DECL_7(t, n, ...) t n, _SHIM_DECL_6(__VA_ARGS__) +#define _SHIM_DECL_8(t, n, ...) t n, _SHIM_DECL_7(__VA_ARGS__) +#define _SHIM_DECL(...) _SHIM_CAT(_SHIM_DECL_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) + +#define _SHIM_NAME_1(t, n) n +#define _SHIM_NAME_2(t, n, ...) n, _SHIM_NAME_1(__VA_ARGS__) +#define _SHIM_NAME_3(t, n, ...) n, _SHIM_NAME_2(__VA_ARGS__) +#define _SHIM_NAME_4(t, n, ...) n, _SHIM_NAME_3(__VA_ARGS__) +#define _SHIM_NAME_5(t, n, ...) n, _SHIM_NAME_4(__VA_ARGS__) +#define _SHIM_NAME_6(t, n, ...) n, _SHIM_NAME_5(__VA_ARGS__) +#define _SHIM_NAME_7(t, n, ...) n, _SHIM_NAME_6(__VA_ARGS__) +#define _SHIM_NAME_8(t, n, ...) n, _SHIM_NAME_7(__VA_ARGS__) +#define _SHIM_NAME(...) _SHIM_CAT(_SHIM_NAME_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) + +/* Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the + * corresponding direct syscall otherwise. The libc symbol is redeclared as a weak reference so the + * binary still loads on libc versions that don't provide it. Each parameter is passed as type, + * name pairs (flat). + * + * The weak reference is bound to the libc symbol via an __asm__("label") rename so that the bare + * libc identifier never appears as a C token. This matters because override/musl headers + * sometimes #define the libc name to redirect it to the _shim variant — without the rename the + * caller would have to #undef each name before invoking the macro. # and ## operators don't + * macro-expand their operands, so the parameter "func" stays a literal token everywhere. */ +#define DEFINE_SYSCALL_SHIM(func, ret, ...) \ + extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_libc_weak) \ + return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + return syscall(__NR_##func, _SHIM_NAME(__VA_ARGS__)); \ + } + +/* Like DEFINE_SYSCALL_SHIM but for libc helpers that have no corresponding syscall and report errors + * by returning the positive errno value directly (posix_spawn-family convention). If the libc symbol + * is missing at runtime, ENOSYS is returned. */ +#define DEFINE_LIBC_SHIM(func, ret, ...) \ + extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_libc_weak) \ + return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + return ENOSYS; \ + } + +/* Like DEFINE_LIBC_SHIM but for libc helpers that report errors via errno + -1 return value. If the + * libc symbol is missing at runtime, errno is set to ENOSYS and -1 is returned. */ +#define DEFINE_LIBC_ERRNO_SHIM(func, ret, ...) \ + extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_libc_weak) \ + return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + errno = ENOSYS; \ + return -1; \ + } diff --git a/src/libc/mempolicy.c b/src/libc/mempolicy.c index 77b0e1ac010f9..686aba2b59024 100644 --- a/src/libc/mempolicy.c +++ b/src/libc/mempolicy.c @@ -1,17 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_SET_MEMPOLICY -int missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode) { - return syscall(__NR_set_mempolicy, mode, nodemask, maxnode); -} -#endif +#include "libc-shim.h" -#if !HAVE_GET_MEMPOLICY -int missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags) { - return syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags); -} -#endif +DEFINE_SYSCALL_SHIM(set_mempolicy, int, + int, mode, + const unsigned long *, nodemask, + unsigned long, maxnode) + +DEFINE_SYSCALL_SHIM(get_mempolicy, int, + int *, mode, + unsigned long *, nodemask, + unsigned long, maxnode, + void *, addr, + unsigned long, flags) diff --git a/src/libc/meson.build b/src/libc/meson.build index e100a6f4ea06d..9b86168536e43 100644 --- a/src/libc/meson.build +++ b/src/libc/meson.build @@ -2,6 +2,7 @@ libc_wrapper_sources = files( 'bpf.c', + 'epoll.c', 'fcntl.c', 'ioprio.c', 'kcmp.c', @@ -13,6 +14,7 @@ libc_wrapper_sources = files( 'quota.c', 'sched.c', 'signal.c', + 'spawn.c', 'stat.c', 'unistd.c', 'xattr.c', diff --git a/src/libc/mount.c b/src/libc/mount.c index 1d324ef386614..3e626e87acd2b 100644 --- a/src/libc/mount.c +++ b/src/libc/mount.c @@ -1,47 +1,47 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include - -#if !HAVE_FSOPEN -int missing_fsopen(const char *fsname, unsigned flags) { - return syscall(__NR_fsopen, fsname, flags); -} -#endif - -#if !HAVE_FSMOUNT -int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) { - return syscall(__NR_fsmount, fd, flags, ms_flags); -} -#endif - -#if !HAVE_MOVE_MOUNT -int missing_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags) { - return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags); -} -#endif - -#if !HAVE_FSCONFIG -int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) { - return syscall(__NR_fsconfig, fd, cmd, key, value, aux); -} -#endif - -#if !HAVE_OPEN_TREE -int missing_open_tree(int dfd, const char *filename, unsigned flags) { - return syscall(__NR_open_tree, dfd, filename, flags); -} -#endif - -#if !HAVE_MOUNT_SETATTR -int missing_mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size) { - return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); -} -#endif - -#if !HAVE_OPEN_TREE_ATTR -int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size) { - return syscall(__NR_open_tree_attr, dfd, filename, flags, attr, size); -} -#endif + +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(fsopen, int, + const char *, fsname, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(fsmount, int, + int, fd, + unsigned, flags, + unsigned, ms_flags) + +DEFINE_SYSCALL_SHIM(move_mount, int, + int, from_dfd, + const char *, from_pathname, + int, to_dfd, + const char *, to_pathname, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(fsconfig, int, + int, fd, + unsigned, cmd, + const char *, key, + const void *, value, + int, aux) + +DEFINE_SYSCALL_SHIM(open_tree, int, + int, dfd, + const char *, filename, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(mount_setattr, int, + int, dfd, + const char *, path, + unsigned, flags, + struct mount_attr *, attr, + size_t, size) + +DEFINE_SYSCALL_SHIM(open_tree_attr, int, + int, dfd, + const char *, filename, + unsigned int, flags, + struct mount_attr *, attr, + size_t, size) diff --git a/src/libc/musl/stdio.c b/src/libc/musl/stdio.c index 6d02aef5f65da..f4e8d9e4c18e7 100644 --- a/src/libc/musl/stdio.c +++ b/src/libc/musl/stdio.c @@ -3,14 +3,15 @@ #include #include #include -#include -#include -#if !HAVE_RENAMEAT2 -int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags) { - return syscall(__NR_renameat2, __oldfd, __old, __newfd, __new, __flags); -} -#endif +#include "../libc-shim.h" + +DEFINE_SYSCALL_SHIM(renameat2, int, + int, __oldfd, + const char *, __old, + int, __newfd, + const char *, __new, + unsigned, __flags) #define DEFINE_PUT(func) \ int func##_check_writable(int c, FILE *stream) { \ diff --git a/src/libc/pidfd.c b/src/libc/pidfd.c index a779e3459c674..8343cf7ff9fa9 100644 --- a/src/libc/pidfd.c +++ b/src/libc/pidfd.c @@ -1,17 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_PIDFD_OPEN -int missing_pidfd_open(pid_t pid, unsigned flags) { - return syscall(__NR_pidfd_open, pid, flags); -} -#endif +#include "libc-shim.h" -#if !HAVE_PIDFD_SEND_SIGNAL -int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) { - return syscall(__NR_pidfd_send_signal, fd, sig, info, flags); -} -#endif +DEFINE_SYSCALL_SHIM(pidfd_open, int, + pid_t, pid, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(pidfd_send_signal, int, + int, fd, + int, sig, + siginfo_t *, info, + unsigned, flags) diff --git a/src/libc/quota.c b/src/libc/quota.c index 19695df9b3c32..08a7e5b8ae2a1 100644 --- a/src/libc/quota.c +++ b/src/libc/quota.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_QUOTACTL_FD -int missing_quotactl_fd(int fd, int cmd, int id, void *addr) { - return syscall(__NR_quotactl_fd, fd, cmd, id, addr); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(quotactl_fd, int, + int, fd, + int, cmd, + int, id, + void *, addr) diff --git a/src/libc/sched.c b/src/libc/sched.c index cf1752651b39b..24de9731beb61 100644 --- a/src/libc/sched.c +++ b/src/libc/sched.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_SCHED_SETATTR -int missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned flags) { - return syscall(__NR_sched_setattr, pid, attr, flags); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(sched_setattr, int, + pid_t, pid, + struct sched_attr *, attr, + unsigned, flags) diff --git a/src/libc/signal.c b/src/libc/signal.c index 1908331b1a4f2..9370491821f82 100644 --- a/src/libc/signal.c +++ b/src/libc/signal.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_RT_TGSIGQUEUEINFO -int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) { - return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(rt_tgsigqueueinfo, int, + pid_t, tgid, + pid_t, tid, + int, sig, + siginfo_t *, info) diff --git a/src/libc/spawn.c b/src/libc/spawn.c new file mode 100644 index 0000000000000..6e2f9cf0429e2 --- /dev/null +++ b/src/libc/spawn.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +DEFINE_LIBC_SHIM(pidfd_spawn, int, + pid_t *restrict, pidfd, + const char *restrict, path, + const posix_spawn_file_actions_t *restrict, file_actions, + const posix_spawnattr_t *restrict, attrp, + char *const *restrict, argv, + char *const *restrict, envp) + +DEFINE_LIBC_SHIM(posix_spawnattr_setcgroup_np, int, + posix_spawnattr_t *, attr, + int, cgroup) diff --git a/src/libc/stat.c b/src/libc/stat.c index b283005f40b55..ffc00f13c647d 100644 --- a/src/libc/stat.c +++ b/src/libc/stat.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_FCHMODAT2 -int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { - return syscall(__NR_fchmodat2, dirfd, path, mode, flags); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(fchmodat2, int, + int, dirfd, + const char *, path, + mode_t, mode, + int, flags) diff --git a/src/libc/unistd.c b/src/libc/unistd.c index 81728631c6b43..22fb704907e13 100644 --- a/src/libc/unistd.c +++ b/src/libc/unistd.c @@ -1,10 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include +#include "libc-shim.h" -#if !HAVE_PIVOT_ROOT -int missing_pivot_root(const char *new_root, const char *put_old) { - return syscall(__NR_pivot_root, new_root, put_old); -} -#endif +DEFINE_SYSCALL_SHIM(pivot_root, int, + const char *, new_root, + const char *, put_old) diff --git a/src/libc/xattr.c b/src/libc/xattr.c index 68fa97ab281d0..668faa766c9da 100644 --- a/src/libc/xattr.c +++ b/src/libc/xattr.c @@ -1,17 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include -#include -#if !HAVE_SETXATTRAT -int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size) { - return syscall(__NR_setxattrat, fd, path, at_flags, name, args, size); -} -#endif +#include "libc-shim.h" -#if !HAVE_REMOVEXATTRAT -int missing_removexattrat(int fd, const char *path, int at_flags, const char *name) { - return syscall(__NR_removexattrat, fd, path, at_flags, name); -} -#endif +DEFINE_SYSCALL_SHIM(setxattrat, int, + int, fd, + const char *, path, + int, at_flags, + const char *, name, + const struct xattr_args *, args, + size_t, size) + +DEFINE_SYSCALL_SHIM(removexattrat, int, + int, fd, + const char *, path, + int, at_flags, + const char *, name) diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 9e7ba7813cde9..8c419fad306ea 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -4629,19 +4629,9 @@ static int epoll_wait_usec( int maxevents, usec_t timeout) { - int msec; - /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */ - -#if HAVE_EPOLL_PWAIT2 static bool epoll_pwait2_absent = false; - int r; - - /* epoll_pwait2() was added to Linux 5.11 (2021-02-14) and to glibc in 2.35 (2022-02-03). In contrast - * to other syscalls we don't bother with our own fallback syscall wrappers on old libcs, since this - * is not that obvious to implement given the libc and kernel definitions differ in the last - * argument. Moreover, the only reason to use it is the more accurate timeouts (which is not a - * biggie), let's hence rely on glibc's definitions, and fallback to epoll_pwait() when that's - * missing. */ + int r, msec; + /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */ if (!epoll_pwait2_absent && timeout != USEC_INFINITY) { r = epoll_pwait2(fd, @@ -4657,7 +4647,6 @@ static int epoll_wait_usec( epoll_pwait2_absent = true; } -#endif if (timeout == USEC_INFINITY) msec = -1; diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 47a148459d95f..7aa3d5d9c5231 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -55,9 +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 +static int missing_crypt_set_keyring_to_link( + struct crypt_device *cd, + const char *key_description, + const char *old_key_description, + const char *key_type_desc, + const char *keyring_to_link_vk) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(crypt_set_keyring_to_link) = missing_crypt_set_keyring_to_link; DLSYM_PROTOTYPE(crypt_set_log_callback) = NULL; DLSYM_PROTOTYPE(crypt_set_metadata_size) = NULL; DLSYM_PROTOTYPE(crypt_set_pbkdf_type) = NULL; @@ -67,9 +73,10 @@ 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; -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH -DLSYM_PROTOTYPE(crypt_token_set_external_path) = NULL; -#endif +static int missing_crypt_token_set_external_path(const char *path) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(crypt_token_set_external_path) = missing_crypt_token_set_external_path; DLSYM_PROTOTYPE(crypt_token_status) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_get) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_keyring) = NULL; @@ -318,9 +325,6 @@ 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), @@ -330,9 +334,6 @@ int dlopen_cryptsetup(int log_level) { DLSYM_ARG(crypt_token_json_get), DLSYM_ARG(crypt_token_json_set), DLSYM_ARG(crypt_token_max), -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH - DLSYM_ARG(crypt_token_set_external_path), -#endif DLSYM_ARG(crypt_token_status), DLSYM_ARG(crypt_volume_key_get), DLSYM_ARG(crypt_volume_key_keyring), @@ -341,6 +342,12 @@ int dlopen_cryptsetup(int log_level) { if (r <= 0) return r; + /* Optional symbols: present in libcryptsetup 2.7+ only. If unresolved, the prototype keeps its + * static initializer pointing at a fallback that returns -ENOSYS, so call sites can invoke the + * symbol unconditionally. */ + DLSYM_OPTIONAL(cryptsetup_dl, crypt_set_keyring_to_link); + DLSYM_OPTIONAL(cryptsetup_dl, crypt_token_set_external_path); + /* Redirect the default logging calls of libcryptsetup to our own logging infra. (Note that * libcryptsetup also maintains per-"struct crypt_device" log functions, which we'll also set * whenever allocating a "struct crypt_device" context. Why set both? To be defensive: maybe some @@ -350,13 +357,11 @@ int dlopen_cryptsetup(int log_level) { const char *e = secure_getenv("SYSTEMD_CRYPTSETUP_TOKEN_PATH"); if (e) { -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH r = sym_crypt_token_set_external_path(e); - if (r < 0) + if (r == -ENOSYS) + log_debug("Loaded libcryptsetup does not support setting the external token path, not setting it to '%s'.", e); + else if (r < 0) log_debug_errno(r, "Failed to set the libcryptsetup external token path to '%s', ignoring: %m", e); -#else - log_debug("libcryptsetup version does not support setting the external token path, not setting it to '%s'.", e); -#endif } return 1; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 4e994ce9f9951..5c83aad8ee211 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -7,6 +7,19 @@ #if HAVE_LIBCRYPTSETUP #include /* IWYU pragma: export */ +/* Available since libcryptsetup 2.7. Always redeclare so DLSYM_PROTOTYPE's typeof() resolves on older + * headers; suppress the warning when newer libcryptsetup already declares them. */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +extern int crypt_set_keyring_to_link(struct crypt_device *cd, + const char *key_description, + const char *old_key_description, + const char *key_type_desc, + const char *keyring_to_link_vk); +extern int crypt_token_set_external_path(const char *path); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + extern DLSYM_PROTOTYPE(crypt_activate_by_passphrase); extern DLSYM_PROTOTYPE(crypt_activate_by_signed_key); extern DLSYM_PROTOTYPE(crypt_activate_by_token_pin); @@ -43,9 +56,7 @@ 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); @@ -55,9 +66,7 @@ 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); -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH extern DLSYM_PROTOTYPE(crypt_token_set_external_path); -#endif extern DLSYM_PROTOTYPE(crypt_token_status); extern DLSYM_PROTOTYPE(crypt_volume_key_get); extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 7c3d10f9e3374..02035404a65de 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -56,9 +56,12 @@ static DLSYM_PROTOTYPE(dwfl_begin) = NULL; static DLSYM_PROTOTYPE(dwfl_build_id_find_elf) = NULL; static DLSYM_PROTOTYPE(dwfl_core_file_attach) = NULL; static DLSYM_PROTOTYPE(dwfl_core_file_report) = NULL; -#if HAVE_DWFL_SET_SYSROOT +/* New in elfutils 0.192. Always redeclare so DLSYM_PROTOTYPE's typeof() resolves on older headers; suppress + * the warning when newer libdw already declared it. */ +DISABLE_WARNING_REDUNDANT_DECLS; +extern int dwfl_set_sysroot(Dwfl *dwfl, const char *sysroot); /* NOLINT(readability-redundant-declaration) */ +REENABLE_WARNING; static DLSYM_PROTOTYPE(dwfl_set_sysroot) = NULL; -#endif static DLSYM_PROTOTYPE(dwfl_end) = NULL; static DLSYM_PROTOTYPE(dwfl_errmsg) = NULL; static DLSYM_PROTOTYPE(dwfl_errno) = NULL; @@ -121,9 +124,6 @@ int dlopen_dw(int log_level) { DLSYM_ARG(dwfl_module_getelf), DLSYM_ARG(dwfl_begin), DLSYM_ARG(dwfl_core_file_report), -#if HAVE_DWFL_SET_SYSROOT - DLSYM_ARG(dwfl_set_sysroot), -#endif DLSYM_ARG(dwfl_report_end), DLSYM_ARG(dwfl_getmodules), DLSYM_ARG(dwfl_core_file_attach), @@ -139,6 +139,9 @@ int dlopen_dw(int log_level) { if (r <= 0) return r; + /* Optional symbol: present in libdw 0.192+. NULL pointer is fine; call sites check at use. */ + DLSYM_OPTIONAL(dw_dl, dwfl_set_sysroot); + return 1; #else return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -146,6 +149,16 @@ int dlopen_dw(int log_level) { #endif } +bool dlopen_dw_has_dwfl_set_sysroot(void) { +#if HAVE_ELFUTILS + if (dlopen_dw(LOG_DEBUG) < 0) + return false; + return sym_dwfl_set_sysroot; +#else + return false; +#endif +} + int dlopen_elf(int log_level) { #if HAVE_ELFUTILS int r; @@ -660,13 +673,13 @@ static int parse_core( if (empty_or_root(root)) root = NULL; -#if HAVE_DWFL_SET_SYSROOT - if (root && sym_dwfl_set_sysroot(c.dwfl, root) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not set root directory, dwfl_set_sysroot() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); -#else - if (root) - log_warning("Compiled without dwfl_set_sysroot() support, ignoring provided root directory."); -#endif + if (root) { + if (sym_dwfl_set_sysroot) { + if (sym_dwfl_set_sysroot(c.dwfl, root) < 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not set root directory, dwfl_set_sysroot() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); + } else + log_warning("Loaded libdw does not support dwfl_set_sysroot(), ignoring provided root directory."); + } if (sym_dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not parse core file, dwfl_core_file_report() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); diff --git a/src/shared/elf-util.h b/src/shared/elf-util.h index e9d9e959dab3d..7ccbec56c6461 100644 --- a/src/shared/elf-util.h +++ b/src/shared/elf-util.h @@ -6,6 +6,8 @@ int dlopen_dw(int log_level); int dlopen_elf(int log_level); +bool dlopen_dw_has_dwfl_set_sysroot(void); + /* 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. * If fork_disable_dump, the child process will not dump core if it crashes. */ diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index 2ef0b37959b93..f0f3439f9284d 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -18,17 +18,15 @@ DLSYM_PROTOTYPE(archive_entry_fflags) = NULL; DLSYM_PROTOTYPE(archive_entry_filetype) = NULL; DLSYM_PROTOTYPE(archive_entry_free) = NULL; DLSYM_PROTOTYPE(archive_entry_gid) = NULL; -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET -DLSYM_PROTOTYPE(archive_entry_gid_is_set) = NULL; -#else -int sym_archive_entry_gid_is_set(struct archive_entry *e) { +static int missing_archive_entry_gid_is_set(struct archive_entry *e) { return gid_is_valid(sym_archive_entry_gid(e)); } -#endif +DLSYM_PROTOTYPE(archive_entry_gid_is_set) = missing_archive_entry_gid_is_set; DLSYM_PROTOTYPE(archive_entry_hardlink) = NULL; -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET -DLSYM_PROTOTYPE(archive_entry_hardlink_is_set) = NULL; -#endif +static int missing_archive_entry_hardlink_is_set(struct archive_entry *e) { + return !!sym_archive_entry_hardlink(e); +} +DLSYM_PROTOTYPE(archive_entry_hardlink_is_set) = missing_archive_entry_hardlink_is_set; DLSYM_PROTOTYPE(archive_entry_mode) = NULL; DLSYM_PROTOTYPE(archive_entry_mtime) = NULL; DLSYM_PROTOTYPE(archive_entry_mtime_is_set) = NULL; @@ -53,13 +51,10 @@ DLSYM_PROTOTYPE(archive_entry_set_uid) = NULL; DLSYM_PROTOTYPE(archive_entry_sparse_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_symlink) = NULL; DLSYM_PROTOTYPE(archive_entry_uid) = NULL; -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET -DLSYM_PROTOTYPE(archive_entry_uid_is_set) = NULL; -#else -int sym_archive_entry_uid_is_set(struct archive_entry *e) { +static int missing_archive_entry_uid_is_set(struct archive_entry *e) { return uid_is_valid(sym_archive_entry_uid(e)); } -#endif +DLSYM_PROTOTYPE(archive_entry_uid_is_set) = missing_archive_entry_uid_is_set; DLSYM_PROTOTYPE(archive_entry_xattr_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_xattr_next) = NULL; DLSYM_PROTOTYPE(archive_entry_xattr_reset) = NULL; @@ -84,13 +79,15 @@ DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL; int dlopen_libarchive(int log_level) { #if HAVE_LIBARCHIVE + int r; + SD_ELF_NOTE_DLOPEN( "archive", "Support for decompressing archive files", SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libarchive.so.13"); - return dlopen_many_sym_or_warn( + r = dlopen_many_sym_or_warn( &libarchive_dl, "libarchive.so.13", log_level, @@ -101,13 +98,7 @@ int dlopen_libarchive(int log_level) { DLSYM_ARG(archive_entry_filetype), DLSYM_ARG(archive_entry_free), DLSYM_ARG(archive_entry_gid), -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET - DLSYM_ARG(archive_entry_gid_is_set), -#endif DLSYM_ARG(archive_entry_hardlink), -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET - DLSYM_ARG(archive_entry_hardlink_is_set), -#endif DLSYM_ARG(archive_entry_mode), DLSYM_ARG(archive_entry_mtime), DLSYM_ARG(archive_entry_mtime_is_set), @@ -132,9 +123,6 @@ int dlopen_libarchive(int log_level) { DLSYM_ARG(archive_entry_sparse_add_entry), DLSYM_ARG(archive_entry_symlink), DLSYM_ARG(archive_entry_uid), -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET - DLSYM_ARG(archive_entry_uid_is_set), -#endif DLSYM_ARG(archive_entry_xattr_add_entry), DLSYM_ARG(archive_entry_xattr_next), DLSYM_ARG(archive_entry_xattr_reset), @@ -155,6 +143,16 @@ int dlopen_libarchive(int log_level) { DLSYM_ARG(archive_write_open_fd), DLSYM_ARG(archive_write_set_format_filter_by_ext), DLSYM_ARG(archive_write_set_format_pax)); + if (r <= 0) + return r; + + /* Optional symbols: present in newer libarchive versions only. If missing, sym_X keeps its + * fallback-function initializer (see above). */ + DLSYM_OPTIONAL(libarchive_dl, archive_entry_gid_is_set); + DLSYM_OPTIONAL(libarchive_dl, archive_entry_uid_is_set); + DLSYM_OPTIONAL(libarchive_dl, archive_entry_hardlink_is_set); + + return 1; #else return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support is not compiled in."); diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index f1f78aad5d1eb..9f47ea67eacd0 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -9,6 +9,16 @@ #include "dlfcn-util.h" +/* Newer symbols that might not be in the header we build against. Always redeclare so DLSYM_PROTOTYPE's + * typeof() resolves; suppress the warning when newer libarchive already declared them. */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +extern int archive_entry_gid_is_set(struct archive_entry *e); +extern int archive_entry_hardlink_is_set(struct archive_entry *e); +extern int archive_entry_uid_is_set(struct archive_entry *e); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry); extern DLSYM_PROTOTYPE(archive_entry_acl_next); extern DLSYM_PROTOTYPE(archive_entry_acl_reset); @@ -16,19 +26,9 @@ extern DLSYM_PROTOTYPE(archive_entry_fflags); extern DLSYM_PROTOTYPE(archive_entry_filetype); extern DLSYM_PROTOTYPE(archive_entry_free); extern DLSYM_PROTOTYPE(archive_entry_gid); -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET extern DLSYM_PROTOTYPE(archive_entry_gid_is_set); -#else -int sym_archive_entry_gid_is_set(struct archive_entry *e); -#endif extern DLSYM_PROTOTYPE(archive_entry_hardlink); -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET extern DLSYM_PROTOTYPE(archive_entry_hardlink_is_set); -#else -static inline int sym_archive_entry_hardlink_is_set(struct archive_entry *e) { - return !!sym_archive_entry_hardlink(e); -} -#endif extern DLSYM_PROTOTYPE(archive_entry_mode); extern DLSYM_PROTOTYPE(archive_entry_mtime); extern DLSYM_PROTOTYPE(archive_entry_mtime_is_set); @@ -53,11 +53,7 @@ extern DLSYM_PROTOTYPE(archive_entry_set_uid); extern DLSYM_PROTOTYPE(archive_entry_sparse_add_entry); extern DLSYM_PROTOTYPE(archive_entry_symlink); extern DLSYM_PROTOTYPE(archive_entry_uid); -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET extern DLSYM_PROTOTYPE(archive_entry_uid_is_set); -#else -int sym_archive_entry_uid_is_set(struct archive_entry *e); -#endif extern DLSYM_PROTOTYPE(archive_entry_xattr_add_entry); extern DLSYM_PROTOTYPE(archive_entry_xattr_next); extern DLSYM_PROTOTYPE(archive_entry_xattr_reset); From 68fe7fa4d62cf777a4be1df00761e27b21754b1b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:45:23 +0200 Subject: [PATCH 1654/2155] core: add RestrictFileSystemAccess= BPF LSM for dm-verity execution enforcement Add a new RestrictFileSystemAccess= boolean setting in the [Manager] section of system.conf that enforces execution only from signed dm-verity block devices and the initramfs during early boot. When RestrictFileSystemAccess=yes is set, PID1 loads a BPF LSM program early in boot that: Integrity tracking (self-populating, no userspace involvement): - bdev_setintegrity: records dm-verity signature status in a BPF hash map when the kernel signals device integrity via security_bdev_setintegrity() - bdev_free_security: removes devices from the map on teardown Execution enforcement (deny-default policy): - bprm_check_security: blocks execve() from untrusted sources - mmap_file: blocks PROT_EXEC mmap (shared libs, anonymous exec memory) - file_mprotect: blocks W->X transitions (JIT, libffi, etc.) Trust anchors: - Signed dm-verity volumes (sig_valid flag in the BPF map) - Initramfs (s_dev captured at load time, cleared after switch_root) - Everything else is denied (tmpfs, procfs, sysfs, anonymous PROT_EXEC) PID1 requires dm-verity require_signatures=1 to be enabled and refuses to load the BPF program otherwise, ensuring the kernel enforces that all dm-verity devices carry valid signatures. After attach, PID1 extracts owned FDs from the skeleton (link FDs + .bss map FD) and lets the skeleton be destroyed. The dup'd link FDs keep programs attached via the kernel reference chain (link FD -> bpf_link -> bpf_prog -> bpf_map). Destroying the skeleton unmaps the .bss page from PID1's address space so no BPF state is readable via /proc/1/mem. The .bss map FD is retained for targeted writes (clearing initramfs_s_dev after switch_root via mmap). Signed-off-by: Christian Brauner --- man/kernel-command-line.xml | 11 ++ man/systemd-system.conf.xml | 43 +++++ src/bpf/meson.build | 18 ++ src/bpf/restrict-fsaccess.bpf.c | 152 ++++++++++++++++ src/core/bpf-restrict-fs.c | 13 -- src/core/bpf-restrict-fsaccess.c | 286 +++++++++++++++++++++++++++++++ src/core/bpf-restrict-fsaccess.h | 49 ++++++ src/core/core-forward.h | 1 + src/core/main.c | 49 ++++++ src/core/manager.c | 15 ++ src/core/manager.h | 11 ++ src/core/meson.build | 5 + src/core/system.conf.in | 1 + src/shared/bpf-link.c | 16 ++ src/shared/bpf-link.h | 1 + 15 files changed, 658 insertions(+), 13 deletions(-) create mode 100644 src/bpf/restrict-fsaccess.bpf.c create mode 100644 src/core/bpf-restrict-fsaccess.c create mode 100644 src/core/bpf-restrict-fsaccess.h diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 83544b3606464..03765010f0744 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -79,6 +79,17 @@ + + systemd.restrict_filesystem_access= + + Controls the RestrictFileSystemAccess= execution enforcement policy. For + details, see + systemd-system.conf5. + + + + + systemd.mask= systemd.wants= diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index eb14cb7f30746..fb565b03506c7 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -532,6 +532,49 @@ + + RestrictFileSystemAccess= + + Takes a boolean argument or the special value exec. Defaults to + no. When enabled, PID 1 loads a BPF LSM program that enforces a deny-default + execution policy: only binaries residing on signed dm-verity block devices (and the initramfs during + early boot) are permitted to execute. Execution from tmpfs, procfs, sysfs, unsigned dm-verity devices, + and anonymous executable memory mappings is denied. + + This setting is intended as one component of an image-based, fully verified system, where the + whole boot chain (firmware, kernel image, kernel command line, initramfs) is measured and attested. + On a general-purpose system without such guarantees it does not provide a meaningful security + boundary on its own: an attacker with sufficient privilege to edit + system.conf, modify the kernel command line, or kexec into an unsigned initrd + can disable or bypass the policy. + + The enforcement hooks block execve() of untrusted binaries + (bprm_check_security), PROT_EXEC memory mappings including + shared libraries (mmap_file), and write-to-execute transitions such as JIT + compilation (file_mprotect). + + Note that execution from overlayfs mounts is blocked even if the underlying layers reside on + signed dm-verity devices, because the BPF program sees the overlay filesystem's anonymous device + number rather than the underlying block device. Multi-device filesystems such as btrfs are similarly + unsupported. + + Note that, without further measures to secure the system, kexec can be used to circumvent this. + + This requires the kernel to be booted with dm_verity.require_signatures=1 + on the kernel command line and with BPF LSM enabled (lsm=...,bpf). If either + prerequisite is not met, PID 1 will refuse to complete startup. + + The value yes is equivalent to exec. Additional + modes may be added in the future. + + This option may also be set via the systemd.restrict_filesystem_access= kernel command + line option, see + kernel-command-line7. + + + + + SystemCallArchitectures= diff --git a/src/bpf/meson.build b/src/bpf/meson.build index 32ab32e93dcdb..13af4c968e825 100644 --- a/src/bpf/meson.build +++ b/src/bpf/meson.build @@ -311,6 +311,19 @@ endif conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h) +# 'enum lsm_integrity_type' was added together with the bdev_setintegrity LSM +# hook in kernel commit 2deeb6c333e5 (v6.5). The generated vmlinux.h reflects +# the running kernel's BTF; a provided vmlinux.h can be older, so probe. +have_lsm_integrity_type = false +if use_generated_vmlinux_h + have_lsm_integrity_type = true +elif use_provided_vmlinux_h + have_lsm_integrity_type = cc.compiles( + '#include "@0@"\nenum lsm_integrity_type _t;\n'.format(provided_vmlinux_h_path), + name : 'enum lsm_integrity_type in vmlinux.h') +endif +conf.set10('HAVE_LSM_INTEGRITY_TYPE', have_lsm_integrity_type) + conf.set10('ENABLE_SYSCTL_BPF', conf.get('HAVE_VMLINUX_H') == 1 and libbpf.version().version_compare('>= 0.7')) bpf_programs = [ @@ -318,6 +331,11 @@ bpf_programs = [ 'source' : files('bind-iface.bpf.c'), 'condition' : 'BPF_FRAMEWORK', }, + { + 'source' : files('restrict-fsaccess.bpf.c'), + 'condition' : 'HAVE_LSM_INTEGRITY_TYPE', + 'depends' : vmlinux_h_dependency, + }, { 'source' : files('restrict-fs.bpf.c'), 'condition' : 'BPF_FRAMEWORK', diff --git a/src/bpf/restrict-fsaccess.bpf.c b/src/bpf/restrict-fsaccess.bpf.c new file mode 100644 index 0000000000000..a9f368ab399c0 --- /dev/null +++ b/src/bpf/restrict-fsaccess.bpf.c @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* 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. + */ + +/* Trusted Execution BPF LSM program. + * + * Enforces that only binaries from signed dm-verity block devices (or the + * initramfs during early boot) can be executed. + * + * Architecture: + * - bdev_setintegrity hook: self-populates a map of trusted devices when + * dm-verity signals signature validity + * - bdev_free_security hook: removes devices from the map on teardown + * - bprm_check_security: blocks execve() from untrusted sources + * - mmap_file: blocks PROT_EXEC mmap from untrusted sources + * - file_mprotect: blocks W->X transitions from untrusted sources + */ + +/* If offsetof() is implemented via __builtin_offset() then it doesn't work on current compilers, since the + * built-ins do not understand CO-RE. Let's undefine any such macros here, to force bpf_helpers.h to define + * its own definitions for this. (In new versions it will do so automatically, but at least in libbpf 1.1.0 + * it does not.) */ +#undef offsetof +#undef container_of + +#include "vmlinux.h" + +#include /* IWYU pragma: keep */ +#include +#include +#include + +#define PROT_EXEC 0x4 +#define VM_EXEC 0x00000004 + +/* ---- Maps ---- */ + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 0); /* placeholder */ + __type(key, __u32); /* dev_t from bdev->bd_dev */ + __type(value, __u8); /* 1 = signature valid */ +} verity_devices SEC(".maps"); + +/* ---- Globals (set by PID1 via skeleton) ---- */ + +/* Device number of the initramfs superblock. PID1 sets this at load time and + * clears it (to 0) after switch_root. A value of 0 means "no initramfs trust + * — the window is closed." */ +volatile __u32 initramfs_s_dev; + +/* ---- Integrity tracking hooks ---- */ + +SEC("lsm/bdev_setintegrity") +int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev, + enum lsm_integrity_type type, const void *value, __u64 size) +{ + if (type == LSM_INT_DMVERITY_SIG_VALID) { + __u32 dev = bdev->bd_dev; + __u8 valid = value && size > 0; + bpf_map_update_elem(&verity_devices, &dev, &valid, BPF_ANY); + } + + return 0; +} + +SEC("lsm/bdev_free_security") +void BPF_PROG(restrict_fsaccess_bdev_free, struct block_device *bdev) +{ + __u32 dev = bdev->bd_dev; + bpf_map_delete_elem(&verity_devices, &dev); +} + +/* ---- Enforcement helpers ---- */ + +/* Check whether a file is from a trusted source. + * Returns 0 (allow) or -EPERM (deny). */ +static __always_inline int check_trusted_file(struct file *file) +{ + __u32 s_dev; + __u8 *sig_valid; + + BPF_CORE_READ_INTO(&s_dev, file, f_inode, i_sb, s_dev); + + /* Check initramfs trust (active only during early boot) */ + if (initramfs_s_dev != 0 && s_dev == initramfs_s_dev) + return 0; + + /* Check verity device map */ + sig_valid = bpf_map_lookup_elem(&verity_devices, &s_dev); + if (sig_valid && *sig_valid) + return 0; + + return -EPERM; +} + +/* ---- Enforcement hooks ---- */ + +SEC("lsm/bprm_check_security") +int BPF_PROG(restrict_fsaccess_bprm_check, struct linux_binprm *bprm) +{ + struct file *file; + + BPF_CORE_READ_INTO(&file, bprm, file); + return check_trusted_file(file); +} + +SEC("lsm/mmap_file") +int BPF_PROG(restrict_fsaccess_mmap_file, struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + /* Only enforce on executable mappings */ + if (!(prot & PROT_EXEC)) + return 0; + + /* Anonymous executable mapping — no file backing, deny */ + if (!file) + return -EPERM; + + return check_trusted_file(file); +} + +SEC("lsm/file_mprotect") +int BPF_PROG(restrict_fsaccess_file_mprotect, struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + struct file *file; + unsigned long vm_flags; + + /* Only enforce when adding PROT_EXEC */ + if (!(prot & PROT_EXEC)) + return 0; + + /* If VM_EXEC is already set, the mapping is already executable — this + * mprotect isn't granting new executable capability, allow */ + BPF_CORE_READ_INTO(&vm_flags, vma, vm_flags); + if (vm_flags & VM_EXEC) + return 0; + + /* Anonymous executable mapping — no file backing, deny */ + BPF_CORE_READ_INTO(&file, vma, vm_file); + if (!file) + return -EPERM; + + return check_trusted_file(file); +} + +static const char _license[] SEC("license") = "GPL"; diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index e60e6ca7efcfe..d3c6adc145251 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -30,19 +30,6 @@ static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free); -static bool bpf_can_link_lsm_program(struct bpf_program *prog) { - _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; - - assert(prog); - - link = sym_bpf_program__attach_lsm(prog); - - /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory - * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence - * BPF_LSM_MAC attach type) is not supported. */ - return bpf_get_error_translated(link) == 0; -} - static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) { _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; _cleanup_close_ int inner_map_fd = -EBADF; diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c new file mode 100644 index 0000000000000..35bb2b86b11f8 --- /dev/null +++ b/src/core/bpf-restrict-fsaccess.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "bpf-restrict-fsaccess.h" +#include "fd-util.h" +#include "fileio.h" +#include "initrd-util.h" +#include "log.h" +#include "lsm-util.h" +#include "manager.h" +#include "memory-util.h" +#include "string-table.h" + +/* DMVERITY_DEVICES_MAX lives in bpf-restrict-fsaccess.h for sharing with tests. */ + +static const char* const restrict_filesystem_access_table[_RESTRICT_FILESYSTEM_ACCESS_MAX] = { + [RESTRICT_FILESYSTEM_ACCESS_NO] = "no", + [RESTRICT_FILESYSTEM_ACCESS_EXEC] = "exec", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(restrict_filesystem_access, RestrictFileSystemAccess, RESTRICT_FILESYSTEM_ACCESS_EXEC); + +const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX] = { + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = "restrict-fsaccess-bdev-setintegrity-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE] = "restrict-fsaccess-bdev-free-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = "restrict-fsaccess-bprm-check-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = "restrict-fsaccess-mmap-file-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = "restrict-fsaccess-file-mprotect-link", +}; + +#if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE +#include "bpf-dlopen.h" +#include "bpf-link.h" +#include "restrict-fsaccess-skel.h" + +static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { + restrict_fsaccess_bpf__destroy(obj); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fsaccess_bpf *, restrict_fsaccess_bpf_free); + +/* Verify that restrict_fsaccess_bss matches the skeleton's .bss layout */ +assert_cc(sizeof(struct restrict_fsaccess_bss) == sizeof_field(struct restrict_fsaccess_bpf, bss[0])); + +/* Build the skeleton links array indexed by the link enum. */ +#define RESTRICT_FSACCESS_LINKS(obj) { \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE] = (obj)->links.restrict_fsaccess_bdev_free, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = (obj)->links.restrict_fsaccess_bprm_check, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = (obj)->links.restrict_fsaccess_mmap_file, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = (obj)->links.restrict_fsaccess_file_mprotect, \ +} + +static bool dm_verity_require_signatures(void) { + int r; + + r = read_boolean_file("/sys/module/dm_verity/parameters/require_signatures"); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "bpf-restrict-fsaccess: Failed to read dm-verity require_signatures: %m"); + return false; + } + + return r > 0; +} + +static int get_root_s_dev(uint32_t *ret) { + struct stat st; + + assert(ret); + + /* Stat /usr/ rather than / — executable code lives in /usr/ and we push toward + * a writable non-executable /. On systems with a separate /usr partition this + * means / is intentionally not trusted. */ + if (stat("/usr/", &st) < 0) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to stat /usr/ filesystem: %m"); + + *ret = STAT_DEV_TO_KERNEL(st.st_dev); + return 0; +} + +static int prepare_restrict_fsaccess_bpf(struct restrict_fsaccess_bpf **ret) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + int r; + + assert(ret); + + obj = restrict_fsaccess_bpf__open(); + if (!obj) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to open BPF object: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.verity_devices, DMVERITY_DEVICES_MAX); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m"); + + r = restrict_fsaccess_bpf__load(obj); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object: %m"); + + *ret = TAKE_PTR(obj); + return 0; +} + +bool bpf_restrict_fsaccess_supported(void) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + static int supported = -1; + int r; + + if (supported >= 0) + return supported; + if (dlopen_bpf(LOG_WARNING) < 0) + return (supported = false); + + r = lsm_supported("bpf"); + if (r == -ENOPKG) { + log_debug_errno(r, "bpf-restrict-fsaccess: securityfs not mounted, BPF LSM not available."); + return (supported = false); + } + if (r < 0) { + log_warning_errno(r, "bpf-restrict-fsaccess: Can't determine whether the BPF LSM module is used: %m"); + return (supported = false); + } + if (r == 0) { + log_info("bpf-restrict-fsaccess: BPF LSM hook not enabled in the kernel, not supported."); + return (supported = false); + } + + r = prepare_restrict_fsaccess_bpf(&obj); + if (r < 0) + return (supported = false); + + if (!bpf_can_link_lsm_program(obj->progs.restrict_fsaccess_bprm_check)) { + log_warning("bpf-restrict-fsaccess: Failed to link program; assuming BPF LSM is not available."); + return (supported = false); + } + + return (supported = true); +} + +/* Close the initramfs trust window after switch_root by clearing initramfs_s_dev + * in the BPF .bss map. The .bss is a BPF_F_MMAPABLE array map — mmap it and do + * a single aligned 4-byte store instead of a full-value read-modify-write via + * bpf_map_update_elem, which would needlessly rewrite the guard globals too. */ +static int restrict_fsaccess_clear_initramfs_trust(int bss_map_fd) { + void *p; + + assert(bss_map_fd >= 0); + assert_cc(offsetof(struct restrict_fsaccess_bss, initramfs_s_dev) == 0); + + p = mmap(NULL, page_size(), PROT_READ | PROT_WRITE, MAP_SHARED, bss_map_fd, 0); + if (p == MAP_FAILED) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to mmap .bss map: %m"); + + /* initramfs_s_dev is at offset 0 in the .bss layout. Single aligned + * 32-bit store is atomic — BPF programs see either the old or new value, + * no torn reads possible. Guard globals are untouched. */ + *(uint32_t *) p = 0; + + /* munmap failure here is harmless: the clear above already landed in + * the kernel, and the mapping is discarded by exec anyway. */ + if (munmap(p, page_size()) < 0) + log_warning_errno(errno, "bpf-restrict-fsaccess: Failed to munmap .bss map, ignoring: %m"); + + log_info("bpf-restrict-fsaccess: Cleared initramfs trust window after switch_root."); + return 0; +} + +int bpf_restrict_fsaccess_setup(Manager *m) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + int r; + + assert(m); + + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + /* Fresh setup: verify BPF LSM is available */ + if (!bpf_restrict_fsaccess_supported()) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bpf-restrict-fsaccess: BPF LSM is not available."); + + /* Require dm-verity signature enforcement */ + if (!dm_verity_require_signatures()) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), + "bpf-restrict-fsaccess: dm-verity require_signatures is not enabled. " + "RestrictFileSystemAccess= requires the kernel to enforce dm-verity signatures. " + "Set dm_verity.require_signatures=1 on the kernel command line."); + + r = prepare_restrict_fsaccess_bpf(&obj); + if (r < 0) + return r; + + /* If we're still in the initramfs, allow execution from it by recording + * its s_dev. After switch_root, PID1 re-execs and in_initrd() returns + * false — initramfs_s_dev stays at 0 (its default), closing the trust + * window. */ + if (in_initrd()) { + uint32_t root_dev; + + r = get_root_s_dev(&root_dev); + if (r < 0) + return r; + + obj->bss->initramfs_s_dev = root_dev; + log_info("bpf-restrict-fsaccess: Initramfs trusted (s_dev=%" PRIu32 ":%" PRIu32 ")", + root_dev >> 20, root_dev & 0xFFFFF); + } + + r = restrict_fsaccess_bpf__attach(obj); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to attach BPF programs: %m"); + + log_info("bpf-restrict-fsaccess: LSM BPF programs attached"); + + /* Extract owned FDs from the skeleton. These keep the kernel BPF objects + * alive after the skeleton is destroyed. Destroying the skeleton unmaps + * the .bss page from our address space so no BPF state is reachable via + * /proc/1/mem. */ + struct bpf_link *links[] = RESTRICT_FSACCESS_LINKS(obj); + + FOREACH_ELEMENT(link, links) { + size_t idx = link - links; + + m->restrict_fsaccess_link_fds[idx] = fcntl(sym_bpf_link__fd(*link), F_DUPFD_CLOEXEC, 3); + if (m->restrict_fsaccess_link_fds[idx] < 0) { + r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup link FD for %s: %m", + restrict_fsaccess_link_names[idx]); + goto fail; + } + } + + m->restrict_fsaccess_bss_map_fd = fcntl(sym_bpf_map__fd(obj->maps.bss), F_DUPFD_CLOEXEC, 3); + if (m->restrict_fsaccess_bss_map_fd < 0) { + r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup .bss map FD: %m"); + goto fail; + } + + return 0; + +fail: + /* Close partial FDs so we don't leave a half-baked policy attached + * once the skeleton is destroyed by _cleanup_. */ + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + *fd = safe_close(*fd); + m->restrict_fsaccess_bss_map_fd = safe_close(m->restrict_fsaccess_bss_map_fd); + return r; +} + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { + assert(m); + + /* Clear initramfs_s_dev in the BPF .bss map BEFORE switch_root unmounts + * the initramfs. This eliminates the dev_t recycling window: the anonymous + * dev_t is still held by the mounted initramfs superblock, so no other + * filesystem can recycle it yet. Anonymous dev_t recycling is immediate + * and lowest-first, so a stale initramfs_s_dev is a near-certain trust + * bypass — fail closed. */ + if (!in_initrd() || m->restrict_fsaccess_bss_map_fd < 0) + return 0; + + return restrict_fsaccess_clear_initramfs_trust(m->restrict_fsaccess_bss_map_fd); +} + +#else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ + +bool bpf_restrict_fsaccess_supported(void) { + return false; +} + +int bpf_restrict_fsaccess_setup(Manager *m) { + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bpf-restrict-fsaccess: RestrictFileSystemAccess= requested but BPF framework is not compiled in."); +} + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { + return 0; +} + +#endif diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h new file mode 100644 index 0000000000000..7abbb7d3c6157 --- /dev/null +++ b/src/core/bpf-restrict-fsaccess.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "core-forward.h" +#include "macro.h" +#include "shared-forward.h" + +typedef enum RestrictFileSystemAccess { + RESTRICT_FILESYSTEM_ACCESS_NO, + RESTRICT_FILESYSTEM_ACCESS_EXEC, + _RESTRICT_FILESYSTEM_ACCESS_MAX, + _RESTRICT_FILESYSTEM_ACCESS_INVALID = -EINVAL, +} RestrictFileSystemAccess; + +const char* restrict_filesystem_access_to_string(RestrictFileSystemAccess i) _const_; +RestrictFileSystemAccess restrict_filesystem_access_from_string(const char *s) _pure_; + +enum { + RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY, + RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK, + RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE, + RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT, + _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX, +}; + +/* Maximum number of dm-verity devices tracked in the BPF hash map. */ +#define DMVERITY_DEVICES_MAX (16U*1024U) + +/* Convert userspace dev_t (from stat()) to kernel dev_t encoding (MKDEV). + * stat() returns new_encode_dev(s_dev); the BPF program reads s_dev directly + * which uses MKDEV(major, minor) = (major << 20) | minor. */ +#define STAT_DEV_TO_KERNEL(dev) \ + ((uint32_t)major(dev) << 20 | (uint32_t)minor(dev)) + +/* Mirrors the BPF program's .bss section layout for read-modify-write via + * bpf_map_lookup_elem/bpf_map_update_elem on the serialized .bss map FD. */ +struct restrict_fsaccess_bss { + uint32_t initramfs_s_dev; /* kernel dev_t encoding: (major << 20) | minor */ +}; + +extern const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; + +bool bpf_restrict_fsaccess_supported(void); +int bpf_restrict_fsaccess_setup(Manager *m); + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m); diff --git a/src/core/core-forward.h b/src/core/core-forward.h index 446cf501e43e7..14bcc142a2e0c 100644 --- a/src/core/core-forward.h +++ b/src/core/core-forward.h @@ -52,3 +52,4 @@ typedef struct Unit Unit; typedef struct UnitRef UnitRef; struct restrict_fs_bpf; +struct restrict_fsaccess_bpf; diff --git a/src/core/main.c b/src/core/main.c index 3bdce441a85bb..c10df7d87a4fc 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -22,6 +22,7 @@ #include "apparmor-setup.h" #include "architecture.h" #include "argv-util.h" +#include "bpf-restrict-fsaccess.h" #include "build.h" #include "bus-error.h" #include "capability-util.h" @@ -150,6 +151,7 @@ static char **arg_manager_environment; static uint64_t arg_capability_bounding_set; static bool arg_no_new_privs; static int arg_protect_system; +static RestrictFileSystemAccess arg_restrict_filesystem_access; static nsec_t arg_timer_slack_nsec; static Set* arg_syscall_archs; static FILE* arg_serialization; @@ -566,6 +568,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.restrict_filesystem_access")) { + + if (value) { + r = restrict_filesystem_access_from_string(value); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.restrict_filesystem_access= argument '%s', ignoring: %m", value); + else + arg_restrict_filesystem_access = r; + } else + arg_restrict_filesystem_access = RESTRICT_FILESYSTEM_ACCESS_EXEC; + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -717,6 +730,29 @@ static int config_parse_protect_system_pid1( return 0; } +static int config_parse_restrict_filesystem_access( + 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) { + + RestrictFileSystemAccess *v = ASSERT_PTR(data); + RestrictFileSystemAccess re; + + re = restrict_filesystem_access_from_string(rvalue); + if (re < 0) + return log_syntax_parse_error(unit, filename, line, re, lvalue, rvalue); + + *v = re; + return 0; +} + static int config_parse_crash_reboot( const char *unit, const char *filename, @@ -774,6 +810,7 @@ static int parse_config_file(void) { { "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", "RestrictFileSystemAccess", config_parse_restrict_filesystem_access, 0, &arg_restrict_filesystem_access }, #if HAVE_SECCOMP { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #else @@ -925,6 +962,7 @@ static void set_manager_settings(Manager *m) { manager_set_show_status(m, arg_show_status, "command line"); m->status_unit_format = arg_status_unit_format; + m->restrict_filesystem_access = arg_restrict_filesystem_access; } static int parse_argv(int argc, char *argv[]) { @@ -1247,6 +1285,16 @@ static int prepare_reexecute( m->n_reloading++; bus_manager_send_reloading(m, true); + /* Only close the initramfs trust window when actually switching root. + * During a plain daemon-reexec in the initrd, PID1 still needs to + * execv() itself from the initramfs — clearing trust here would cause + * the BPF bprm_check_security hook to deny the exec. */ + if (switching_root) { + r = bpf_restrict_fsaccess_close_initramfs_trust(m); + if (r < 0) + return r; + } + r = manager_open_serialization(m, &f); if (r < 0) return log_error_errno(r, "Failed to create serialization file: %m"); @@ -2834,6 +2882,7 @@ static void reset_arguments(void) { arg_capability_bounding_set = CAP_MASK_ALL; arg_no_new_privs = false; arg_protect_system = -1; + arg_restrict_filesystem_access = RESTRICT_FILESYSTEM_ACCESS_NO; arg_timer_slack_nsec = NSEC_INFINITY; arg_syscall_archs = set_free(arg_syscall_archs); diff --git a/src/core/manager.c b/src/core/manager.c index da4e9ca408127..0e33273c3dc24 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -21,6 +21,7 @@ #include "audit-fd.h" #include "boot-timestamps.h" #include "bpf-restrict-fs.h" +#include "bpf-restrict-fsaccess.h" #include "build-path.h" #include "bus-common-errors.h" #include "bus-error.h" @@ -941,8 +942,13 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, .dump_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_MINUTE, .burst = 10 }, .executor_fd = -EBADF, + + .restrict_fsaccess_bss_map_fd = -EBADF, }; + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + *fd = -EBADF; + unit_defaults_init(&m->defaults, runtime_scope); #if ENABLE_EFI @@ -1784,6 +1790,8 @@ Manager* manager_free(Manager *m) { #if BPF_FRAMEWORK bpf_restrict_fs_destroy(m->restrict_fs); #endif + close_many(m->restrict_fsaccess_link_fds, ELEMENTSOF(m->restrict_fsaccess_link_fds)); + safe_close(m->restrict_fsaccess_bss_map_fd); safe_close(m->executor_fd); free(m->executor_path); @@ -2140,6 +2148,13 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo m->send_reloading_done = true; } + /* Set up RestrictFileSystemAccess= BPF LSM after deserialization (so we can detect deserialized link FDs) + * and before clearing switching_root (so we can close the initramfs trust window). This must + * run after set_manager_settings() has set m->restrict_filesystem_access. */ + r = bpf_restrict_fsaccess_setup(m); + if (r < 0) + return r; + manager_ready(m); manager_set_switching_root(m, false); diff --git a/src/core/manager.h b/src/core/manager.h index 9afc70b39a1d8..fb65705d321df 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -3,6 +3,7 @@ #include "sd-event.h" +#include "bpf-restrict-fsaccess.h" #include "cgroup.h" #include "common-signal.h" #include "execute.h" @@ -479,6 +480,16 @@ typedef struct Manager { /* Reference to RestrictFileSystems= BPF program */ struct restrict_fs_bpf *restrict_fs; + /* Reference to RestrictFileSystemAccess= BPF LSM program */ + RestrictFileSystemAccess restrict_filesystem_access; + + /* Raw BPF FDs extracted from the skeleton after attach. The kernel + * reference chain (link FD -> bpf_link -> bpf_prog -> bpf_map) keeps + * programs attached and map data alive. The .bss map FD is used for + * targeted writes (clearing initramfs_s_dev after switch_root). */ + int restrict_fsaccess_link_fds[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; + int restrict_fsaccess_bss_map_fd; + /* Allow users to configure a rate limit for Reload()/Reexecute() operations */ RateLimit reload_reexec_ratelimit; /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ diff --git a/src/core/meson.build b/src/core/meson.build index eef53be94bc75..7f5845244e650 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -7,6 +7,7 @@ libcore_sources = files( 'bpf-firewall.c', 'bpf-foreign.c', 'bpf-restrict-fs.c', + 'bpf-restrict-fsaccess.c', 'bpf-restrict-ifaces.c', 'bpf-socket-bind.c', 'bpf-bind-iface.c', @@ -86,6 +87,10 @@ if conf.get('BPF_FRAMEWORK') == 1 endforeach endif +if conf.get('HAVE_LSM_INTEGRITY_TYPE') == 1 + libcore_sources += bpf_programs_by_name['restrict-fsaccess'] +endif + sources += libcore_sources load_fragment_gperf_gperf = custom_target( diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 63d28059305fe..35c7cec6efcbb 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -41,6 +41,7 @@ #CapabilityBoundingSet= #NoNewPrivileges=no #ProtectSystem=auto +#RestrictFileSystemAccess=no #SystemCallArchitectures= #TimerSlackNSec= #StatusUnitFormat={{STATUS_UNIT_FORMAT_DEFAULT_STR}} diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 95f7256a56795..80a6db47ded23 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -19,6 +19,22 @@ bool bpf_can_link_program(struct bpf_program *prog) { return bpf_get_error_translated(link) == -EBADF; } +bool bpf_can_link_lsm_program(struct bpf_program *prog) { + _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; + + assert(prog); + + if (dlopen_bpf(LOG_DEBUG) < 0) + return false; + + link = sym_bpf_program__attach_lsm(prog); + + /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory + * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence + * BPF_LSM_MAC attach type) is not supported. */ + return bpf_get_error_translated(link) == 0; +} + int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) { assert(key); diff --git a/src/shared/bpf-link.h b/src/shared/bpf-link.h index 79da1c2fea26c..4de95eb2e1ee3 100644 --- a/src/shared/bpf-link.h +++ b/src/shared/bpf-link.h @@ -8,6 +8,7 @@ #include "shared-forward.h" bool bpf_can_link_program(struct bpf_program *prog); +bool bpf_can_link_lsm_program(struct bpf_program *prog); int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link); From 6c0f509587eb90f9d586d097500a66c2f33078d3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:48:12 +0200 Subject: [PATCH 1655/2155] core: preserve RestrictFileSystemAccess= BPF state across daemon-reexec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BPF link and .bss map FDs must survive PID1 re-execution (daemon-reexec, switch_root, soft-reboot). Without serialization, manager_free() closes them before execv, programs detach, and the verity_devices map is freed. After exec a fresh skeleton would have an empty map — but existing dm-verity devices have already called bdev_setintegrity and won't call it again. The result would be a deny-default policy with an empty map, i.e., all execution denied and the system bricked. Add serialize/deserialize support using systemd's existing serialize_fd / fdset_cloexec / deserialize_fd infrastructure: Before exec (in manager_serialize via bpf_restrict_fsaccess_serialize): - Dup each link FD and the .bss map FD into the FDSet - fdset_cloexec(fds, false) + execv() preserves them across exec After exec (in manager_deserialize + bpf_restrict_fsaccess_setup): - Deserialize the link FDs and .bss map FD into the Manager struct - bpf_restrict_fsaccess_setup() detects the deserialized FDs and skips skeleton re-creation entirely — the programs are already attached - If no longer in initrd, clear initramfs_s_dev in the kernel map No bpffs pinning is needed. This avoids a bpffs mount dependency and eliminates the external attack surface that pinned objects would create (discoverable/manipulable via unlink or BPF_OBJ_GET). The FDs remain private to PID1. Signed-off-by: Christian Brauner --- src/core/bpf-restrict-fsaccess.c | 132 +++++++++++++++++++++++++++++++ src/core/bpf-restrict-fsaccess.h | 1 + src/core/manager-serialize.c | 41 +++++++++- src/shared/bpf-dlopen.c | 2 + src/shared/bpf-dlopen.h | 1 + 5 files changed, 176 insertions(+), 1 deletion(-) diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index 35bb2b86b11f8..dc8a7d63a755c 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -12,6 +12,7 @@ #include "lsm-util.h" #include "manager.h" #include "memory-util.h" +#include "serialize.h" #include "string-table.h" /* DMVERITY_DEVICES_MAX lives in bpf-restrict-fsaccess.h for sharing with tests. */ @@ -141,6 +142,27 @@ bool bpf_restrict_fsaccess_supported(void) { return (supported = true); } +/* Partial deserialization (some FDs but not all) is fatal: continuing + * would leave enforcement incomplete. */ +static int restrict_fsaccess_have_deserialized_fds(Manager *m) { + size_t count = 0; + + assert(m); + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + if (*fd >= 0) + count++; + + if (count == 0) + return 0; + if (count == ELEMENTSOF(m->restrict_fsaccess_link_fds)) + return 1; + + return log_error_errno(SYNTHETIC_ERRNO(EBADFD), + "bpf-restrict-fsaccess: Only %zu of %zu link FDs deserialized, refusing to continue with partial enforcement.", + count, ELEMENTSOF(m->restrict_fsaccess_link_fds)); +} + /* Close the initramfs trust window after switch_root by clearing initramfs_s_dev * in the BPF .bss map. The .bss is a BPF_F_MMAPABLE array map — mmap it and do * a single aligned 4-byte store instead of a full-value read-modify-write via @@ -169,6 +191,68 @@ static int restrict_fsaccess_clear_initramfs_trust(int bss_map_fd) { return 0; } +static int bpf_get_map_id(int fd, uint32_t *ret_id) { + struct bpf_map_info info = {}; + uint32_t len = sizeof(info); + int r; + + if (fd < 0) + return -EBADF; + + assert(ret_id); + + r = sym_bpf_obj_get_info_by_fd(fd, &info, &len); + if (r < 0) + return r; + + *ret_id = info.id; + return 0; +} + +/* Validate that deserialized FDs actually reference our LSM BPF links. A + * corrupted serialization file could leave FDs pointing at arbitrary kernel + * objects; a stale FD could point at a BPF link of an entirely different type + * (e.g. kprobe-multi). Verify both link type and attach type so a substituted + * FD that happens to be a BPF link still fails the check. */ +static int restrict_fsaccess_validate_deserialized_fds(Manager *m) { + int r; + + assert(m); + + r = dlopen_bpf(LOG_WARNING); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Failed to load libbpf for FD validation, aborting."); + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) { + struct bpf_link_info info = {}; + uint32_t len = sizeof(info); + const char *name = restrict_fsaccess_link_names[fd - m->restrict_fsaccess_link_fds]; + + r = sym_bpf_obj_get_info_by_fd(*fd, &info, &len); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for %s is not a valid BPF object, aborting.", + name); + + if (info.type != BPF_LINK_TYPE_TRACING || info.tracing.attach_type != BPF_LSM_MAC) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for %s is not an LSM tracing link (type=%u attach=%u), aborting.", + name, info.type, info.tracing.attach_type); + } + + if (m->restrict_fsaccess_bss_map_fd >= 0) { + uint32_t id; + + r = bpf_get_map_id(m->restrict_fsaccess_bss_map_fd, &id); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for .bss map is not a valid BPF map, aborting."); + } + + return 0; +} + int bpf_restrict_fsaccess_setup(Manager *m) { _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; int r; @@ -178,6 +262,27 @@ int bpf_restrict_fsaccess_setup(Manager *m) { if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) return 0; + r = restrict_fsaccess_have_deserialized_fds(m); + if (r < 0) + return r; + if (r > 0) { + log_info("bpf-restrict-fsaccess: Recovered link FDs from previous exec, programs still attached."); + + r = restrict_fsaccess_validate_deserialized_fds(m); + if (r < 0) + return r; + if (m->switching_root) { + if (m->restrict_fsaccess_bss_map_fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "bpf-restrict-fsaccess: Cannot clear initramfs trust after switch_root."); + r = restrict_fsaccess_clear_initramfs_trust(m->restrict_fsaccess_bss_map_fd); + if (r < 0) + return r; + } + + return 0; + } + /* Fresh setup: verify BPF LSM is available */ if (!bpf_restrict_fsaccess_supported()) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -265,6 +370,29 @@ int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { return restrict_fsaccess_clear_initramfs_trust(m->restrict_fsaccess_bss_map_fd); } +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds) { + int r; + + assert(m); + assert(f); + assert(fds); + + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) { + r = serialize_fd(f, fds, restrict_fsaccess_link_names[fd - m->restrict_fsaccess_link_fds], *fd); + if (r < 0) + return r; + } + + r = serialize_fd(f, fds, "restrict-fsaccess-bss-map", m->restrict_fsaccess_bss_map_fd); + if (r < 0) + return r; + + return 0; +} + #else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ bool bpf_restrict_fsaccess_supported(void) { @@ -283,4 +411,8 @@ int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { return 0; } +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds) { + return 0; +} + #endif diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h index 7abbb7d3c6157..8a0a9cf267760 100644 --- a/src/core/bpf-restrict-fsaccess.h +++ b/src/core/bpf-restrict-fsaccess.h @@ -47,3 +47,4 @@ bool bpf_restrict_fsaccess_supported(void); int bpf_restrict_fsaccess_setup(Manager *m); int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m); +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds); diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index c794888164874..528540e57e2e7 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "bpf-restrict-fsaccess.h" #include "dbus.h" #include "dynamic-user.h" #include "fd-util.h" @@ -180,6 +181,10 @@ int manager_serialize( if (r < 0) return r; + r = bpf_restrict_fsaccess_serialize(m, f, fds); + if (r < 0) + return r; + (void) fputc('\n', f); HASHMAP_FOREACH_KEY(u, t, m->units) { @@ -386,6 +391,38 @@ static void manager_deserialize_gid_refs_one(Manager *m, const char *value) { manager_deserialize_uid_refs_one_internal(&m->gid_refs, value); } +static void deserialize_restrict_fsaccess(Manager *m, const char *l, FDSet *fds) { + const char *val; + int fd; + + FOREACH_ELEMENT(name, restrict_fsaccess_link_names) { + val = startswith(l, *name); + if (!val) + continue; + val = startswith(val, "="); + if (!val) + continue; + fd = deserialize_fd(fds, val); + if (fd < 0) { + log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for %s: %m", *name); + return; + } + close_and_replace(m->restrict_fsaccess_link_fds[name - restrict_fsaccess_link_names], fd); + return; + } + + val = startswith(l, "restrict-fsaccess-bss-map="); + if (!val) + return; + + fd = deserialize_fd(fds, val); + if (fd < 0) { + log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for .bss map: %m"); + return; + } + close_and_replace(m->restrict_fsaccess_bss_map_fd, fd); +} + int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { int r; @@ -616,7 +653,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { else (void) varlink_server_deserialize_one(m->varlink_server, val, fds); - } else if ((val = startswith(l, "dump-ratelimit="))) + } else if (startswith(l, "restrict-fsaccess-")) + deserialize_restrict_fsaccess(m, l, fds); + else if ((val = startswith(l, "dump-ratelimit="))) deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val); else if ((val = startswith(l, "reload-reexec-ratelimit="))) deserialize_ratelimit(&m->reload_reexec_ratelimit, "reload-reexec-ratelimit", val); diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index 1d2fdef781eea..c7d9dbdd5bfa6 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -38,6 +38,7 @@ DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; +DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; @@ -154,6 +155,7 @@ int dlopen_bpf(int log_level) { DLSYM_ARG(bpf_map_get_fd_by_id), DLSYM_ARG(bpf_map_lookup_elem), DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_obj_get_info_by_fd), DLSYM_ARG(bpf_object__attach_skeleton), DLSYM_ARG(bpf_object__destroy_skeleton), DLSYM_ARG(bpf_object__detach_skeleton), diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index b3d14f9b5f437..71e6ca5d1d65a 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -25,6 +25,7 @@ extern DLSYM_PROTOTYPE(bpf_map_delete_elem); extern DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); extern DLSYM_PROTOTYPE(bpf_map_lookup_elem); extern DLSYM_PROTOTYPE(bpf_map_update_elem); +extern DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd); /* The *_skeleton APIs are autogenerated by bpftool, the targets can be found * in ./build/src/core/bpf/socket-bind/socket-bind.skel.h */ extern DLSYM_PROTOTYPE(bpf_object__attach_skeleton); From 51e88de7f8be8434f474bc595d909ba8826cc01f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:49:10 +0200 Subject: [PATCH 1656/2155] core: add self-protection guard for RestrictFileSystemAccess= BPF LSM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add self-protection guard programs to the RestrictFileSystemAccess= skeleton that prevent non-PID1 processes from obtaining FDs to our maps, programs, or links via the bpf() syscall. This blocks the primary attack vector against the RestrictFileSystemAccess= policy: using BPF_MAP_GET_FD_BY_ID to get an FD to the verity_devices map, then BPF_MAP_UPDATE_ELEM to inject fake trusted devices. Protection of program and link IDs is defense-in-depth (the kernel already blocks BPF_LINK_UPDATE and BPF_LINK_DETACH for LSM tracing links). Additionally, a ptrace guard (lsm/ptrace_access_check) blocks PTRACE_MODE_ATTACH to PID1 from other processes, preventing extraction of sensitive state from PID1's address space via ptrace, /proc/1/mem, process_vm_readv(), or pidfd_getfd(). Guard logic: 1. Allow all BPF ops from PID1 (tgid == 1, unspoofable) 2. Deny BPF_MAP_GET_FD_BY_ID for our protected map IDs 3. Deny BPF_PROG_GET_FD_BY_ID for our program IDs 4. Deny BPF_LINK_GET_FD_BY_ID for our link IDs 5. Allow everything else (zero collateral damage) The guard starts inactive (all protected IDs default to 0 in .bss). After skeleton attach, PID1 queries kernel-assigned IDs via bpf_obj_get_info_by_fd() and writes them into the guard globals via the mmap'd .bss, then extracts owned FDs and destroys the skeleton. Destroying the skeleton unmaps the .bss page from PID1's address space, so no BPF state — guard globals, protected map/prog/link IDs, initramfs_s_dev — remains readable via /proc/1/mem. The kernel map data persists (held by the dup'd FDs) but is only accessible via bpf_map_* syscalls, which the guard itself blocks for non-PID1. Signed-off-by: Christian Brauner --- src/bpf/restrict-fsaccess.bpf.c | 127 ++++++++++++++++++++++++++++++- src/core/bpf-restrict-fsaccess.c | 94 ++++++++++++++++++++++- src/core/bpf-restrict-fsaccess.h | 9 +++ 3 files changed, 224 insertions(+), 6 deletions(-) diff --git a/src/bpf/restrict-fsaccess.bpf.c b/src/bpf/restrict-fsaccess.bpf.c index a9f368ab399c0..538ddf3ef17b6 100644 --- a/src/bpf/restrict-fsaccess.bpf.c +++ b/src/bpf/restrict-fsaccess.bpf.c @@ -34,8 +34,9 @@ #include #include -#define PROT_EXEC 0x4 -#define VM_EXEC 0x00000004 +#define PROT_EXEC 0x4 +#define VM_EXEC 0x00000004 +#define PTRACE_MODE_ATTACH 0x02 /* ---- Maps ---- */ @@ -53,6 +54,19 @@ struct { * — the window is closed." */ volatile __u32 initramfs_s_dev; +/* ---- Self-protection guard globals (set by PID1 after attach) ---- + * + * While all IDs are 0 (the .bss default), the guard is inactive — no real BPF + * object has ID 0, so no comparisons match. PID1 populates these after + * attaching all programs. */ +volatile __u32 protected_map_id_verity; +volatile __u32 protected_map_id_bss; + +/* Must equal _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX in bpf-restrict-fsaccess.h — update when adding programs */ +#define NUM_PROTECTED_OBJS 9 /* 5 enforcement + 4 guard (bpf, bpf_map, bpf_prog, ptrace) */ +volatile __u32 protected_prog_ids[NUM_PROTECTED_OBJS]; +volatile __u32 protected_link_ids[NUM_PROTECTED_OBJS]; + /* ---- Integrity tracking hooks ---- */ SEC("lsm/bdev_setintegrity") @@ -149,4 +163,113 @@ int BPF_PROG(restrict_fsaccess_file_mprotect, struct vm_area_struct *vma, return check_trusted_file(file); } +/* ---- PID1 ptrace protection ---- + * + * Blocks PTRACE_MODE_ATTACH access to PID1 from any other process. This + * prevents ptrace(PTRACE_ATTACH), /proc/1/mem, process_vm_readv(), and + * pidfd_getfd() from extracting sensitive state from PID1's address space. + * + * PTRACE_MODE_READ is allowed — monitoring tools and systemctl need + * /proc/1/status, /proc/1/fd/, /proc/1/ns/ *, etc. + * + * PID1 accessing itself is allowed. */ + +SEC("lsm/ptrace_access_check") +int BPF_PROG(restrict_fsaccess_ptrace_guard, struct task_struct *child, + unsigned int mode) +{ + /* We only care about PID 1 and its threads (There are none but still.). */ + if (child->tgid != 1) + return 0; + + /* We only care about dangerous operations. */ + if (!(mode & PTRACE_MODE_ATTACH)) + return 0; + + /* PID1 (any thread) accessing itself is allowed. */ + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + return -EPERM; +} + +/* ---- Self-protection guard ---- + * + * Three hooks protect our BPF objects from non-PID1 processes: + * + * lsm/bpf_map — fires inside bpf_map_new_fd(), the chokepoint for ALL + * code paths that produce a map FD (BPF_MAP_GET_FD_BY_ID, + * BPF_OBJ_GET, BPF_MAP_CREATE). Blocks the primary attack: + * obtaining an FD to verity_devices to inject fake trusted + * devices via BPF_MAP_UPDATE_ELEM. + * + * lsm/bpf_prog — fires inside bpf_prog_new_fd(), same chokepoint coverage + * for programs. Defense-in-depth. + * + * lsm/bpf — handles BPF_LINK_GET_FD_BY_ID only. There is no + * security_bpf_link() hook in the kernel, so link + * protection uses the command-level bpf() hook. This is + * sufficient: we don't pin links in production, so + * BPF_OBJ_GET is not an attack vector for links. */ + +SEC("lsm/bpf_map") +int BPF_PROG(restrict_fsaccess_bpf_map_guard, struct bpf_map *map, + unsigned int fmode) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + id = map->id; + if (id != 0 && (id == protected_map_id_verity || + id == protected_map_id_bss)) + return -EPERM; + + return 0; +} + +SEC("lsm/bpf_prog") +int BPF_PROG(restrict_fsaccess_bpf_prog_guard, struct bpf_prog *prog) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + id = BPF_CORE_READ(prog, aux, id); + if (id == 0) + return 0; + + for (int i = 0; i < NUM_PROTECTED_OBJS; i++) + if (id == protected_prog_ids[i]) + return -EPERM; + + return 0; +} + +SEC("lsm/bpf") +int BPF_PROG(restrict_fsaccess_bpf_guard, int cmd, union bpf_attr *attr, + unsigned int size) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + if (cmd != BPF_LINK_GET_FD_BY_ID) + return 0; + + /* link_id/map_id/prog_id share the same offset in the bpf_attr union */ + id = attr->link_id; + if (id == 0) + return 0; + + for (int i = 0; i < NUM_PROTECTED_OBJS; i++) + if (id == protected_link_ids[i]) + return -EPERM; + + return 0; +} + static const char _license[] SEC("license") = "GPL"; diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index dc8a7d63a755c..af8a97c6627d0 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -30,6 +30,10 @@ const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_ [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = "restrict-fsaccess-bprm-check-link", [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = "restrict-fsaccess-mmap-file-link", [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = "restrict-fsaccess-file-mprotect-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD] = "restrict-fsaccess-ptrace-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD] = "restrict-fsaccess-bpf-map-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD] = "restrict-fsaccess-bpf-prog-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD] = "restrict-fsaccess-bpf-guard-link", }; #if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE @@ -44,8 +48,19 @@ static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_ DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fsaccess_bpf *, restrict_fsaccess_bpf_free); -/* Verify that restrict_fsaccess_bss matches the skeleton's .bss layout */ +/* Verify that restrict_fsaccess_bss matches the skeleton's .bss layout. The sizeof + * check catches field additions/removals; the offsetof checks catch field + * reordering. Field order in restrict_fsaccess_bss must match the BPF global + * declaration order in restrict-fsaccess.bpf.c — this is what bpftool uses for the + * generated struct. The read-modify-write in restrict_fsaccess_clear_initramfs_trust() + * depends on this layout. */ assert_cc(sizeof(struct restrict_fsaccess_bss) == sizeof_field(struct restrict_fsaccess_bpf, bss[0])); +assert_cc(offsetof(struct restrict_fsaccess_bss, initramfs_s_dev) == + offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), initramfs_s_dev)); +assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_verity) == + offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), protected_map_id_verity)); +assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_bss) == + offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), protected_map_id_bss)); /* Build the skeleton links array indexed by the link enum. */ #define RESTRICT_FSACCESS_LINKS(obj) { \ @@ -54,6 +69,10 @@ assert_cc(sizeof(struct restrict_fsaccess_bss) == sizeof_field(struct restrict_f [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = (obj)->links.restrict_fsaccess_bprm_check, \ [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = (obj)->links.restrict_fsaccess_mmap_file, \ [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = (obj)->links.restrict_fsaccess_file_mprotect, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD] = (obj)->links.restrict_fsaccess_ptrace_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD] = (obj)->links.restrict_fsaccess_bpf_map_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD] = (obj)->links.restrict_fsaccess_bpf_prog_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD] = (obj)->links.restrict_fsaccess_bpf_guard, \ } static bool dm_verity_require_signatures(void) { @@ -209,6 +228,63 @@ static int bpf_get_map_id(int fd, uint32_t *ret_id) { return 0; } +static int bpf_get_link_ids(int fd, uint32_t *ret_link_id, uint32_t *ret_prog_id) { + struct bpf_link_info info = {}; + uint32_t len = sizeof(info); + int r; + + if (fd < 0) + return -EBADF; + + r = sym_bpf_obj_get_info_by_fd(fd, &info, &len); + if (r < 0) + return r; + + if (ret_link_id) + *ret_link_id = info.id; + if (ret_prog_id) + *ret_prog_id = info.prog_id; + + return 0; +} + +/* Populate guard globals with kernel-assigned IDs so the guard hooks block + * non-PID1 access to our maps/progs/links via the bpf() syscall. */ +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { + int r; + + assert(obj); + + struct bpf_link *links[] = RESTRICT_FSACCESS_LINKS(obj); + assert_cc(ELEMENTSOF(links) == _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX); + + /* Map IDs */ + r = bpf_get_map_id(sym_bpf_map__fd(obj->maps.verity_devices), &obj->bss->protected_map_id_verity); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get verity_devices map ID: %m"); + + r = bpf_get_map_id(sym_bpf_map__fd(obj->maps.bss), &obj->bss->protected_map_id_bss); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get .bss map ID: %m"); + + /* Link and program IDs (each link knows its associated program) */ + FOREACH_ELEMENT(link, links) { + size_t idx = link - links; + + r = bpf_get_link_ids(sym_bpf_link__fd(*link), + &obj->bss->protected_link_ids[idx], + &obj->bss->protected_prog_ids[idx]); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get link/prog IDs for %s: %m", + restrict_fsaccess_link_names[idx]); + } + + log_info("bpf-restrict-fsaccess: Guard globals populated (verity_map=%u, bss_map=%u)", + (unsigned) obj->bss->protected_map_id_verity, + (unsigned) obj->bss->protected_map_id_bss); + return 0; +} + /* Validate that deserialized FDs actually reference our LSM BPF links. A * corrupted serialization file could leave FDs pointing at arbitrary kernel * objects; a stale FD could point at a BPF link of an entirely different type @@ -321,12 +397,18 @@ int bpf_restrict_fsaccess_setup(Manager *m) { log_info("bpf-restrict-fsaccess: LSM BPF programs attached"); + /* Now that all programs are attached, populate the guard's globals with + * the kernel-assigned IDs of our maps, programs, and links. From this + * point on, non-PID1 processes cannot obtain FDs to our BPF objects. */ + r = bpf_restrict_fsaccess_populate_guard(obj); + if (r < 0) + return r; + /* Extract owned FDs from the skeleton. These keep the kernel BPF objects * alive after the skeleton is destroyed. Destroying the skeleton unmaps - * the .bss page from our address space so no BPF state is reachable via - * /proc/1/mem. */ + * the .bss page from our address space so no BPF state (guard globals, + * map IDs, initramfs_s_dev) is reachable via /proc/1/mem. */ struct bpf_link *links[] = RESTRICT_FSACCESS_LINKS(obj); - FOREACH_ELEMENT(link, links) { size_t idx = link - links; @@ -407,6 +489,10 @@ int bpf_restrict_fsaccess_setup(Manager *m) { "bpf-restrict-fsaccess: RestrictFileSystemAccess= requested but BPF framework is not compiled in."); } +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { + return 0; +} + int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { return 0; } diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h index 8a0a9cf267760..a23beab4ce59c 100644 --- a/src/core/bpf-restrict-fsaccess.h +++ b/src/core/bpf-restrict-fsaccess.h @@ -23,6 +23,10 @@ enum { RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK, RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE, RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT, + RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD, _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX, }; @@ -39,12 +43,17 @@ enum { * bpf_map_lookup_elem/bpf_map_update_elem on the serialized .bss map FD. */ struct restrict_fsaccess_bss { uint32_t initramfs_s_dev; /* kernel dev_t encoding: (major << 20) | minor */ + uint32_t protected_map_id_verity; + uint32_t protected_map_id_bss; + uint32_t protected_prog_ids[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; + uint32_t protected_link_ids[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; }; extern const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; bool bpf_restrict_fsaccess_supported(void); int bpf_restrict_fsaccess_setup(Manager *m); +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj); int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m); int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds); From ad5eb75e607d2184c7ab6b778b2559cca4a17796 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:50:20 +0200 Subject: [PATCH 1657/2155] core: expose internal helpers for test-bpf-restrict-fsaccess Make dm_verity_require_signatures() non-static and declare it in the header so the test helper binary can exercise the same precondition checks that PID1 uses. Signed-off-by: Christian Brauner --- src/core/bpf-restrict-fsaccess.c | 16 ++++++++++++---- src/core/bpf-restrict-fsaccess.h | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index af8a97c6627d0..a38665a6e20d6 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -75,7 +75,7 @@ assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_bss) == [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD] = (obj)->links.restrict_fsaccess_bpf_guard, \ } -static bool dm_verity_require_signatures(void) { +bool dm_verity_require_signatures(void) { int r; r = read_boolean_file("/sys/module/dm_verity/parameters/require_signatures"); @@ -103,7 +103,7 @@ static int get_root_s_dev(uint32_t *ret) { return 0; } -static int prepare_restrict_fsaccess_bpf(struct restrict_fsaccess_bpf **ret) { +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; int r; @@ -149,7 +149,7 @@ bool bpf_restrict_fsaccess_supported(void) { return (supported = false); } - r = prepare_restrict_fsaccess_bpf(&obj); + r = bpf_restrict_fsaccess_prepare(&obj); if (r < 0) return (supported = false); @@ -371,7 +371,7 @@ int bpf_restrict_fsaccess_setup(Manager *m) { "RestrictFileSystemAccess= requires the kernel to enforce dm-verity signatures. " "Set dm_verity.require_signatures=1 on the kernel command line."); - r = prepare_restrict_fsaccess_bpf(&obj); + r = bpf_restrict_fsaccess_prepare(&obj); if (r < 0) return r; @@ -477,6 +477,10 @@ int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds) { #else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ +bool dm_verity_require_signatures(void) { + return false; +} + bool bpf_restrict_fsaccess_supported(void) { return false; } @@ -489,6 +493,10 @@ int bpf_restrict_fsaccess_setup(Manager *m) { "bpf-restrict-fsaccess: RestrictFileSystemAccess= requested but BPF framework is not compiled in."); } +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { + return -EOPNOTSUPP; +} + int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { return 0; } diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h index a23beab4ce59c..a39f602539af3 100644 --- a/src/core/bpf-restrict-fsaccess.h +++ b/src/core/bpf-restrict-fsaccess.h @@ -51,8 +51,10 @@ struct restrict_fsaccess_bss { extern const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; +bool dm_verity_require_signatures(void); bool bpf_restrict_fsaccess_supported(void); int bpf_restrict_fsaccess_setup(Manager *m); +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret); int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj); int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m); From 5439911f59db2aa32799b4c88e5a2c34a61367f6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:52:18 +0200 Subject: [PATCH 1658/2155] test: add integration tests for RestrictFileSystemAccess= BPF LSM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TEST-90-RESTRICT-FSACCESS with two subtests: config subtest — Tests PID1's RestrictFileSystemAccess= configuration parsing and failure modes via system.conf drop-ins and daemon-reexec: - Default RestrictFileSystemAccess=no produces no log messages - RestrictFileSystemAccess=yes without BPF LSM logs appropriate warning - RestrictFileSystemAccess=yes without require_signatures is correctly rejected by the test helper binary's precondition check enforce subtest — Tests actual BPF LSM enforcement using a test helper binary (test-bpf-restrict-fsaccess) that loads the BPF skeleton with initramfs_s_dev set to the rootfs s_dev, pins BPF links, and exits: - Execution from rootfs continues to work (trusted via initramfs_s_dev) - Execution from tmpfs is blocked with EPERM - Execution from a signed dm-verity device is allowed, driven via systemd-run -p RootImage= against the pre-built signed minimal_0 images that mkosi ships and signs at image build time (no on-the-fly squashfs / verity hash tree / signature build required) - After BPF detach, enforcement is lifted All tests skip gracefully when prerequisites are not met (BPF LSM, BPF framework, dm-verity tools, signing keys). Signed-off-by: Christian Brauner --- mkosi/mkosi.conf | 1 + src/test/meson.build | 5 + src/test/test-bpf-restrict-fsaccess.c | 222 +++++++++++++ .../TEST-90-RESTRICT-FSACCESS/meson.build | 50 +++ test/integration-tests/meson.build | 1 + .../units/TEST-90-RESTRICT-FSACCESS.config.sh | 103 ++++++ ...-90-RESTRICT-FSACCESS.dm-verity-keyring.sh | 107 ++++++ .../TEST-90-RESTRICT-FSACCESS.enforce.sh | 311 ++++++++++++++++++ test/units/TEST-90-RESTRICT-FSACCESS.sh | 11 + 9 files changed, 811 insertions(+) create mode 100644 src/test/test-bpf-restrict-fsaccess.c create mode 100644 test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build create mode 100755 test/units/TEST-90-RESTRICT-FSACCESS.config.sh create mode 100755 test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh create mode 100755 test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh create mode 100755 test/units/TEST-90-RESTRICT-FSACCESS.sh diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 0fbb81eeed23e..ca5c061079b85 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -101,6 +101,7 @@ Packages= gzip jq kbd + keyutils kmod less lsof diff --git a/src/test/meson.build b/src/test/meson.build index 966619c95f6d6..482f431b80ab9 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -544,6 +544,11 @@ executables += [ 'sources' : files('test-bpf-restrict-fs.c'), 'dependencies' : common_test_dependencies, }, + core_test_template + { + 'sources' : files('test-bpf-restrict-fsaccess.c'), + 'dependencies' : common_test_dependencies, + 'type' : 'manual', + }, core_test_template + { 'sources' : files('test-bpf-token.c'), 'dependencies' : common_test_dependencies + libbpf, diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c new file mode 100644 index 0000000000000..e95e706c276cc --- /dev/null +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Test helper for RestrictFileSystemAccess= BPF enforcement tests. + * + * Usage: + * test-bpf-restrict-fsaccess attach — Load, attach, print IDs, then block. + * Kill the process to detach (synchronous + * via bpf_link_put_direct on last FD close). + * test-bpf-restrict-fsaccess check — Check BPF LSM + require_signatures preconditions + * test-bpf-restrict-fsaccess mmap-exec PATH — Attempt PROT_READ|PROT_EXEC mmap of PATH + * test-bpf-restrict-fsaccess anon-mmap-exec — Attempt anonymous PROT_READ|PROT_EXEC mmap + * test-bpf-restrict-fsaccess mprotect-exec PATH — mmap PATH PROT_READ, then mprotect to PROT_EXEC + * + * When "attach" is used, the BPF LSM program is loaded with initramfs_s_dev + * set to the current rootfs s_dev, so the calling test script (running from + * the rootfs) continues to work. The process holds all link FDs and blocks; + * when killed, close() drops the last reference synchronously. + */ + +#include +#include +#include +#include +#include + +#include "bpf-restrict-fsaccess.h" +#include "fd-util.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +/* ---- mmap/mprotect probe commands (no BPF dependency) ---- + * + * These exercise the mmap_file, file_mprotect, and anonymous-mmap LSM hooks. + * The test script copies a file to tmpfs and passes its path here. + * Returns 0 if the operation was allowed, negative errno if denied. */ + +static int do_mmap_exec(const char *path) { + _cleanup_close_ int fd = -EBADF; + void *addr; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + addr = mmap(NULL, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + return log_info_errno(errno, "PROT_EXEC mmap of %s denied: %m", path); + + (void) munmap(addr, 4096); + log_info("PROT_EXEC mmap of %s succeeded", path); + return 0; +} + +static int do_anon_mmap_exec(void) { + void *addr; + + addr = mmap(NULL, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) + return log_info_errno(errno, "Anonymous PROT_EXEC mmap denied: %m"); + + (void) munmap(addr, 4096); + log_info("Anonymous PROT_EXEC mmap succeeded"); + return 0; +} + +static int do_mprotect_exec(const char *path) { + _cleanup_close_ int fd = -EBADF; + void *addr; + int r; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + addr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + return log_error_errno(errno, "PROT_READ mmap of %s failed: %m", path); + + r = mprotect(addr, 4096, PROT_READ | PROT_EXEC); + if (r < 0) + r = -errno; + + (void) munmap(addr, 4096); + + if (r < 0) + return log_info_errno(r, "mprotect PROT_EXEC on %s denied: %m", path); + + log_info("mprotect PROT_EXEC on %s succeeded", path); + return 0; +} + +#if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE +#include "bpf-dlopen.h" +#include "restrict-fsaccess-skel.h" + +static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { + restrict_fsaccess_bpf__destroy(obj); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fsaccess_bpf *, restrict_fsaccess_bpf_free); + +static int do_attach(void) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + struct stat st; + int r; + + r = dlopen_bpf(LOG_ERR); + if (r < 0) + return log_error_errno(r, "Failed to dlopen libbpf: %m"); + + r = bpf_restrict_fsaccess_prepare(&obj); + if (r < 0) + return r; + + /* Set initramfs_s_dev to rootfs s_dev so the test script keeps running */ + if (stat("/", &st) < 0) + return log_error_errno(errno, "Failed to stat /: %m"); + + obj->bss->initramfs_s_dev = STAT_DEV_TO_KERNEL(st.st_dev); + log_info("Set initramfs_s_dev to %u:%u (kernel dev_t=0x%x)", + major(st.st_dev), minor(st.st_dev), obj->bss->initramfs_s_dev); + + r = restrict_fsaccess_bpf__attach(obj); + if (r < 0) + return log_error_errno(r, "Failed to attach BPF programs: %m"); + + /* Populate guard globals so the guard protects our BPF objects */ + r = bpf_restrict_fsaccess_populate_guard(obj); + if (r < 0) + return log_error_errno(r, "Failed to populate guard globals: %m"); + + printf("VERITY_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_verity); + printf("BSS_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_bss); + + /* Print comma-separated prog and link IDs for guard tests */ + printf("PROG_IDS=\""); + for (size_t i = 0; i < _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX; i++) + printf("%s%u", i > 0 ? "," : "", (unsigned) obj->bss->protected_prog_ids[i]); + printf("\"\n"); + + printf("LINK_IDS=\""); + for (size_t i = 0; i < _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX; i++) + printf("%s%u", i > 0 ? "," : "", (unsigned) obj->bss->protected_link_ids[i]); + printf("\"\n"); + + fflush(stdout); + + /* Block until killed. The _cleanup_ destructor holds all link FDs via + * the skeleton. When this process is killed, close() on the FDs goes + * through bpf_link_put_direct() which synchronously detaches the + * trampoline before the process exits. No bpffs pins needed. */ + log_info("BPF programs attached, waiting for signal to detach..."); + for (;;) + pause(); + + /* unreachable — cleanup happens via signal/exit */ +} + +static int do_check(void) { + if (!bpf_restrict_fsaccess_supported()) { + log_error("BPF LSM is not available"); + return -EOPNOTSUPP; + } + log_info("BPF LSM: supported"); + + if (!dm_verity_require_signatures()) { + log_error("dm-verity require_signatures is not enabled"); + return -ENOKEY; + } + log_info("dm-verity require_signatures: enabled"); + + return 0; +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + if (argc < 2) { + log_error("Usage: %s attach|check|mmap-exec|anon-mmap-exec|mprotect-exec", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (streq(argv[1], "attach")) + return do_attach() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "check")) + return do_check() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mmap-exec") && argc == 3) + return do_mmap_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "anon-mmap-exec")) + return do_anon_mmap_exec() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mprotect-exec") && argc == 3) + return do_mprotect_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_error("Usage: %s attach|check|mmap-exec PATH|anon-mmap-exec|mprotect-exec PATH", + program_invocation_short_name); + return EXIT_FAILURE; +} + +#else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + /* mmap/mprotect probes work without BPF */ + if (argc >= 2) { + if (streq(argv[1], "mmap-exec") && argc == 3) + return do_mmap_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "anon-mmap-exec")) + return do_anon_mmap_exec() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mprotect-exec") && argc == 3) + return do_mprotect_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + + log_info("BPF framework not available, attach/check not supported"); + return 77; /* skip */ +} + +#endif diff --git a/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build b/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build new file mode 100644 index 0000000000000..2f3228f010056 --- /dev/null +++ b/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Config subtest: no require_signatures — tests error paths and config parsing. +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + 'vm' : true, + 'firmware' : 'auto', + 'configuration' : integration_test_template['configuration'] + { + 'env' : {'TEST_MATCH_SUBTEST' : 'config'}, + }, + }, +] + +# Enforce subtest: with require_signatures=1 — tests actual BPF enforcement +# via the SecureBoot DB → .platform keyring path (firmware: auto enables +# UEFI/SecureBoot in the test VM). +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()) + '-enforce', + 'vm' : true, + 'firmware' : 'auto', + 'cmdline' : integration_test_template['cmdline'] + [ + 'dm_verity.require_signatures=1', + ], + 'configuration' : integration_test_template['configuration'] + { + 'command' : '/usr/lib/systemd/tests/testdata/units/' + fs.name(meson.current_source_dir()) + '.sh', + 'env' : {'TEST_MATCH_SUBTEST' : 'enforce'}, + }, + }, +] + +# dm-verity keyring subtest: exercise the .dm-verity keyring provisioning path +# (kernel commit 033724b1c627, v7.0+) without UEFI/SecureBoot — boots with +# linux-noinitrd so the .platform keyring stays empty and the only way to +# trust the signed verity image is via the dedicated .dm-verity keyring. +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()) + '-dm-verity-keyring', + 'vm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'dm_verity.require_signatures=1', + 'dm_verity.keyring_unsealed=1', + ], + 'configuration' : integration_test_template['configuration'] + { + 'command' : '/usr/lib/systemd/tests/testdata/units/' + fs.name(meson.current_source_dir()) + '.sh', + 'env' : {'TEST_MATCH_SUBTEST' : 'dm-verity-keyring'}, + }, + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 7888283db81cb..28d8e97d935a6 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-RESTRICT-FSACCESS', ] subdir(dirname) endforeach diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.config.sh b/test/units/TEST-90-RESTRICT-FSACCESS.config.sh new file mode 100755 index 0000000000000..81bd0ff4144b6 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.config.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Test RestrictFileSystemAccess= configuration parsing and graceful failure modes. +# +# Runs in a VM WITHOUT dm_verity.require_signatures=1, so enabling RestrictFileSystemAccess +# triggers the require_signatures error path without activating enforcement. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +# RestrictFileSystemAccess= requires +BPF_FRAMEWORK at compile time +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +HELPER=/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess +CURSOR_FILE=/tmp/restrict-fsaccess-config.cursor + +cleanup() { + rm -f "$CURSOR_FILE" + rm -f /run/systemd/system.conf.d/50-restrict-fsaccess.conf +} +trap cleanup EXIT + +disable_restrict_fsaccess() { + rm -f /run/systemd/system.conf.d/50-restrict-fsaccess.conf +} + +# ------ Test case 1: Default (RestrictFileSystemAccess=no) — no log messages ------ + +testcase_default_no_messages() { + disable_restrict_fsaccess + + # Save a journal cursor so we only check messages from after this point. + journalctl -q -n 0 --cursor-file="$CURSOR_FILE" + + systemctl daemon-reexec + # daemon-reexec is synchronous: PID1 has completed startup (including any + # RestrictFileSystemAccess= setup) and is back on D-Bus by the time it returns. PID1 + # logs to kmsg synchronously, so messages are already in the journal. + + # No RestrictFileSystemAccess-related messages should appear + if journalctl --cursor-file="$CURSOR_FILE" -o cat _PID=1 | grep "bpf-restrict-fsaccess" >/dev/null 2>&1; then + echo "Unexpected RestrictFileSystemAccess log messages with RestrictFileSystemAccess=no" + return 1 + fi +} + +# ------ Test case 2: require_signatures check via helper binary ------ +# +# The helper binary runs the same precondition checks as PID1 (BPF LSM +# availability, dm-verity require_signatures). When require_signatures is +# off the check must fail — this verifies the C code gate without going +# through daemon-reexec (which would kill PID1). + +testcase_no_require_signatures_helper() { + if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available, skipping require_signatures test" + return 0 + fi + + # Check that the kernel has the bdev_setintegrity LSM hook in BTF. + # Without it the skeleton fails to load and the check reports "BPF LSM + # is not available" which masks the real reason. + if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook, skipping require_signatures test" + return 0 + fi + fi + + if [[ ! -x "$HELPER" ]]; then + echo "Helper binary not found, skipping" + return 0 + fi + + # This VM boots WITHOUT require_signatures. + if [[ -e /sys/module/dm_verity/parameters/require_signatures ]]; then + local val + val="$(cat /sys/module/dm_verity/parameters/require_signatures)" + if [[ "$val" == "Y" || "$val" == "1" ]]; then + echo "require_signatures already enabled, skipping (enforce VM covers this)" + return 0 + fi + fi + + # The helper's "check" command runs the same bpf_restrict_fsaccess_supported() + # and dm_verity_require_signatures() checks that PID1 uses. It must fail + # because require_signatures is not enabled. + if "$HELPER" check; then + echo "ERROR: helper check succeeded but require_signatures is not enabled" + return 1 + fi + echo "Helper correctly rejected setup: require_signatures not enabled" +} + +run_testcases diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh b/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh new file mode 100755 index 0000000000000..6237483a4575c --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Exercise the dedicated .dm-verity keyring trust path (kernel commit +# 033724b1c627, v7.0+): boot with linux-noinitrd so .platform stays empty, +# provision the mkosi cert into .dm-verity via keyctl, then verify a signed +# verity image still loads and execs under the BPF policy. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available in kernel, skipping" + exit 0 +fi + +if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook, skipping" + exit 0 + fi +fi + +if [[ -v ASAN_OPTIONS ]]; then + echo "Skipping under sanitizers" + exit 0 +fi + +HELPER="/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess" +if [[ ! -x "$HELPER" ]]; then + echo "ERROR: test-bpf-restrict-fsaccess helper not found at $HELPER" >&2 + exit 1 +fi + +# Helper exits 77 when systemd was built with bpf-framework=enabled but no +# vmlinux.h (HAVE_LSM_INTEGRITY_TYPE=0), so the BPF program isn't compiled in. +rc=0 +"$HELPER" check >/dev/null 2>&1 || rc=$? +if [[ "$rc" -eq 77 ]]; then + echo "test-bpf-restrict-fsaccess built without BPF attach support, skipping" + exit 0 +fi + +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + modprobe dm_verity 2>/dev/null || true +fi +val="$(cat /sys/module/dm_verity/parameters/require_signatures 2>/dev/null || echo)" +if [[ "$val" != "Y" && "$val" != "1" ]]; then + echo "require_signatures not enabled, skipping" + exit 0 +fi + +# Provision the .dm-verity keyring. Empty description lets the kernel derive +# one from the X.509 subject so machine_supports_verity_keyring finds the CN. +keyid=$(openssl x509 -in /usr/share/mkosi.crt -outform DER | + keyctl padd asymmetric '' %:.dm-verity 2>/dev/null) || keyid="" +if [[ -z "$keyid" ]]; then + echo ".dm-verity keyring not provisionable (kernel < v7.0?), skipping" + exit 0 +fi +if ! keyctl restrict_keyring %:.dm-verity; then + keyctl unlink "$keyid" %:.dm-verity 2>/dev/null || true + echo "ERROR: keyctl restrict_keyring failed" >&2 + exit 1 +fi +echo "Provisioned .dm-verity keyring with mkosi.crt" + +at_exit() { + set +e + [[ -n "${HELPER_PID:-}" ]] && kill "$HELPER_PID" 2>/dev/null && wait "$HELPER_PID" 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-dvk-attach.out +} +trap at_exit EXIT + +HELPER_PID= +exec 3< <(exec "$HELPER" attach) +HELPER_PID=$! +while IFS= read -r -t 60 line <&3; do + echo "$line" + [[ "$line" == LINK_IDS=* ]] && break +done > /tmp/restrict-fsaccess-dvk-attach.out + +# Fail closed if helper died before printing the full handshake: an unattached +# program would let the subsequent verity exec test pass trivially. +if ! kill -0 "$HELPER_PID" 2>/dev/null; then + echo "ERROR: helper exited before BPF programs were attached" >&2 + exit 1 +fi +grep -E '^LINK_IDS="[^"]+"' /tmp/restrict-fsaccess-dvk-attach.out >/dev/null || { + echo "ERROR: helper did not report LINK_IDS, BPF programs not attached" >&2 + exit 1 +} + +# Run a binary off the signed minimal_0 verity image. Trust path is exclusively +# the .dm-verity keyring we just provisioned; .platform is empty under +# linux-noinitrd. +systemd-run --pipe --wait \ + --property RootImage=/usr/share/minimal_0.raw \ + bash --version >/dev/null +echo "Execution from signed dm-verity device (via .dm-verity keyring): OK" diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh new file mode 100755 index 0000000000000..a9db974803678 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh @@ -0,0 +1,311 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Test RestrictFileSystemAccess= BPF enforcement. +# +# Uses a C test helper to load the BPF program with initramfs_s_dev set to the +# current rootfs s_dev, then verifies that execution from tmpfs is blocked +# while execution from the rootfs continues to work. If dm-verity signing +# support is available, also tests execution from a signed verity device. +# +# Requires the VM to be booted with dm-verity.require_signatures=1 on the +# kernel command line (set in the test's meson.build). +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Skip if prerequisites not met +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available in kernel, skipping" + exit 0 +fi + +# Check that the kernel has the bdev_setintegrity LSM hook in BTF. +# Older kernels (e.g., CentOS 9 with 5.14) lack this hook entirely. +if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook (required for RestrictFileSystemAccess=), skipping" + exit 0 + fi +fi + +if [[ -v ASAN_OPTIONS ]]; then + echo "Skipping enforcement test under sanitizers" + exit 0 +fi + +HELPER="/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess" +if [[ ! -x "$HELPER" ]]; then + echo "ERROR: test-bpf-restrict-fsaccess helper not found at $HELPER" >&2 + exit 1 +fi + +# Helper exits 77 when systemd was built with bpf-framework=enabled but no +# vmlinux.h (HAVE_LSM_INTEGRITY_TYPE=0), so the BPF program isn't compiled in. +rc=0 +"$HELPER" check >/dev/null 2>&1 || rc=$? +if [[ "$rc" -eq 77 ]]; then + echo "test-bpf-restrict-fsaccess built without BPF attach support, skipping" + exit 0 +fi + +# require_signatures is read-only — must be set via kernel cmdline +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + modprobe dm_verity 2>/dev/null || true +fi +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + echo "dm_verity module not available, skipping enforcement test" + exit 0 +fi +val="$(cat /sys/module/dm_verity/parameters/require_signatures)" +if [[ "$val" != "Y" && "$val" != "1" ]]; then + echo "require_signatures not enabled (need dm-verity.require_signatures=1 on cmdline), skipping" + exit 0 +fi + +at_exit() { + set +e + # Kill the attach helper to detach BPF programs synchronously + [[ -n "${HELPER_PID:-}" ]] && kill "$HELPER_PID" 2>/dev/null && wait "$HELPER_PID" 2>/dev/null || true + # Clean up tmpfs test directories + umount /tmp/restrict-fsaccess-test 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-test + umount /tmp/restrict-fsaccess-baseline 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-baseline + # Clean up background processes + [[ -n "${SLEEP_PID:-}" ]] && kill "$SLEEP_PID" 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-attach.out +} +trap at_exit EXIT + +# ------ Baseline: verify tmpfs exec works WITHOUT our BPF ------ +# +# Keep the destination basename as "true": on systems shipping uutils-coreutils +# (or busybox) as a multicall binary, /usr/bin/true is a symlink and cp +# dereferences it, copying the multicall binary. The dispatcher selects the +# subcommand from basename(argv[0]), so the copy only behaves as true when +# invoked under that name. + +mkdir -p /tmp/restrict-fsaccess-baseline +mount -t tmpfs tmpfs /tmp/restrict-fsaccess-baseline +cp /usr/bin/true /tmp/restrict-fsaccess-baseline/true +chmod +x /tmp/restrict-fsaccess-baseline/true +if ! /tmp/restrict-fsaccess-baseline/true 2>/dev/null; then + echo "WARNING: tmpfs exec blocked BEFORE BPF attach (another LSM?)" >&2 + echo "Skipping enforcement test, baseline tmpfs exec fails" + umount /tmp/restrict-fsaccess-baseline; rm -rf /tmp/restrict-fsaccess-baseline + exit 0 +fi +echo "Baseline: tmpfs exec works without BPF" +umount /tmp/restrict-fsaccess-baseline; rm -rf /tmp/restrict-fsaccess-baseline + +# ------ Attach BPF with rootfs trusted ------ +# The helper attaches, prints map/prog/link IDs, then blocks holding FDs. +# Kill it to detach synchronously (close() drops last ref via bpf_link_put_direct). + +HELPER_PID= +exec 3< <(exec "$HELPER" attach) +HELPER_PID=$! + +# Read helper output line by line until LINK_IDS= (the last line before pause()). +# read -t 60 handles both timeout and helper crash (EOF on death). +while IFS= read -r -t 60 line <&3; do + echo "$line" + [[ "$line" == LINK_IDS=* ]] && break +done > /tmp/restrict-fsaccess-attach.out + +VERITY_MAP_ID=$(sed -n 's/^VERITY_MAP_ID=//p' /tmp/restrict-fsaccess-attach.out) +BSS_MAP_ID=$(sed -n 's/^BSS_MAP_ID=//p' /tmp/restrict-fsaccess-attach.out) +PROG_IDS=$(sed -n 's/^PROG_IDS="\(.*\)"$/\1/p' /tmp/restrict-fsaccess-attach.out) +LINK_IDS=$(sed -n 's/^LINK_IDS="\(.*\)"$/\1/p' /tmp/restrict-fsaccess-attach.out) +[[ -n "$VERITY_MAP_ID" ]] || { echo "ERROR: Failed to capture VERITY_MAP_ID from helper output" >&2; exit 1; } +[[ -n "$BSS_MAP_ID" ]] || { echo "ERROR: Failed to capture BSS_MAP_ID from helper output" >&2; exit 1; } +[[ -n "$PROG_IDS" ]] || { echo "ERROR: Failed to capture PROG_IDS from helper output" >&2; exit 1; } +[[ -n "$LINK_IDS" ]] || { echo "ERROR: Failed to capture LINK_IDS from helper output" >&2; exit 1; } + +# ------ Test: Rootfs execution still works ------ + +/usr/bin/true +echo "Rootfs execution: OK" + +# ------ Test: Execution from tmpfs is blocked ------ + +mkdir -p /tmp/restrict-fsaccess-test +mount -t tmpfs tmpfs /tmp/restrict-fsaccess-test + +# Copy a binary to tmpfs. Basename must stay "true" for multicall coreutils +# binaries (uutils, busybox) — see the baseline comment above. +cp /usr/bin/true /tmp/restrict-fsaccess-test/true +chmod +x /tmp/restrict-fsaccess-test/true + +# This should fail with EPERM +if /tmp/restrict-fsaccess-test/true 2>/dev/null; then + echo "ERROR: Execution from tmpfs should have been blocked!" >&2 + exit 1 +fi +echo "Execution from tmpfs blocked: OK" + +# ------ Test: PROT_EXEC mmap from tmpfs is blocked (mmap_file hook) ------ + +# Write a test file on the tmpfs mount for mmap/mprotect tests +dd if=/dev/zero of=/tmp/restrict-fsaccess-test/testfile bs=4096 count=1 2>/dev/null + +# File-backed PROT_EXEC mmap should be denied. +# The helper exits 0 if mmap succeeds (bad), 1 if denied (good). +if "$HELPER" mmap-exec /tmp/restrict-fsaccess-test/testfile; then + echo "ERROR: PROT_EXEC mmap of tmpfs file should have been blocked!" >&2 + exit 1 +fi +echo "PROT_EXEC mmap from tmpfs blocked: OK" + +# Anonymous PROT_EXEC mmap should be denied (NULL file — mmap_file hook) +if "$HELPER" anon-mmap-exec; then + echo "ERROR: Anonymous PROT_EXEC mmap should have been blocked!" >&2 + exit 1 +fi +echo "Anonymous PROT_EXEC mmap blocked: OK" + +# ------ Test: mprotect adding PROT_EXEC is blocked (file_mprotect hook) ------ + +# mmap PROT_READ then mprotect to PROT_EXEC — the file_mprotect hook should deny this. +if "$HELPER" mprotect-exec /tmp/restrict-fsaccess-test/testfile; then + echo "ERROR: mprotect PROT_EXEC on tmpfs file should have been blocked!" >&2 + exit 1 +fi +echo "mprotect PROT_EXEC from tmpfs blocked: OK" + +# ------ Test: Execution from signed dm-verity device ------ +# Trust path: .platform keyring (SecureBoot DB auto-enrolled by mkosi, made +# available by 'firmware': 'auto' in the test's meson.build). + +MINIMAL=/usr/share/minimal_0 +if machine_supports_verity_keyring; then + systemd-run --pipe --wait \ + --property RootImage="$MINIMAL.raw" \ + bash --version >/dev/null + echo "Execution from signed dm-verity device: OK" +else + echo "Verity keyring trust not available, skipping positive verity test" +fi + +# ------ Test: Guard blocks non-PID1 from obtaining BPF object FDs by ID ------ + +if command -v bpftool >/dev/null 2>&1 && [[ -n "${VERITY_MAP_ID:-}" ]]; then + # bpftool uses BPF_MAP_GET_FD_BY_ID / BPF_PROG_GET_FD_BY_ID / + # BPF_LINK_GET_FD_BY_ID internally. The guard should block these for + # our protected IDs since we're not PID1. + + # -- Map ID guard -- + if bpftool map show id "$VERITY_MAP_ID" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access verity_devices map (ID $VERITY_MAP_ID)!" >&2 + exit 1 + fi + echo "Guard blocked verity_devices map access: OK (ID $VERITY_MAP_ID)" + + if [[ -n "${BSS_MAP_ID:-}" ]]; then + if bpftool map show id "$BSS_MAP_ID" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access .bss map (ID $BSS_MAP_ID)!" >&2 + exit 1 + fi + echo "Guard blocked .bss map access: OK (ID $BSS_MAP_ID)" + fi + + # -- Prog ID guard (defense-in-depth) -- + if [[ -n "${PROG_IDS:-}" ]]; then + IFS=',' read -ra prog_ids <<< "$PROG_IDS" + for prog_id in "${prog_ids[@]}"; do + if bpftool prog show id "$prog_id" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access protected prog (ID $prog_id)!" >&2 + exit 1 + fi + done + echo "Guard blocked prog access: OK (${#prog_ids[@]} IDs)" + fi + + # -- Link ID guard (defense-in-depth) -- + if [[ -n "${LINK_IDS:-}" ]]; then + IFS=',' read -ra link_ids <<< "$LINK_IDS" + for lid in "${link_ids[@]}"; do + if bpftool link show id "$lid" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access protected link (ID $lid)!" >&2 + exit 1 + fi + done + echo "Guard blocked link access: OK (${#link_ids[@]} IDs)" + fi + + # Verify the guard doesn't block unrelated BPF operations. + # bpftool prog list uses BPF_PROG_GET_NEXT_ID which the guard doesn't + # intercept (it only blocks *_GET_FD_BY_ID for specific IDs). + bpftool prog list >/dev/null 2>&1 || true + echo "Unrelated BPF operations still work: OK" +else + echo "bpftool not available or map IDs not captured, skipping guard test" +fi + +# ------ Test: ptrace attach to PID1 is blocked ------ + +# dd from /proc/1/mem uses PTRACE_MODE_ATTACH_FSCREDS via mm_access(). +# Read from a valid mapped address (not offset 0 which is the unmapped NULL +# page and would fail with -EIO even without the guard). +PID1_ADDR=$(awk '/r-xp/ { split($1, a, "-"); print a[1]; exit }' /proc/1/maps) +if [[ -n "$PID1_ADDR" ]]; then + PID1_OFFSET=$((16#$PID1_ADDR)) + if ! dd if=/proc/1/mem of=/dev/null bs=1 count=1 skip="$PID1_OFFSET" iflag=skip_bytes 2>/dev/null; then + echo "Ptrace ATTACH access to PID1 blocked: OK" + else + echo "ERROR: /proc/1/mem read should have been blocked!" >&2 + exit 1 + fi +else + echo "WARNING: Could not determine mapped address for PID1, skipping ptrace test" +fi + +# Verify READ-level access to PID1 still works (monitoring tools need this) +if cat /proc/1/status >/dev/null 2>&1; then + echo "Ptrace READ access to PID1 allowed: OK" +else + echo "ERROR: /proc/1/status should still be readable!" >&2 + exit 1 +fi + +# Verify ptrace to non-PID1 processes is unaffected +SLEEP_PID= +sleep 60 & +SLEEP_PID=$! +if cat /proc/$SLEEP_PID/status >/dev/null 2>&1; then + echo "Ptrace access to non-PID1 unaffected: OK" +else + echo "ERROR: /proc/$SLEEP_PID/status should be readable!" >&2 + kill "$SLEEP_PID" 2>/dev/null || true + exit 1 +fi +kill "$SLEEP_PID" 2>/dev/null || true +wait "$SLEEP_PID" 2>/dev/null || true +SLEEP_PID= + +# ------ Detach and verify enforcement is lifted ------ +# Kill the helper process. close() on the link FDs goes through +# bpf_link_put_direct() which synchronously detaches the trampoline. + +kill "$HELPER_PID" +wait "$HELPER_PID" 2>/dev/null || true +HELPER_PID= +echo "Helper killed, BPF programs detached synchronously" + +if [[ -x /tmp/restrict-fsaccess-test/true ]]; then + /tmp/restrict-fsaccess-test/true + echo "Execution from tmpfs after detach: OK" +fi + +umount /tmp/restrict-fsaccess-test 2>/dev/null || true +rm -rf /tmp/restrict-fsaccess-test + +echo "All enforcement tests passed" diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.sh b/test/units/TEST-90-RESTRICT-FSACCESS.sh new file mode 100755 index 0000000000000..9c2a033aa98d1 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok From 4684df8f9700ccb9b5f744e50fb565eaa25993c5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 8 May 2026 10:53:16 +0200 Subject: [PATCH 1659/2155] core: work around btf_ctx_access() rejection of const void * in BPF LSM Kernels before v6.16 (missing commit 1271a40eeafa "bpf: Allow access to const void pointer arguments in tracing programs") have a bug in btf_ctx_access() where const void * parameters in LSM hook signatures are not recognized as void pointers. The function checks t->type == 0 to detect void *, but for const void * the BTF chain is PTR -> CONST -> void, so t->type points to the CONST node rather than directly to type_id 0. This causes the verifier to reject any BPF program that reads the const void *value argument of bdev_setintegrity: func 'bpf_lsm_bdev_setintegrity' arg2 type UNKNOWN is not a struct invalid bpf_context access off=16 size=8 Work around this by providing a compat variant of the bdev_setintegrity BPF program that avoids reading the const void *value argument entirely. Instead it reads the size argument (a scalar integer) directly from the raw BPF context (ctx[3]), which is not subject to the broken type check. This is safe because dm-verity guarantees that value and size are always in lockstep: both NULL/0 for unsigned devices, both non-zero for signed devices. The loader tries the full version first (which reads both value and size for defense-in-depth) and falls back to the compat variant if loading fails. bpf_program__set_autoload(false) disables whichever variant is not needed so the verifier never sees it. This compat logic can be removed once the minimum kernel baseline includes the 1271a40eeafa fix. Signed-off-by: Christian Brauner --- src/bpf/restrict-fsaccess.bpf.c | 25 ++++++++++++++ src/core/bpf-restrict-fsaccess.c | 56 ++++++++++++++++++++++++++++++-- src/shared/bpf-dlopen.c | 2 ++ src/shared/bpf-dlopen.h | 1 + 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/bpf/restrict-fsaccess.bpf.c b/src/bpf/restrict-fsaccess.bpf.c index 538ddf3ef17b6..13e11aff500be 100644 --- a/src/bpf/restrict-fsaccess.bpf.c +++ b/src/bpf/restrict-fsaccess.bpf.c @@ -69,6 +69,13 @@ volatile __u32 protected_link_ids[NUM_PROTECTED_OBJS]; /* ---- Integrity tracking hooks ---- */ +/* Preferred version: reads both value and size for defense-in-depth. + * Requires kernel v6.16+ or the backport of 1271a40eeafa ("bpf: Allow + * access to const void pointer arguments in tracing programs"). + * On older kernels btf_ctx_access() rejects loads from const void * + * arguments because it fails to skip the CONST modifier when checking + * for void pointers. prepare_restrict_fsaccess_bpf() tries this version + * first and falls back to the _compat variant below if loading fails. */ SEC("lsm/bdev_setintegrity") int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev, enum lsm_integrity_type type, const void *value, __u64 size) @@ -82,6 +89,24 @@ int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev, return 0; } +/* Compatibility version for kernels without 1271a40eeafa: does not + * read the const void *value argument (ctx[2]) to avoid the verifier + * rejection. Reads size (ctx[3]) directly from the raw context instead. + * This is safe because dm-verity guarantees value!=NULL iff size>0. */ +#define BDEV_SETINTEGRITY_SIZE_CTX_IDX 3 /* bdev_setintegrity(bdev, type, value, size) */ +SEC("lsm/bdev_setintegrity") +int BPF_PROG(restrict_fsaccess_bdev_setintegrity_compat, struct block_device *bdev, + enum lsm_integrity_type type) +{ + if (type == LSM_INT_DMVERITY_SIG_VALID) { + __u32 dev = bdev->bd_dev; + __u8 valid = ctx[BDEV_SETINTEGRITY_SIZE_CTX_IDX] > 0; + bpf_map_update_elem(&verity_devices, &dev, &valid, BPF_ANY); + } + + return 0; +} + SEC("lsm/bdev_free_security") void BPF_PROG(restrict_fsaccess_bdev_free, struct block_device *bdev) { diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index a38665a6e20d6..be66f7da01c92 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -62,9 +62,13 @@ assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_verity) == assert_cc(offsetof(struct restrict_fsaccess_bss, protected_map_id_bss) == offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), protected_map_id_bss)); -/* Build the skeleton links array indexed by the link enum. */ +/* Build the skeleton links array indexed by the link enum. + * For BDEV_SETINTEGRITY, use whichever variant was loaded (full or compat). + * This compat logic can be removed once the kernel baseline includes + * 1271a40eeafa ("bpf: Allow access to const void pointer arguments"). */ #define RESTRICT_FSACCESS_LINKS(obj) { \ - [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity ?: \ + (obj)->links.restrict_fsaccess_bdev_setintegrity_compat, \ [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE] = (obj)->links.restrict_fsaccess_bdev_free, \ [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = (obj)->links.restrict_fsaccess_bprm_check, \ [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = (obj)->links.restrict_fsaccess_mmap_file, \ @@ -109,6 +113,11 @@ int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { assert(ret); + /* Try the preferred version first — it reads the const void *value + * argument for defense-in-depth. On kernels before v6.16 (missing + * 1271a40eeafa) the verifier rejects loads from const void * context + * arguments, so we fall back to the _compat variant that only reads + * the size argument via raw ctx access. */ obj = restrict_fsaccess_bpf__open(); if (!obj) return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to open BPF object: %m"); @@ -117,10 +126,37 @@ int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { if (r < 0) return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m"); + r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity_compat, false); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable compat program: %m"); + + r = restrict_fsaccess_bpf__load(obj); + if (r >= 0) { + log_debug("bpf-restrict-fsaccess: Loaded with full const void * access."); + *ret = TAKE_PTR(obj); + return 0; + } + + log_debug_errno(r, "bpf-restrict-fsaccess: Full version failed to load (%m), trying compat variant."); + obj = restrict_fsaccess_bpf_free(obj); + + obj = restrict_fsaccess_bpf__open(); + if (!obj) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to reopen BPF object: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.verity_devices, DMVERITY_DEVICES_MAX); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m"); + + r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity, false); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable full program: %m"); + r = restrict_fsaccess_bpf__load(obj); if (r < 0) - return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object: %m"); + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object (compat): %m"); + log_debug("bpf-restrict-fsaccess: Loaded with compat bdev_setintegrity."); *ret = TAKE_PTR(obj); return 0; } @@ -271,6 +307,13 @@ int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { FOREACH_ELEMENT(link, links) { size_t idx = link - links; + /* BDEV_SETINTEGRITY slot resolves via ?: between full and compat + * variants; assert at least one was attached. */ + if (!*link) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "bpf-restrict-fsaccess: %s link missing after attach.", + restrict_fsaccess_link_names[idx]); + r = bpf_get_link_ids(sym_bpf_link__fd(*link), &obj->bss->protected_link_ids[idx], &obj->bss->protected_prog_ids[idx]); @@ -412,6 +455,13 @@ int bpf_restrict_fsaccess_setup(Manager *m) { FOREACH_ELEMENT(link, links) { size_t idx = link - links; + if (!*link) { + r = log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "bpf-restrict-fsaccess: %s link missing after attach.", + restrict_fsaccess_link_names[idx]); + goto fail; + } + m->restrict_fsaccess_link_fds[idx] = fcntl(sym_bpf_link__fd(*link), F_DUPFD_CLOEXEC, 3); if (m->restrict_fsaccess_link_fds[idx] < 0) { r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup link FD for %s: %m", diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index c7d9dbdd5bfa6..ca746828233c7 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -50,6 +50,7 @@ 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(bpf_program__set_autoload) = NULL; DLSYM_PROTOTYPE(libbpf_set_print) = NULL; DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL; DLSYM_PROTOTYPE(ring_buffer__free) = NULL; @@ -174,6 +175,7 @@ int dlopen_bpf(int log_level) { DLSYM_ARG_FORCE(bpf_program__attach_lsm), #endif DLSYM_ARG(bpf_program__name), + DLSYM_ARG(bpf_program__set_autoload), DLSYM_ARG(libbpf_get_error), DLSYM_ARG(libbpf_set_print), DLSYM_ARG(ring_buffer__epoll_fd), diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index 71e6ca5d1d65a..62ea9cf707b84 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -39,6 +39,7 @@ extern DLSYM_PROTOTYPE(bpf_program__attach); extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup); extern DLSYM_PROTOTYPE(bpf_program__attach_lsm); extern DLSYM_PROTOTYPE(bpf_program__name); +extern DLSYM_PROTOTYPE(bpf_program__set_autoload); extern DLSYM_PROTOTYPE(libbpf_set_print); extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd); extern DLSYM_PROTOTYPE(ring_buffer__free); From 8b10063527ba99e2a049eac5764930cb1ddebe12 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 12 May 2026 16:04:44 +0200 Subject: [PATCH 1660/2155] ci: disable BPF framework in Jammy build tests Jammy's kernel is too old at this point, and doesn't even provide a vmlinux.h, so disable the feature in the build smoketests to let us add new features Co-developed-by: Luca Boccassi Signed-off-by: Christian Brauner --- .github/workflows/build-test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 506479a55845d..68242ac922a15 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -65,6 +65,7 @@ PACKAGES=( util-linux zstd ) +FEATURES=() COMPILER="${COMPILER:?}" COMPILER_VERSION="${COMPILER_VERSION:?}" LINKER="${LINKER:?}" @@ -133,6 +134,8 @@ sudo rm -f /etc/apt/sources.list.d/microsoft-prod.{list,sources} if grep -q 'VERSION_CODENAME=jammy' /usr/lib/os-release; then sudo add-apt-repository -y --no-update ppa:upstream-systemd-ci/systemd-ci sudo add-apt-repository -y --no-update --enable-source + # Jammy's kernel is too old and there's no vmlinux.h + FEATURES+=("-Dbpf-framework=disabled") else # add-apt-repository --enable-source does not work on deb822 style sources. for f in /etc/apt/sources.list.d/*.sources; do @@ -175,6 +178,7 @@ for args in "${ARGS[@]}"; do meson setup \ -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \ -Dnobody-group=nogroup -Ddebug=false \ + "${FEATURES[@]}" \ $args build; then cat build/meson-logs/meson-log.txt From 7cc5d161e659ceacf262fdad53d7c493292061e6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 09:41:01 +0200 Subject: [PATCH 1661/2155] test: Modernize btrfs tests Convert test-btrfs to use the test framework and assertions, merge the physical offset test into it and beef it up to include what TEST-83-BTRFS does and finally get rid of TEST-83-BTRFS as it is unneeded now. --- src/test/meson.build | 5 - src/test/test-btrfs-physical-offset.c | 35 -- src/test/test-btrfs.c | 340 ++++++++++-------- .../TEST-83-BTRFS/meson.build | 8 - test/integration-tests/meson.build | 1 - test/units/TEST-83-BTRFS.sh | 32 -- 6 files changed, 200 insertions(+), 221 deletions(-) delete mode 100644 src/test/test-btrfs-physical-offset.c delete mode 100644 test/integration-tests/TEST-83-BTRFS/meson.build delete mode 100755 test/units/TEST-83-BTRFS.sh diff --git a/src/test/meson.build b/src/test/meson.build index 966619c95f6d6..f13868d4e12dd 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -256,11 +256,6 @@ executables += [ }, test_template + { 'sources' : files('test-btrfs.c'), - 'type' : 'manual', - }, - test_template + { - 'sources' : files('test-btrfs-physical-offset.c'), - 'type' : 'manual', }, test_template + { 'sources' : files('test-chase-manual.c'), diff --git a/src/test/test-btrfs-physical-offset.c b/src/test/test-btrfs-physical-offset.c deleted file mode 100644 index e039a63b933bd..0000000000000 --- a/src/test/test-btrfs-physical-offset.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "btrfs-util.h" -#include "fd-util.h" -#include "log.h" -#include "memory-util.h" -#include "tests.h" - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -EBADF; - uint64_t offset; - int r; - - assert(argc == 2); - assert(!isempty(argv[1])); - - test_setup_logging(LOG_DEBUG); - - fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - return EXIT_FAILURE; - } - - r = btrfs_get_file_physical_offset_fd(fd, &offset); - if (r < 0) { - log_error_errno(r, "Failed to get physical offset of '%s': %m", argv[1]); - return EXIT_FAILURE; - } - - printf("%" PRIu64 "\n", offset / page_size()); - return EXIT_SUCCESS; -} diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index b3fcf8068ec46..66cb10a4c2534 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -1,202 +1,262 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include #include #include +#include "alloc-util.h" #include "btrfs-util.h" +#include "chattr-util.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" #include "log.h" +#include "memory-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "rm-rf.h" +#include "stat-util.h" #include "string-util.h" #include "tests.h" #include "time-util.h" +#include "tmpfile-util.h" -int main(int argc, char *argv[]) { - BtrfsQuotaInfo quota; - int r, fd; +static int open_test_subvol(char **ret_path) { + const char *vtd; + int r; - test_setup_logging(LOG_DEBUG); + r = var_tmp_dir(&vtd); + if (r < 0) + return r; - fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - log_error_errno(errno, "Failed to open root directory: %m"); - else { - BtrfsSubvolInfo info; - - r = btrfs_subvol_get_info_fd(fd, 0, &info); - if (r < 0) - log_error_errno(r, "Failed to get subvolume info: %m"); - else { - log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); - log_info("read-only (search): %s", yes_no(info.read_only)); - } - - r = btrfs_qgroup_get_quota_fd(fd, 0, "a); - if (r < 0) - log_error_errno(r, "Failed to get quota info: %m"); - else { - log_info("referenced: %s", strna(FORMAT_BYTES(quota.referenced))); - log_info("exclusive: %s", strna(FORMAT_BYTES(quota.exclusive))); - log_info("referenced_max: %s", strna(FORMAT_BYTES(quota.referenced_max))); - log_info("exclusive_max: %s", strna(FORMAT_BYTES(quota.exclusive_max))); - } - - r = btrfs_subvol_get_read_only_fd(fd); - if (r < 0) - log_error_errno(r, "Failed to get read only flag: %m"); - else - log_info("read-only (ioctl): %s", yes_no(r)); - - safe_close(fd); - } + _cleanup_free_ char *base = path_join(vtd, "test-btrfs"), *p = NULL; + if (!base) + return -ENOMEM; - r = btrfs_subvol_make(AT_FDCWD, "/xxxtest"); + r = tempfn_random(base, /* extra= */ NULL, &p); if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + return r; - r = write_string_file("/xxxtest/file", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE); - if (r < 0) - log_error_errno(r, "Failed to write file: %m"); + int fd = xopenat_full(AT_FDCWD, p, O_DIRECTORY|O_CREAT|O_CLOEXEC, XO_SUBVOLUME, MODE_INVALID); + if (fd < 0) + return fd; - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest2", 0); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + if (ret_path) + *ret_path = TAKE_PTR(p); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest3", BTRFS_SNAPSHOT_READ_ONLY); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + return fd; +} - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest4", BTRFS_SNAPSHOT_LOCK_BSD); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); - if (r >= 0) - assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); +TEST(info) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + BtrfsSubvolInfo info; + BtrfsQuotaInfo quota; + int r; - safe_close(r); + ASSERT_OK(btrfs_subvol_get_info_fd(dir_fd, 0, &info)); + log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); + log_info("read-only (search): %s", yes_no(info.read_only)); - r = btrfs_subvol_remove("/xxxtest", BTRFS_REMOVE_QUOTA); + r = btrfs_qgroup_get_quota_fd(dir_fd, 0, "a); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + log_info_errno(r, "Failed to get quota info: %m"); + else { + log_info("referenced: %s", strna(FORMAT_BYTES(quota.referenced))); + log_info("exclusive: %s", strna(FORMAT_BYTES(quota.exclusive))); + log_info("referenced_max: %s", strna(FORMAT_BYTES(quota.referenced_max))); + log_info("exclusive_max: %s", strna(FORMAT_BYTES(quota.exclusive_max))); + } - r = btrfs_subvol_remove("/xxxtest2", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + r = ASSERT_OK(btrfs_subvol_get_read_only_fd(dir_fd)); + log_info("read-only (ioctl): %s", yes_no(r)); +} - r = btrfs_subvol_remove("/xxxtest3", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); +TEST(subvol) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - r = btrfs_subvol_remove("/xxxtest4", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "test1")); + ASSERT_OK(write_string_file_at(dir_fd, "test1/file", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE)); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/etc", AT_FDCWD, "/etc2", - BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test2", 0)); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test3", BTRFS_SNAPSHOT_READ_ONLY)); - r = btrfs_subvol_remove("/etc2", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + _unused_ _cleanup_close_ int locked_fd = ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test4", BTRFS_SNAPSHOT_LOCK_BSD)); + ASSERT_ERROR(xopenat_lock(dir_fd, "test4", 0, LOCK_BSD, LOCK_EX|LOCK_NB), EAGAIN); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + /* The destroy ioctl needs CAP_SYS_ADMIN; without it, leave cleanup to rm_rf_subvolume_and_freep. */ + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test1", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test2", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test3", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test4", BTRFS_REMOVE_QUOTA), -EPERM); +} - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest2"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); +TEST(fallback_copy) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + /* Snapshot a regular directory (not a subvolume) — exercises the FALLBACK_COPY path. */ + ASSERT_OK_ERRNO(mkdirat(dir_fd, "src", 0755)); + ASSERT_OK(write_string_file_at(dir_fd, "src/file1", "hello", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(write_string_file_at(dir_fd, "src/file2", "world", WRITE_STRING_FILE_CREATE)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3/sub"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "src", dir_fd, "snap", + BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY)); - if (mkdir("/xxxrectest/dir", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "snap", BTRFS_REMOVE_QUOTA), -EPERM); +} - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); +TEST(recursive) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - if (mkdir("/xxxrectest/dir/xxxrectest4/dir", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv2")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv3")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv3/sub")); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4/dir/xxxrectest5"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/dir", 0755)); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/dir/sv4")); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/dir/sv4/dir", 0755)); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/dir/sv4/dir/sv5")); - if (mkdir("/xxxrectest/mnt", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/mnt", 0755)); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxrectest", AT_FDCWD, "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to snapshot subvolume: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "rec", dir_fd, "rec-snap", BTRFS_SNAPSHOT_RECURSIVE)); - r = btrfs_subvol_remove("/xxxrectest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to recursively remove subvolume: %m"); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "rec", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "rec-snap", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); +} - r = btrfs_subvol_remove("/xxxrectest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to recursively remove subvolume: %m"); +TEST(quota) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + BtrfsQuotaInfo quota; + int r; - r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + _cleanup_free_ char *qt = ASSERT_NOT_NULL(path_join(dir, "quotatest")), + *qt2 = ASSERT_NOT_NULL(path_join(dir, "quotatest2")), + *beneath = ASSERT_NOT_NULL(path_join(dir, "quotatest/beneath")), + *snap_beneath = ASSERT_NOT_NULL(path_join(dir, "quotatest2/beneath")); - r = btrfs_subvol_auto_qgroup("/xxxquotatest", 0, true); - if (r < 0) - log_error_errno(r, "Failed to set up auto qgroup: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "quotatest")); + /* The qgroup/quota ioctls require CAP_SYS_ADMIN; skip the rest of the test if we don't have it + * or quotas are not enabled on this filesystem. */ + r = btrfs_subvol_auto_qgroup(qt, 0, true); + if (r == -EPERM) + return (void) log_tests_skipped("not running privileged"); + if (IN_SET(r, -ENOTCONN, -ENOENT)) + return (void) log_tests_skipped_errno(r, "btrfs quotas not enabled on this filesystem"); + ASSERT_OK(r); - r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest/beneath"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "quotatest/beneath")); + ASSERT_OK(btrfs_subvol_auto_qgroup(beneath, 0, false)); + ASSERT_OK(btrfs_qgroup_set_limit(beneath, 0, 4ULL * 1024 * 1024 * 1024)); - r = btrfs_subvol_auto_qgroup("/xxxquotatest/beneath", 0, false); - if (r < 0) - log_error_errno(r, "Failed to set up auto qgroup: %m"); + ASSERT_OK(btrfs_subvol_set_subtree_quota_limit(qt, 0, 5ULL * 1024 * 1024 * 1024)); - r = btrfs_qgroup_set_limit("/xxxquotatest/beneath", 0, 4ULL * 1024 * 1024 * 1024); - if (r < 0) - log_error_errno(r, "Failed to set up quota limit: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "quotatest", dir_fd, "quotatest2", + BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA)); - r = btrfs_subvol_set_subtree_quota_limit("/xxxquotatest", 0, 5ULL * 1024 * 1024 * 1024); - if (r < 0) - log_error_errno(r, "Failed to set up quota limit: %m"); + ASSERT_OK(btrfs_qgroup_get_quota(snap_beneath, 0, "a)); + ASSERT_EQ(quota.referenced_max, 4ULL * 1024 * 1024 * 1024); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxquotatest", AT_FDCWD, "/xxxquotatest2", - BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to set up snapshot: %m"); + ASSERT_OK(btrfs_subvol_get_subtree_quota(qt2, 0, "a)); + ASSERT_EQ(quota.referenced_max, 5ULL * 1024 * 1024 * 1024); + + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "quotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "quotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); +} - r = btrfs_qgroup_get_quota("/xxxquotatest2/beneath", 0, "a); +TEST(physical_offset) { + _cleanup_free_ char *btrfs_progs = NULL; + int r = find_executable("btrfs", &btrfs_progs); if (r < 0) - log_error_errno(r, "Failed to query quota: %m"); + return (void) log_tests_skipped_errno(r, "btrfs(8) not found"); + + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + + /* Set NOCOW on the subvol dir so the swapfile inherits it on creation. Older btrfs-progs + * versions don't reliably set NOCOW from `btrfs filesystem mkswapfile`. */ + ASSERT_OK(chattr_fd(dir_fd, FS_NOCOW_FL, FS_NOCOW_FL)); + + /* btrfs filesystem mkswapfile produces a NOCOW, contiguous file with a swap header — exactly + * what btrfs inspect-internal map-swapfile expects, and what btrfs_get_file_physical_offset_fd + * works with. */ + _cleanup_free_ char *path = ASSERT_NOT_NULL(path_join(dir, "swapfile")); + r = ASSERT_OK(pidref_safe_fork("(mkswapfile)", + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, + NULL)); + if (r == 0) { + execlp(btrfs_progs, "btrfs", "filesystem", "mkswapfile", "-s", "1m", path, NULL); + _exit(EXIT_FAILURE); + } - if (r >= 0) - assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); + _cleanup_close_ int fd = ASSERT_OK_ERRNO(openat(dir_fd, "swapfile", O_RDONLY|O_CLOEXEC|O_NOCTTY)); + + unsigned attrs; + ASSERT_OK(read_attr_fd(fd, &attrs)); + if (!FLAGS_SET(attrs, FS_NOCOW_FL) || FLAGS_SET(attrs, FS_COMPR_FL)) + return (void) log_tests_skipped("swapfile is not NOCOW/non-compressed (old btrfs-progs?)"); + + /* btrfs_get_file_physical_offset_fd() uses BTRFS_IOC_TREE_SEARCH, which needs CAP_SYS_ADMIN. */ + uint64_t offset; + r = btrfs_get_file_physical_offset_fd(fd, &offset); + if (r == -EPERM) + return (void) log_tests_skipped("not running privileged"); + ASSERT_OK(r); + + /* Cross-check against `btrfs inspect-internal map-swapfile -r`, which prints the first + * physical address in page units. */ + _cleanup_close_pair_ int pipe_fds[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipe_fds, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef inspect = PIDREF_NULL; + r = pidref_safe_fork_full("(btrfs-inspect)", + (int[3]) { -EBADF, pipe_fds[1], STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_REARRANGE_STDIO, + &inspect); + ASSERT_OK(r); + if (r == 0) { + execlp(btrfs_progs, "btrfs", "inspect-internal", "map-swapfile", "-r", path, NULL); + _exit(EXIT_FAILURE); + } + pipe_fds[1] = safe_close(pipe_fds[1]); - r = btrfs_subvol_get_subtree_quota("/xxxquotatest2", 0, "a); - if (r < 0) - log_error_errno(r, "Failed to query quota: %m"); + _cleanup_fclose_ FILE *f = ASSERT_NOT_NULL(take_fdopen(&pipe_fds[0], "r")); + _cleanup_free_ char *out = NULL; + ASSERT_OK(read_full_stream(f, &out, /* ret_size= */ NULL)); + ASSERT_OK_EQ(pidref_wait_for_terminate_and_check("(btrfs-inspect)", &inspect, 0), 0); - if (r >= 0) - assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); + uint64_t expected; + ASSERT_OK(safe_atou64(strstrip(out), &expected)); + ASSERT_EQ(offset / page_size(), expected); + log_info("physical offset: page %" PRIu64, expected); +} + +static int intro(void) { + const char *vtd; + int r; - r = btrfs_subvol_remove("/xxxquotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + r = var_tmp_dir(&vtd); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + return log_tests_skipped_errno(r, "Failed to resolve /var/tmp"); - r = btrfs_subvol_remove("/xxxquotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + r = path_is_fs_type(vtd, BTRFS_SUPER_MAGIC); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + return log_tests_skipped_errno(r, "Failed to determine filesystem type of %s", vtd); + if (r == 0) + return log_tests_skipped("%s is not on btrfs", vtd); - return 0; + return EXIT_SUCCESS; } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/test/integration-tests/TEST-83-BTRFS/meson.build b/test/integration-tests/TEST-83-BTRFS/meson.build deleted file mode 100644 index 77370ce4588c4..0000000000000 --- a/test/integration-tests/TEST-83-BTRFS/meson.build +++ /dev/null @@ -1,8 +0,0 @@ -# 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..37ce3915984e2 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -96,7 +96,6 @@ foreach dirname : [ 'TEST-80-NOTIFYACCESS', 'TEST-81-GENERATORS', 'TEST-82-SOFTREBOOT', - 'TEST-83-BTRFS', 'TEST-84-STORAGETM', 'TEST-85-NETWORK', 'TEST-86-MULTI-PROFILE-UKI', diff --git a/test/units/TEST-83-BTRFS.sh b/test/units/TEST-83-BTRFS.sh deleted file mode 100755 index 4f10ae74682db..0000000000000 --- a/test/units/TEST-83-BTRFS.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later -set -eux -set -o pipefail - -FSTYPE="$(stat --file-system --format "%T" /)" - -if [[ "$FSTYPE" != "btrfs" ]]; then - echo "Root filesystem is $FSTYPE instead of btrfs, skipping" - exit 77 -fi - -TEST_BTRFS_OFFSET=/usr/lib/systemd/tests/unit-tests/manual/test-btrfs-physical-offset - -SWAPFILE=/var/tmp/swapfile - -btrfs filesystem mkswapfile -s 10m "$SWAPFILE" -sync -f "$SWAPFILE" - -offset_btrfs_progs="$(btrfs inspect-internal map-swapfile -r "$SWAPFILE")" -echo "btrfs-progs: $offset_btrfs_progs" - -offset_btrfs_util="$("$TEST_BTRFS_OFFSET" "$SWAPFILE")" -echo "btrfs-util: $offset_btrfs_util" - -(( offset_btrfs_progs == offset_btrfs_util )) - -rm -f "$SWAPFILE" - -/usr/lib/systemd/tests/unit-tests/manual/test-btrfs - -touch /testok From 0a9bbdcd33de71fc30036a23533d4e92d5e29c4c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 14:12:37 +0200 Subject: [PATCH 1662/2155] update TODO --- TODO.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/TODO.md b/TODO.md index 30cc5d02dcfba..fe13b0b24dff4 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,33 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- sysupdate: offer reading transfer files/components/features optionally from + some JSON fragment rather than transfer files, so that we can update it + independently from any DDI, and it needs no activation cycle. Why? so that + making additional transfers/components/features available can be done without + reloading confext/sysext, and out-band with other configuratoin changes. + +- sysupdate: go through all components, and update them all, one by one. + +- sysupdate: add concept for enabling/disabling specific components explicitly, + just like features. + +- machine-info: add a TAGS concept that can be used to categorize a machine + +- udev: add a MACHINE_TAGS field, that augments /etc/machine-info configured + tags. + +- hostnamectl: management, collation of all tags. four sources: udev, + /etc/machine-info, credentials, and /etc/machine-tags.d/*.conf + +- sysupdate: add conditions to transfer files, copying what we have for unit + files and .network files + +- pid1,sysupdate,network: add support for a new "tags" condition, that checks + all of the above. + +- sysupdate: write out database of all files created, and support gc of it + - pcrextend: we probably should measure /etc/machine-info during boot somehow - pcrextend: we should measure something when we enter developer mode, by some From ff32dd2474bf970a5c0762020d040bd76e74e89c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 15:25:41 +0200 Subject: [PATCH 1663/2155] preset: enable cgroup metrics logic This stuff is so useful, and should work out of the box I am sure. Given that the metrics are only generated on request this shouldn't create any additional burden by default. Yes, this might enlarge reports a bit, if generated with everything on, but we really should solve that at the report generation level, not at the point where we make the metrics available. Follow-up for: 4409e52494d803426a365b6636a66fd2dfc70b62 --- presets/90-systemd.preset | 1 + 1 file changed, 1 insertion(+) diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index 4a668fb8ce93b..1dd89f3df90f7 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -32,6 +32,7 @@ enable systemd-networkd-wait-online.service enable systemd-nsresourced.socket enable systemd-pstore.service enable systemd-report-basic.socket +enable systemd-report-cgroup.socket enable systemd-resolved.service enable systemd-sysext.service enable systemd-timesyncd.service From 78577ecd006c105205f443ca102314a8a9029522 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 15:57:24 +0200 Subject: [PATCH 1664/2155] update TODO --- TODO.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TODO.md b/TODO.md index fe13b0b24dff4..422145e25b72f 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,14 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- report: add filtering by metric prefix, so that we reports don't have to + include everything, and we have a way to have "small" and "big" reports, that + have different number of fields. + +- allow metrics to indicate which values mean + "nothing"/"invalid"/"zero"/"please-suppress". Then use that to reduce noise + in systemd-report output. + - sysupdate: offer reading transfer files/components/features optionally from some JSON fragment rather than transfer files, so that we can update it independently from any DDI, and it needs no activation cycle. Why? so that From 3061ec2707c626ebdee0de03b130bca79727d70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 09:52:19 +0200 Subject: [PATCH 1665/2155] meson: move systemd-sysupdate to /usr/bin/ Let's make systemd-sysupdate easy to call. It was added in 2021 and it's around to stay and not "experimental" in any way. --- src/sysupdate/meson.build | 9 ++++++++- test/units/TEST-72-SYSUPDATE.sh | 2 +- units/meson.build | 4 ++-- ...eboot.service.in => systemd-sysupdate-reboot.service} | 2 +- ...md-sysupdate.service.in => systemd-sysupdate.service} | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) rename units/{systemd-sysupdate-reboot.service.in => systemd-sysupdate-reboot.service} (91%) rename units/{systemd-sysupdate.service.in => systemd-sysupdate.service} (95%) diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 3a1ee1a048c62..0cfe22f5283dd 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -21,7 +21,7 @@ systemd_updatectl_sources = files( ) executables += [ - libexec_template + { + executable_template + { 'name' : 'systemd-sysupdate', 'public' : true, 'conditions' : ['ENABLE_SYSUPDATE'], @@ -57,3 +57,10 @@ if conf.get('ENABLE_SYSUPDATED') == 1 install_data('org.freedesktop.sysupdate1.policy', install_dir : polkitpolicydir) endif + +if conf.get('ENABLE_SYSUPDATE') == 1 + # symlink for backwards compatibility after rename + install_symlink('systemd-sysupdate', + pointing_to : libexecdir_to_bin / 'systemd-sysupdate', + install_dir : libexecdir) +endif diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 6709cd543f926..0e9bf78646e46 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -5,7 +5,7 @@ set -eux set -o pipefail -SYSUPDATE=/lib/systemd/systemd-sysupdate +SYSUPDATE=/usr/bin/systemd-sysupdate SYSUPDATED=/lib/systemd/systemd-sysupdated SECTOR_SIZES=(512 4096) WORKDIR="$(mktemp -d /var/tmp/test-72-XXXXXX)" diff --git a/units/meson.build b/units/meson.build index fca299aa8465a..c78835aa70e31 100644 --- a/units/meson.build +++ b/units/meson.build @@ -785,7 +785,7 @@ units = [ 'symlinks' : ['system-install.target.wants/'], }, { - 'file' : 'systemd-sysupdate-reboot.service.in', + 'file' : 'systemd-sysupdate-reboot.service', 'conditions' : ['ENABLE_SYSUPDATE'], }, { @@ -793,7 +793,7 @@ units = [ 'conditions' : ['ENABLE_SYSUPDATE'], }, { - 'file' : 'systemd-sysupdate.service.in', + 'file' : 'systemd-sysupdate.service', 'conditions' : ['ENABLE_SYSUPDATE'], }, { diff --git a/units/systemd-sysupdate-reboot.service.in b/units/systemd-sysupdate-reboot.service similarity index 91% rename from units/systemd-sysupdate-reboot.service.in rename to units/systemd-sysupdate-reboot.service index 5d4011a21327f..6750fc431ef7e 100644 --- a/units/systemd-sysupdate-reboot.service.in +++ b/units/systemd-sysupdate-reboot.service @@ -14,7 +14,7 @@ ConditionVirtualization=!container [Service] Type=oneshot -ExecStart={{LIBEXECDIR}}/systemd-sysupdate reboot +ExecStart=systemd-sysupdate reboot [Install] Also=systemd-sysupdate-reboot.timer diff --git a/units/systemd-sysupdate.service.in b/units/systemd-sysupdate.service similarity index 95% rename from units/systemd-sysupdate.service.in rename to units/systemd-sysupdate.service index 1becbec5edeb4..fc4d74a583f08 100644 --- a/units/systemd-sysupdate.service.in +++ b/units/systemd-sysupdate.service @@ -17,7 +17,7 @@ ConditionVirtualization=!container [Service] Type=simple NotifyAccess=main -ExecStart={{LIBEXECDIR}}/systemd-sysupdate update +ExecStart=systemd-sysupdate update CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE NoNewPrivileges=yes MemoryDenyWriteExecute=yes From 94e05aca0d1f8f37a4656947f6bf0aa77594364c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 12 May 2026 16:13:36 +0200 Subject: [PATCH 1666/2155] copy: retire splice use() for copying files on disk Apparently splice() is quite problematic, hence just don't anymore. It's also unnecessary these days since either copy_file_range() or sendfile() nowadays typically work, the splice() fallback doesn't give us much anymore. (At least I am not aware of a combo of fds where splice() would work where neither cfr nor sf would work). This leaves one use of splice() in place, in src/shared/socket-forward.c. We should probably kill that too, but that'd require some reworking to use sendfile() I guess, and I am too lazy for that right now. Moreover, in contrast to the other uses it's probably even safe, since it uses an intermediary pipe always. But what do I know... Fixes: #29044 --- src/import/export-tar.c | 22 --------- src/shared/copy.c | 102 +++------------------------------------- 2 files changed, 7 insertions(+), 117 deletions(-) diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 93d163adda63d..1e01da0c94faf 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -59,7 +59,6 @@ typedef struct TarExport { RateLimit progress_ratelimit; bool eof; - bool tried_splice; } TarExport; TarExport *tar_export_unref(TarExport *e) { @@ -189,27 +188,6 @@ static int tar_export_process(TarExport *e) { assert(e); - if (!e->tried_splice && compressor_type(e->compress) == COMPRESSION_NONE) { - - l = splice(e->tar_fd, NULL, e->output_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE, 0); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - e->tried_splice = true; - } else if (l == 0) { - r = tar_export_finish(e); - goto finish; - } else { - e->written_uncompressed += l; - e->written_compressed += l; - - tar_export_report_progress(e); - - return 0; - } - } - while (e->buffer_size <= 0) { uint8_t input[COMPRESS_PIPE_BUFFER_SIZE]; diff --git a/src/shared/copy.c b/src/shared/copy.c index d5788c7d77fb5..5b68bb9a5ab97 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -71,31 +71,6 @@ static ssize_t try_copy_file_range( return r; } -enum { - FD_IS_NO_PIPE, - FD_IS_BLOCKING_PIPE, - FD_IS_NONBLOCKING_PIPE, -}; - -static int fd_is_nonblock_pipe(int fd) { - struct stat st; - int flags; - - /* Checks whether the specified file descriptor refers to a pipe, and if so if O_NONBLOCK is set. */ - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISFIFO(st.st_mode)) - return FD_IS_NO_PIPE; - - flags = fcntl(fd, F_GETFL); - if (flags < 0) - return -errno; - - return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE; -} - static int look_for_signals(CopyFlags copy_flags) { int r; @@ -164,9 +139,9 @@ int copy_bytes_full( void *userdata) { _cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF; - bool try_cfr = true, try_sendfile = true, try_splice = true; + bool try_cfr = true, try_sendfile = true; uint64_t copied_total = 0; - int r, nonblock_pipe = -1; + int r; assert(fdf >= 0); assert(fdt >= 0); @@ -366,7 +341,7 @@ int copy_bytes_full( } } - /* First try copy_file_range(), unless we already tried */ + /* First, try copy_file_range(), unless we already tried */ if (try_cfr) { n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); if (n < 0) { @@ -387,13 +362,13 @@ int copy_bytes_full( * back to simple read()s in case we encounter empty files. * * See: https://lwn.net/Articles/846403/ */ - try_cfr = try_sendfile = try_splice = false; + try_cfr = try_sendfile = false; } else /* Success! */ goto next; } - /* First try sendfile(), unless we already tried */ + /* Second, try sendfile(), unless we already tried */ if (try_sendfile) { n = sendfile(fdt, fdf, NULL, m); if (n < 0) { @@ -407,76 +382,13 @@ int copy_bytes_full( if (copied_total > 0) break; - try_sendfile = try_splice = false; /* same logic as above for copy_file_range() */ - } else - /* Success! */ - goto next; - } - - /* Then try splice, unless we already tried. */ - if (try_splice) { - - /* splice()'s asynchronous I/O support is a bit weird. When it encounters a pipe file - * descriptor, then it will ignore its O_NONBLOCK flag and instead only honour the - * SPLICE_F_NONBLOCK flag specified in its flag parameter. Let's hide this behaviour - * here, and check if either of the specified fds are a pipe, and if so, let's pass - * the flag automatically, depending on O_NONBLOCK being set. - * - * Here's a twist though: when we use it to move data between two pipes of which one - * has O_NONBLOCK set and the other has not, then we have no individual control over - * O_NONBLOCK behaviour. Hence in that case we can't use splice() and still guarantee - * systematic O_NONBLOCK behaviour, hence don't. */ - - if (nonblock_pipe < 0) { - int a, b; - - /* Check if either of these fds is a pipe, and if so non-blocking or not */ - a = fd_is_nonblock_pipe(fdf); - if (a < 0) - return a; - - b = fd_is_nonblock_pipe(fdt); - if (b < 0) - return b; - - if ((a == FD_IS_NO_PIPE && b == FD_IS_NO_PIPE) || - (a == FD_IS_BLOCKING_PIPE && b == FD_IS_NONBLOCKING_PIPE) || - (a == FD_IS_NONBLOCKING_PIPE && b == FD_IS_BLOCKING_PIPE)) - - /* splice() only works if one of the fds is a pipe. If neither is, - * let's skip this step right-away. As mentioned above, if one of the - * two fds refers to a blocking pipe and the other to a non-blocking - * pipe, we can't use splice() either, hence don't try either. This - * hence means we can only use splice() if either only one of the two - * fds is a pipe, or if both are pipes with the same nonblocking flag - * setting. */ - - try_splice = false; - else - nonblock_pipe = a == FD_IS_NONBLOCKING_PIPE || b == FD_IS_NONBLOCKING_PIPE; - } - } - - if (try_splice) { - n = splice(fdf, NULL, fdt, NULL, m, nonblock_pipe ? SPLICE_F_NONBLOCK : 0); - if (n < 0) { - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - try_splice = false; - /* use fallback below */ - } else if (n == 0) { /* likely EOF */ - - if (copied_total > 0) - break; - - try_splice = false; /* same logic as above for copy_file_range() + sendfile() */ + try_sendfile = false; /* same logic as above for copy_file_range() */ } else /* Success! */ goto next; } - /* As a fallback just copy bits by hand */ + /* Third, as a fallback just copy bits by hand */ { uint8_t buf[MIN(m, COPY_BUFFER_SIZE)], *p = buf; ssize_t z; From 74d742fbfcb99a4121a7fd025aa6aca8b9cd6e0d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 13:35:21 +0000 Subject: [PATCH 1667/2155] TEST-64-UDEV-STORAGE: Drop number of nvme devices to 12 qemu by default only has 30 PCI slots and with vmspawn now reserving some of those for its hotplug features, we go over the limit for the nvme test. Let's drop the number of nvme devices to 12 to fix the conflict. --- .../integration-tests/TEST-64-UDEV-STORAGE/meson.build | 10 +++++----- test/units/TEST-64-UDEV-STORAGE.sh | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build index 3df914fd4d98c..a1c28f5e6ae60 100644 --- a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build +++ b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build @@ -176,23 +176,23 @@ udev_storage_tests += udev_storage_test_template + { cmdline = [] qemu_args = [] -foreach i : range(20) +foreach i : range(12) cmdline += [f'--drive=nvme@i@:1M::'] endforeach -foreach i : range(5) +foreach i : range(3) qemu_args += ['-device', f'nvme,drive=nvme@i@,serial=deadbeef@i@,max_ioqpairs=8'] endforeach -foreach i : range(5, 10) +foreach i : range(3, 6) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial= deadbeef @i@ ,max_ioqpairs=8"'] endforeach -foreach i : range(10, 15) +foreach i : range(6, 9) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial= dead/beef/@i@ ,max_ioqpairs=8"'] endforeach -foreach i : range(15, 20) +foreach i : range(9, 12) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial=dead/../../beef/@i@,max_ioqpairs=8"'] endforeach diff --git a/test/units/TEST-64-UDEV-STORAGE.sh b/test/units/TEST-64-UDEV-STORAGE.sh index e175f6c3d7848..85cd26d18f2a1 100755 --- a/test/units/TEST-64-UDEV-STORAGE.sh +++ b/test/units/TEST-64-UDEV-STORAGE.sh @@ -178,7 +178,7 @@ testcase_nvme_basic() { local expected_symlinks=() local i - for i in {0..4}; do + for i in {0..2}; do expected_symlinks+=( # both replace mode provides the same devlink /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i" @@ -186,7 +186,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1 ) done - for i in {5..9}; do + for i in {3..5}; do expected_symlinks+=( # old replace mode /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i" @@ -196,7 +196,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1 ) done - for i in {10..14}; do + for i in {6..8}; do expected_symlinks+=( # old replace mode does not provide devlink, as serial contains "/" # newer replace mode @@ -205,7 +205,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1 ) done - for i in {15..19}; do + for i in {9..11}; do expected_symlinks+=( # old replace mode does not provide devlink, as serial contains "/" # newer replace mode @@ -222,7 +222,7 @@ testcase_nvme_basic() { test ! -e /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef lsblk --noheadings | grep "^nvme" - [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]] + [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 12 ]] } testcase_nvme_subsystem() { From eb435b832669a14d745ce587cfe0227b87cad7be Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 15:27:54 +0200 Subject: [PATCH 1668/2155] core: tweak pattern of applying varlink properties for StartTransient So far we had the pattern to check first if any property needs setting and then have a function to set it. The downside is that when we add a new property two different places need to change (once the `if` in vl_method_start_transient_unit() and once in the specific helper to do the actual setting). So instead this commit moves everything to the helpers and tweaks the code so that we can always call the function. --- src/core/varlink-unit.c | 42 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2e3b1dd1961b8..85139f068dcb6 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -703,6 +703,7 @@ static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_typ static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string); typedef struct TransientServiceParameters { + bool present; ServiceType type; TransientExecCommandItem *exec_start; size_t n_exec_start; @@ -782,6 +783,7 @@ static int dispatch_transient_service(const char *name, sd_json_variant *variant }; StartTransientContextParameters *p = ASSERT_PTR(userdata); + p->service.present = true; return sd_json_dispatch(variant, service_dispatch, /* flags= */ 0, &p->service); } @@ -807,10 +809,26 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant return r; } -static int transient_service_apply_properties(Unit *u, TransientServiceParameters *sp) { +static int transient_unit_apply_properties(Unit *u, StartTransientContextParameters *p) { + int r; + + assert(u); + assert(p); + + if (p->description) { + r = unit_set_description(u, p->description); + if (r < 0) + return r; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p->description); + } + + return 0; +} + +static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp) { + Unit *u = UNIT(ASSERT_PTR(s)); int r; - Service *s = ASSERT_PTR(SERVICE(u)); assert(sp); if (sp->type >= 0) { @@ -956,22 +974,18 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters return varlink_reply_bus_error(link, r, &bus_error); /* Apply unit-level properties from context */ - if (p.context.description) { - r = unit_set_description(u, p.context.description); - if (r < 0) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); - unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p.context.description); - } + r = transient_unit_apply_properties(u, &p.context); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); /* Apply service-specific properties from context.Service */ - if (p.context.service.type >= 0 || p.context.service.n_exec_start > 0 || p.context.service.remain_after_exit >= 0) { - if (t != UNIT_SERVICE) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); - - r = transient_service_apply_properties(u, &p.context.service); + Service *s = SERVICE(u); + if (s) { + r = transient_service_apply_properties(s, &p.context.service); if (r < 0) return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); - } + } else if (p.context.service.present) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); unit_add_to_load_queue(u); manager_dispatch_load_queue(manager); From 6e8e0ba26d4666963d527ae69e5f06146db5b450 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 16:52:15 +0200 Subject: [PATCH 1669/2155] varlink: make fields of ExecContext nullable for partial input We want to allow partial inputs for the ExecContext when we pass that to io.systemd.Unit.StartTransient. So this commit makes the fields nullable. Without this the varlink input validation will reject partial ExecContext objects. This is similar to 913b81735e for UnitContext. --- src/shared/varlink-io.systemd.Unit.c | 92 ++++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 883154b2f1951..e62fe40a628e8 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -600,7 +600,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImageOptions="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RootImageOptions, PartitionMountOptions, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootEphemeral="), - SD_VARLINK_DEFINE_FIELD(RootEphemeral, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RootEphemeral, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootHash="), SD_VARLINK_DEFINE_FIELD(RootHash, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootHash="), @@ -612,19 +612,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootVerity="), SD_VARLINK_DEFINE_FIELD(RootVerity, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(RootImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(RootImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(MountImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(MountImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(ExtensionImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ExtensionImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MountAPIVFS="), - SD_VARLINK_DEFINE_FIELD(MountAPIVFS, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(MountAPIVFS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(BindLogSockets, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectProc="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectProc, ProtectProc, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectProc, ProtectProc, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProcSubset="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProcSubset, ProcSubset, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProcSubset, ProcSubset, SD_VARLINK_NULLABLE), 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="), @@ -643,7 +643,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#User="), SD_VARLINK_DEFINE_FIELD(Group, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#DynamicUser="), - SD_VARLINK_DEFINE_FIELD(DynamicUser, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DynamicUser, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SupplementaryGroups="), SD_VARLINK_DEFINE_FIELD(SupplementaryGroups, 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#SetLoginEnvironment="), @@ -661,7 +661,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Security * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Security */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NoNewPrivileges="), - SD_VARLINK_DEFINE_FIELD(NoNewPrivileges, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(NoNewPrivileges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SecureBits="), SD_VARLINK_DEFINE_FIELD(SecureBits, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -677,13 +677,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Process Properties * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Process%20Properties */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#LimitCPU="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Limits, ResourceLimitTable, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Limits, ResourceLimitTable, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UMask="), SD_VARLINK_DEFINE_FIELD(UMask, SD_VARLINK_INT, SD_VARLINK_NULLABLE), 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_BY_TYPE(KeyringMode, ExecKeyringMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(KeyringMode, ExecKeyringMode, SD_VARLINK_NULLABLE), 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="), @@ -691,18 +691,18 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#Personality="), SD_VARLINK_DEFINE_FIELD(Personality, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IgnoreSIGPIPE="), - SD_VARLINK_DEFINE_FIELD(IgnoreSIGPIPE, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(IgnoreSIGPIPE, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), /* Scheduling * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Scheduling */ 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_DEFINE_FIELD(Nice, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingPolicy="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSchedulingPolicy, CPUSchedulingPolicy, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSchedulingPolicy, CPUSchedulingPolicy, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(CPUSchedulingPriority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingResetOnFork="), - SD_VARLINK_DEFINE_FIELD(CPUSchedulingResetOnFork, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(CPUSchedulingResetOnFork, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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="), @@ -710,21 +710,21 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( 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_BY_TYPE(IOSchedulingClass, IOSchedulingClass, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOSchedulingClass, IOSchedulingClass, SD_VARLINK_NULLABLE), 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), + SD_VARLINK_DEFINE_FIELD(IOSchedulingPriority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), 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_BY_TYPE(MemoryTHP, MemoryTHP, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MemoryTHP, MemoryTHP, SD_VARLINK_NULLABLE), /* 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_BY_TYPE(ProtectSystem, ProtectSystem, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectSystem, ProtectSystem, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHome="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHome, ProtectHome, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHome, ProtectHome, SD_VARLINK_NULLABLE), 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="), @@ -736,7 +736,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_BY_TYPE(RuntimeDirectoryPreserve, ExecPreserveMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RuntimeDirectoryPreserve, ExecPreserveMode, SD_VARLINK_NULLABLE), 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="), @@ -752,35 +752,35 @@ 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_BY_TYPE(PrivateTmp, PrivateTmp, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateTmp, PrivateTmp, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(PrivateDevices, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateNetwork="), - SD_VARLINK_DEFINE_FIELD(PrivateNetwork, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(PrivateNetwork, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NetworkNamespacePath="), SD_VARLINK_DEFINE_FIELD(NetworkNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateIPC="), - SD_VARLINK_DEFINE_FIELD(PrivateIPC, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(PrivateIPC, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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_BY_TYPE(PrivatePIDs, PrivatePIDs, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePIDs, PrivatePIDs, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateUsers="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateUsers, PrivateUsers, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateUsers, PrivateUsers, SD_VARLINK_NULLABLE), 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_BY_TYPE(ProtectHostname, ProtectHostname, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHostname, ProtectHostname, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(ProtectClock, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelTunables="), - SD_VARLINK_DEFINE_FIELD(ProtectKernelTunables, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ProtectKernelTunables, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelModules="), - SD_VARLINK_DEFINE_FIELD(ProtectKernelModules, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ProtectKernelModules, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(ProtectKernelLogs, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectControlGroups="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectControlGroups, ProtectControlGroups, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectControlGroups, ProtectControlGroups, SD_VARLINK_NULLABLE), 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="), @@ -790,7 +790,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_BY_TYPE(PrivatePBF, PrivateBPF, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePBF, PrivateBPF, SD_VARLINK_NULLABLE), 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="), @@ -800,15 +800,15 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateAttachments="), SD_VARLINK_DEFINE_FIELD(BPFDelegateAttachments, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#LockPersonality="), - SD_VARLINK_DEFINE_FIELD(LockPersonality, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(LockPersonality, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryDenyWriteExecute="), - SD_VARLINK_DEFINE_FIELD(MemoryDenyWriteExecute, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(MemoryDenyWriteExecute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictRealtime="), - SD_VARLINK_DEFINE_FIELD(RestrictRealtime, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RestrictRealtime, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictSUIDSGID="), - SD_VARLINK_DEFINE_FIELD(RestrictSUIDSGID, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RestrictSUIDSGID, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether to remove all System V and POSIX IPC objects owned by the user and group this unit runs under"), - SD_VARLINK_DEFINE_FIELD(RemoveIPC, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RemoveIPC, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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="), @@ -839,11 +839,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_BY_TYPE(StandardInput, ExecInputType, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardInput, ExecInputType, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardOutput="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardOutput, ExecOutputType, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardOutput, ExecOutputType, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardError="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardError, ExecOutputType, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardError, ExecOutputType, SD_VARLINK_NULLABLE), 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"), @@ -869,7 +869,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SyslogLevel="), SD_VARLINK_DEFINE_FIELD(SyslogLevel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SyslogLevelPrefix="), - SD_VARLINK_DEFINE_FIELD(SyslogLevelPrefix, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(SyslogLevelPrefix, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TTYPath="), SD_VARLINK_DEFINE_FIELD(TTYPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TTYReset="), @@ -901,7 +901,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_BY_TYPE(UtmpMode, ExecUtmpMode, 0)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(UtmpMode, ExecUtmpMode, SD_VARLINK_NULLABLE)); SD_VARLINK_DEFINE_ENUM_TYPE( KillMode, From a2aa3a7450a7592b7639a66f12cfe83b6a0973a1 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 10:56:41 +0200 Subject: [PATCH 1670/2155] core: add WorkingDirectory to Unit.StartTransient This commit adds setting the WorkingDirectory to the `io.systemd.Unit.StartTransient` varlink call. This is a first step towards more complete StartTransient in varlink. The goal is to be as close as possible to the D-Bus parameters. The exception is WorkingDirectory which is an object here so that we avoid the `-` prefixes and use a more type-safe approach by making it an explicit `missingOK` parameter. The key names stay the same as the D-Bus properties (PascalCase). If there are no equivalent D-Bus properties the native varlink convention of camelCase is used. --- src/core/varlink-unit.c | 126 +++++++++++++++++++++++++-- src/shared/varlink-io.systemd.Unit.c | 13 +-- test/units/TEST-26-SYSTEMCTL.sh | 32 +++++++ 3 files changed, 159 insertions(+), 12 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 85139f068dcb6..cb67dfa70fd96 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -702,6 +702,18 @@ static void transient_exec_command_item_done(TransientExecCommandItem *i) { static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_type_from_string); static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string); +typedef struct TransientWorkingDirectory { + const char *path; + bool home; + bool missing_ok; +} TransientWorkingDirectory; + +typedef struct TransientExecContextParameters { + bool present; + bool working_directory_set; + TransientWorkingDirectory working_directory; +} TransientExecContextParameters; + typedef struct TransientServiceParameters { bool present; ServiceType type; @@ -753,7 +765,9 @@ static int dispatch_transient_exec_command(const char *name, sd_json_variant *va typedef struct StartTransientContextParameters { const char *id; const char *description; + TransientExecContextParameters exec; TransientServiceParameters service; + const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */ } StartTransientContextParameters; static void start_transient_context_parameters_done(StartTransientContextParameters *p) { @@ -766,12 +780,49 @@ typedef struct StartTransientParameters { JobMode mode; int notify_job_changes; int notify_unit_changes; - const char *unsupported_property; /* For error reporting on unknown context fields */ + char *unsupported_property; /* For error reporting on unknown context fields */ } StartTransientParameters; static void start_transient_parameters_done(StartTransientParameters *p) { assert(p); start_transient_context_parameters_done(&p->context); + free(p->unsupported_property); +} + +static int dispatch_const_string_empty_as_null(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + const char **s = ASSERT_PTR(userdata); + int r; + + r = sd_json_dispatch_const_string(name, variant, flags, s); + if (r >= 0 && isempty(*s)) + *s = NULL; + return r; +} + +static int dispatch_transient_working_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + /* No equivalent D-Bus properties, so use varlink camelCase */ + static const sd_json_dispatch_field dispatch[] = { + { "path", SD_JSON_VARIANT_STRING, dispatch_const_string_empty_as_null, offsetof(TransientWorkingDirectory, path), 0 }, + { "home", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, home), 0 }, + { "missingOK", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, missing_ok), 0 }, + {} + }; + + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->working_directory_set = true; + return sd_json_dispatch(variant, dispatch, flags, &p->working_directory); +} + +static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + /* Key names compatible with D-Bus property names */ + static const sd_json_dispatch_field exec_dispatch[] = { + { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, + {} + }; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + p->exec.present = true; + return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field); } static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { @@ -789,9 +840,10 @@ static int dispatch_transient_service(const char *name, sd_json_variant *variant static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field context_dispatch[] = { - { "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY }, - { "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 }, - { "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 }, + { "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY }, + { "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 }, + { "Exec", SD_JSON_VARIANT_OBJECT, dispatch_transient_exec_context, 0, 0 }, + { "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 }, {} }; @@ -802,10 +854,18 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant /* Don't propagate the caller's flags (in particular SD_JSON_MANDATORY from the outer 'context' * field) into the nested dispatch, otherwise every inner field becomes mandatory. */ r = sd_json_dispatch_full(variant, context_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->context, &bad_field); - if (r == -EADDRNOTAVAIL && !isempty(bad_field)) + if (r == -EADDRNOTAVAIL && !isempty(bad_field)) { /* A UnitContext field that exists in the schema but is not settable at creation time: stash - * the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. */ - p->unsupported_property = bad_field; + * the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. If the + * unknown field lives inside the nested Exec object, compose a dotted name to identify the + * actual sub-property. */ + if (streq(bad_field, "Exec") && !isempty(p->context.bad_exec_field)) + p->unsupported_property = strjoin("Exec.", p->context.bad_exec_field); + else + p->unsupported_property = strdup(bad_field); + if (!p->unsupported_property) + return -ENOMEM; + } return r; } @@ -825,6 +885,49 @@ static int transient_unit_apply_properties(Unit *u, StartTransientContextParamet return 0; } +static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + int r; + + assert(u); + assert(c); + assert(p); + + if (p->working_directory_set) { + TransientWorkingDirectory *wd = &p->working_directory; + _cleanup_free_ char *simplified = NULL; + + if (wd->home && wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: 'home' and 'path' are mutually exclusive"); + if (!wd->home && !wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: must specify either 'home' or 'path'"); + + if (!wd->home) { + if (!path_is_absolute(wd->path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects an absolute path"); + r = path_simplify_alloc(wd->path, &simplified); + if (r < 0) + return r; + if (!path_is_normalized(simplified)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects a normalized path"); + } + + free_and_replace(c->working_directory, simplified); + c->working_directory_home = wd->home; + c->working_directory_missing_ok = wd->missing_ok; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory", + "WorkingDirectory=%s%s", + c->working_directory_missing_ok ? "-" : "", + c->working_directory_home ? "~" : strempty(c->working_directory)); + } + + return 0; +} + static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp) { Unit *u = UNIT(ASSERT_PTR(s)); int r; @@ -978,6 +1081,15 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters if (r < 0) return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + /* Apply exec-specific properties from context.Exec */ + ExecContext *c = unit_get_exec_context(u); + if (c) { + r = transient_exec_context_apply_properties(u, c, &p.context.exec); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + } else if (p.context.exec.present) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + /* Apply service-specific properties from context.Service */ Service *s = SERVICE(u); if (s) { diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index e62fe40a628e8..dada1d58139d8 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -421,10 +421,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* ExecContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( WorkingDirectory, - SD_VARLINK_FIELD_COMMENT("The path to the working directory"), - SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The path to the working directory. Mutually exclusive with 'home'"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, use the configured user's home directory as the working directory. Mutually exclusive with 'path'"), + SD_VARLINK_DEFINE_FIELD(home, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether the path to the working directory is allowed to not exist"), - SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, 0)); + SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( PartitionMountOptions, @@ -1182,8 +1184,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); /* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, - * Description, Service) and as output from List/StartTransient (full unit configuration). Fields that - * are not settable at creation time are rejected with PropertyNotSupported when supplied as input. */ + * Description, Service, and the Exec subset {WorkingDirectory}) and as output from + * List/StartTransient (full unit configuration). Fields that are not settable at creation time are + * rejected with PropertyNotSupported when supplied as input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index ed030031d26cf..ec3f88b3a9260 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -621,6 +621,27 @@ result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' +# Exec.WorkingDirectory +defer_transient_cleanup varlink-transient-exec.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"' +timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null + +# WorkingDirectory with missingOK=true (path does not exist but unit still starts) +defer_transient_cleanup varlink-transient-wd-missing.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done' + +# WorkingDirectory with home=true, missingOK omitted (defaults to false) +defer_transient_cleanup varlink-transient-wd-home.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -634,6 +655,17 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ defer_transient_cleanup varlink-transient-badpath.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Relative WorkingDirectory path is rejected +defer_transient_cleanup varlink-transient-bad-wd.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Exec on a unit type without an exec context (.target) is rejected +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Unknown field in Exec is rejected as PropertyNotSupported +defer_transient_cleanup varlink-transient-unknown-exec.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.PropertyNotSupported" set -o pipefail transient_cleanup From ed6b2b9c30a24183e4a2ae71cc58bc461f0d6ca4 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 15:41:24 +0200 Subject: [PATCH 1671/2155] core: extract exec_context_apply_environment() helper Extract a small helper exec_context_apply_environment() from the dbus-execute.c file so that it can be re-used in the coming varlink version. --- src/core/dbus-execute.c | 28 +++++----------------------- src/core/execute.c | 35 +++++++++++++++++++++++++++++++++++ src/core/execute.h | 2 ++ 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2329762f16b68..068b1ebb3d3a4 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -3456,32 +3456,14 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + r = exec_context_apply_environment(u, c, l, flags); + if (r == -E2BIG) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many environment assignments."); - if (!strv_env_is_valid(l)) + if (r == -EINVAL) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); - - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - if (strv_isempty(l)) { - c->environment = strv_free(c->environment); - unit_write_setting(u, flags, name, "Environment="); - } else { - _cleanup_free_ char *joined = NULL; - char **e; - - joined = unit_concat_strv(l, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_C); - if (!joined) - return -ENOMEM; - - e = strv_env_merge(c->environment, l); - if (!e) - return -ENOMEM; - - strv_free_and_replace(c->environment, e); - unit_write_settingf(u, flags, name, "Environment=%s", joined); - } - } + if (r < 0) + return r; return 1; diff --git a/src/core/execute.c b/src/core/execute.c index 987093511fc57..7f92eba30f532 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -753,6 +753,41 @@ void exec_context_done(ExecContext *c) { c->private_hostname = mfree(c->private_hostname); } +int exec_context_apply_environment( + Unit *u, + ExecContext *c, + char **env, + UnitWriteFlags flags) { + + assert(u); + assert(c); + + if (strv_length(env) > ENVIRONMENT_ASSIGNMENTS_MAX) + return -E2BIG; + if (!strv_env_is_valid(env)) + return -EINVAL; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (strv_isempty(env)) { + c->environment = strv_free(c->environment); + unit_write_setting(u, flags, "Environment", "Environment="); + } else { + _cleanup_free_ char *joined = unit_concat_strv(env, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_C); + if (!joined) + return -ENOMEM; + + char **e = strv_env_merge(c->environment, env); + if (!e) + return -ENOMEM; + + strv_free_and_replace(c->environment, e); + unit_write_settingf(u, flags, "Environment", "Environment=%s", joined); + } + } + + return 0; +} + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { assert(c); diff --git a/src/core/execute.h b/src/core/execute.h index 66bb40a675010..f29f23bc2ef41 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -540,6 +540,8 @@ void exec_context_init(ExecContext *c); void exec_context_done(ExecContext *c); void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix); +int exec_context_apply_environment(Unit *u, ExecContext *c, char **env, UnitWriteFlags flags); + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix); int exec_context_destroy_mount_ns_dir(Unit *u); From 27fa92aa3cc3e5b8b7511c52c32b49d136a2d092 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 15:45:10 +0200 Subject: [PATCH 1672/2155] core: add support for Environment in io.systemd.Unit.StartTransient This commit adds support to set `Environment` in the `io.systemd.Unit.StartTransient` varlink call. The behavior is similar to D-Bus, i.e. a `null` or `[]` clears the environment. This is not needed for StartTransient() as there the env always starts empty but it seems a good property to have if this is reused. --- src/core/varlink-unit.c | 27 ++++++++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.c | 2 +- test/units/TEST-26-SYSTEMCTL.sh | 12 ++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index cb67dfa70fd96..10fbc1d7bcf4f 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -712,8 +712,15 @@ typedef struct TransientExecContextParameters { bool present; bool working_directory_set; TransientWorkingDirectory working_directory; + bool environment_set; + char **environment; } TransientExecContextParameters; +static void transient_exec_context_parameters_done(TransientExecContextParameters *p) { + assert(p); + strv_free(p->environment); +} + typedef struct TransientServiceParameters { bool present; ServiceType type; @@ -772,6 +779,7 @@ typedef struct StartTransientContextParameters { static void start_transient_context_parameters_done(StartTransientContextParameters *p) { assert(p); + transient_exec_context_parameters_done(&p->exec); transient_service_parameters_done(&p->service); } @@ -813,10 +821,17 @@ static int dispatch_transient_working_directory(const char *name, sd_json_varian return sd_json_dispatch(variant, dispatch, flags, &p->working_directory); } +static int dispatch_transient_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->environment_set = true; + return sd_json_dispatch_strv(name, variant, flags, &p->environment); +} + static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { /* Key names compatible with D-Bus property names */ static const sd_json_dispatch_field exec_dispatch[] = { - { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, + { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, + { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, {} }; @@ -925,6 +940,16 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran c->working_directory_home ? "~" : strempty(c->working_directory)); } + if (p->environment_set) { + r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE); + if (r == -E2BIG) + return log_debug_errno(r, "Too many environment assignments."); + if (r == -EINVAL) + return log_debug_errno(r, "Invalid Environment list."); + if (r < 0) + return r; + } + return 0; } diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index dada1d58139d8..ae0f78db9c2e8 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1184,7 +1184,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); /* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, - * Description, Service, and the Exec subset {WorkingDirectory}) and as output from + * Description, Service, and the Exec subset {WorkingDirectory, Environment}) and as output from * List/StartTransient (full unit configuration). Fields that are not settable at creation time are * rejected with PropertyNotSupported when supplied as input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index ec3f88b3a9260..6f615eb1c3ab6 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -621,13 +621,17 @@ result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' -# Exec.WorkingDirectory +# Exec.WorkingDirectory and Exec.Environment defer_transient_cleanup varlink-transient-exec.service result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') + '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"' +echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null' +echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null' timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done' systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null # WorkingDirectory with missingOK=true (path does not exist but unit still starts) defer_transient_cleanup varlink-transient-wd-missing.service @@ -659,6 +663,10 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ defer_transient_cleanup varlink-transient-bad-wd.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Malformed environment entry (not KEY=VALUE) +defer_transient_cleanup varlink-transient-bad-env.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" # Exec on a unit type without an exec context (.target) is rejected varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" From 045d08e41744f0f0ec54a684ba37cd8ad46b5243 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 5 May 2026 09:43:50 +0200 Subject: [PATCH 1673/2155] core: extract exec_context_apply_set_credential() helper Extract the SetCredential{,Encrypted} logic out of bus_exec_context_set_transient_property() into a new helper. No functional changes. This will be used in the varlink Unit.StartTransient SetCredential implementation. --- src/core/dbus-execute.c | 33 ++++++--------------------- src/core/execute.c | 50 +++++++++++++++++++++++++++++++++++++++++ src/core/execute.h | 1 + 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 068b1ebb3d3a4..906002570f1e8 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -2536,6 +2536,7 @@ int bus_exec_context_set_transient_property( return 1; } else if (STR_IN_SET(name, "SetCredential", "SetCredentialEncrypted")) { + bool encrypted = endswith(name, "Encrypted"); bool isempty = true; r = sd_bus_message_enter_container(message, 'a', "(say)"); @@ -2546,6 +2547,7 @@ int bus_exec_context_set_transient_property( const char *id; const void *p; size_t sz; + const char *err = NULL; r = sd_bus_message_enter_container(message, 'r', "say"); if (r < 0) @@ -2565,34 +2567,13 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (!credential_name_valid(id)) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id); - isempty = false; - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - bool encrypted = endswith(name, "Encrypted"); - _cleanup_free_ char *a = NULL, *b = NULL; - _cleanup_free_ void *copy = NULL; - - copy = memdup(p, sz); - if (!copy) - return -ENOMEM; - - a = specifier_escape(id); - if (!a) - return -ENOMEM; - - b = cescape_length(p, sz); - if (!b) - return -ENOMEM; - - r = exec_context_put_set_credential(c, id, TAKE_PTR(copy), sz, encrypted); - if (r < 0) - return r; - - (void) unit_write_settingf(u, flags, name, "%s=%s:%s", name, a, b); - } + r = exec_context_apply_set_credential(u, c, id, p, sz, encrypted, flags, &err); + if (r == -EINVAL) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "%s: %s", err, id); + if (r < 0) + return r; } r = sd_bus_message_exit_container(message); diff --git a/src/core/execute.c b/src/core/execute.c index 7f92eba30f532..7935da743164b 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -18,11 +18,13 @@ #include "cgroup-setup.h" #include "coredump-util.h" #include "cpu-set-util.h" +#include "creds-util.h" #include "dissect-image.h" #include "dynamic-user.h" #include "env-file.h" #include "env-util.h" #include "escape.h" +#include "exec-credential.h" #include "execute.h" #include "execute-serialize.h" #include "fd-util.h" @@ -55,6 +57,7 @@ #include "serialize.h" #include "set.h" #include "sort-util.h" +#include "specifier.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -788,6 +791,53 @@ int exec_context_apply_environment( return 0; } +int exec_context_apply_set_credential( + Unit *u, + ExecContext *c, + const char *id, + const void *data, + size_t size, + bool encrypted, + UnitWriteFlags flags, + const char **reterr_message) { + + int r; + + assert(u); + assert(c); + assert(id); + assert(data || size == 0); + + if (!credential_name_valid(id)) { + if (reterr_message) + *reterr_message = "Credential ID is invalid"; + return -EINVAL; + } + + if (UNIT_WRITE_FLAGS_NOOP(flags)) + return 0; + + _cleanup_free_ void *copy = memdup(data, size); + if (!copy) + return -ENOMEM; + + _cleanup_free_ char *escaped_id = specifier_escape(id); + if (!escaped_id) + return -ENOMEM; + + _cleanup_free_ char *escaped_value = cescape_length(data, size); + if (!escaped_value) + return -ENOMEM; + + r = exec_context_put_set_credential(c, id, TAKE_PTR(copy), size, encrypted); + if (r < 0) + return r; + + const char *name = encrypted ? "SetCredentialEncrypted" : "SetCredential"; + unit_write_settingf(u, flags, name, "%s=%s:%s", name, escaped_id, escaped_value); + return 0; +} + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { assert(c); diff --git a/src/core/execute.h b/src/core/execute.h index f29f23bc2ef41..4553ce9d84d0b 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -541,6 +541,7 @@ void exec_context_done(ExecContext *c); void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix); int exec_context_apply_environment(Unit *u, ExecContext *c, char **env, UnitWriteFlags flags); +int exec_context_apply_set_credential(Unit *u, ExecContext *c, const char *id, const void *data, size_t size, bool encrypted, UnitWriteFlags flags, const char **reterr_message); int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix); int exec_context_destroy_mount_ns_dir(Unit *u); From de2ad965c2ef6f775f61019ad86899bd5a5bf410 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 5 May 2026 10:16:11 +0200 Subject: [PATCH 1674/2155] core: add SetCredentials{,Encrypted} to varlink Unit.StartTransient This commit adds support to set `SetCredentials` and `SetCredentialsEncrypted` in the `io.systemd.Unit.StartTransient` varlink call. --- src/core/varlink-unit.c | 120 ++++++++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.c | 7 +- test/units/TEST-26-SYSTEMCTL.sh | 20 ++++- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 10fbc1d7bcf4f..3b289e46458a2 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -11,6 +11,7 @@ #include "execute.h" #include "format-util.h" #include "install.h" +#include "iovec-util.h" #include "job.h" #include "json-util.h" #include "locale-util.h" @@ -708,17 +709,38 @@ typedef struct TransientWorkingDirectory { bool missing_ok; } TransientWorkingDirectory; +typedef struct TransientSetCredential { + const char *id; + struct iovec value; +} TransientSetCredential; + typedef struct TransientExecContextParameters { bool present; bool working_directory_set; TransientWorkingDirectory working_directory; bool environment_set; char **environment; + + bool set_credentials_set; + TransientSetCredential *set_credentials; + size_t n_set_credentials; + + bool set_credentials_encrypted_set; + TransientSetCredential *set_credentials_encrypted; + size_t n_set_credentials_encrypted; } TransientExecContextParameters; +static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) { + FOREACH_ARRAY(item, items, n) + iovec_done_erase(&item->value); + free(items); +} + static void transient_exec_context_parameters_done(TransientExecContextParameters *p) { assert(p); strv_free(p->environment); + transient_set_credential_array_free(p->set_credentials, p->n_set_credentials); + transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted); } typedef struct TransientServiceParameters { @@ -827,11 +849,67 @@ static int dispatch_transient_environment(const char *name, sd_json_variant *var return sd_json_dispatch_strv(name, variant, flags, &p->environment); } +static int dispatch_transient_set_credential_array( + sd_json_variant *variant, + TransientSetCredential **ret_items, + size_t *ret_n) { + + static const sd_json_dispatch_field item_dispatch[] = { + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientSetCredential, id), SD_JSON_MANDATORY }, + { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(TransientSetCredential, value), SD_JSON_MANDATORY }, + {} + }; + + TransientSetCredential *items = NULL; + size_t n = 0; + int r; + + assert(ret_items); + assert(ret_n); + + CLEANUP_ARRAY(items, n, transient_set_credential_array_free); + + size_t n_items = sd_json_variant_elements(variant); + if (n_items == 0) { + *ret_items = NULL; + *ret_n = 0; + return 0; + } + + items = new0(TransientSetCredential, n_items); + if (!items) + return -ENOMEM; + + for (; n < n_items; n++) { + r = sd_json_dispatch(sd_json_variant_by_index(variant, n), item_dispatch, /* flags= */ 0, &items[n]); + if (r < 0) + return r; + } + + *ret_n = n; + *ret_items = TAKE_PTR(items); + return 0; +} + +static int dispatch_transient_set_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials, &p->n_set_credentials); +} + +static int dispatch_transient_set_credential_encrypted(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_encrypted_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials_encrypted, &p->n_set_credentials_encrypted); +} + static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { /* Key names compatible with D-Bus property names */ static const sd_json_dispatch_field exec_dispatch[] = { - { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, - { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, + { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, + { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, + { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 }, + { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 }, {} }; @@ -900,6 +978,32 @@ static int transient_unit_apply_properties(Unit *u, StartTransientContextParamet return 0; } +static int transient_apply_set_credentials( + Unit *u, + ExecContext *c, + const TransientSetCredential *items, + size_t n_items, + bool encrypted) { + + int r; + + assert(u); + assert(c); + + FOREACH_ARRAY(item, items, n_items) { + const char *err = NULL; + + r = exec_context_apply_set_credential(u, c, item->id, item->value.iov_base, item->value.iov_len, + encrypted, UNIT_RUNTIME|UNIT_PRIVATE, &err); + if (r == -EINVAL) + return log_debug_errno(r, "%s: %s", err, item->id); + if (r < 0) + return r; + } + + return 0; +} + static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p) { int r; @@ -950,6 +1054,18 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran return r; } + if (p->set_credentials_set) { + r = transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); + if (r < 0) + return r; + } + + if (p->set_credentials_encrypted_set) { + r = transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); + if (r < 0) + return r; + } + return 0; } diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index ae0f78db9c2e8..f4ff0b648a840 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1184,9 +1184,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); /* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, - * Description, Service, and the Exec subset {WorkingDirectory, Environment}) and as output from - * List/StartTransient (full unit configuration). Fields that are not settable at creation time are - * rejected with PropertyNotSupported when supplied as input. */ + * Description, Service, and the Exec subset {WorkingDirectory, Environment, SetCredential, + * SetCredentialEncrypted}) and as output from List/StartTransient (full unit configuration). Fields + * that are not settable at creation time are rejected with PropertyNotSupported when supplied as + * input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 6f615eb1c3ab6..a98ef7646d33b 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -646,6 +646,16 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null +# Exec.SetCredential: pass a credential and verify the running process can read it +defer_transient_cleanup varlink-transient-cred.service +CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0) +CRED_OUTPUT=$(mktemp) +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + "{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}" +timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done" +grep '^secret-value$' "$CRED_OUTPUT" >/dev/null +rm -f "$CRED_OUTPUT" + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -667,7 +677,15 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ defer_transient_cleanup varlink-transient-bad-env.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" -# Exec on a unit type without an exec context (.target) is rejected +# Invalid credential ID +defer_transient_cleanup varlink-transient-bad-cred-id.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) +defer_transient_cleanup varlink-transient-bad-cred-value.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument" +# Exec on a unit type without an exec context (.slice) is rejected varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" # Unknown field in Exec is rejected as PropertyNotSupported From b05d828b5c16606d267b3a7869329bdf7c2a4c68 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 13 May 2026 18:31:27 +0100 Subject: [PATCH 1675/2155] import: do not create foreign ns on cleanup if not needed The user ns is only used if the appropriate flag is set, so avoid creating it unless it is. This avoids a spurious EPERM error in TEST-13-NSPAWN.machined that is confusing when debugging failures [ 34.054] systemd-importd[504]: (transfer18) Imported 92%. [ 34.118] systemd-importd[504]: (transfer18) Failed to decode and write: Broken pipe [ 34.119] systemd-importd[504]: (transfer18) Exiting. [ 34.121] systemd-importd[504]: (transfer18) Failed to allocate transient user namespace: Operation not permitted [ 34.121] systemd-importd[504]: Transfer process failed with exit code 1. Follow-up for 1be8caa6be6f5a10a7dea5ac562a0df5c5fac2e9 --- src/import/import-common.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/import/import-common.c b/src/import/import-common.c index 5f17084f9fd94..840eac210f869 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -392,13 +392,14 @@ int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) { assert(path); assert(userns_fd); - r = import_make_foreign_userns(userns_fd); - if (r < 0) - return r; - /* Try the userns dance first, to remove foreign UID range owned trees */ - if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) + if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + (void) remove_tree_foreign(path, *userns_fd); + } r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD); if (r < 0) From 148c34dd6aa57d62079fca3c66dc1deeb2f3cbc6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 13 May 2026 18:39:06 +0100 Subject: [PATCH 1676/2155] import: try to capture tar exit codes on failure TEST-13-NSPAWN.machined occasionally fails with a tar error, and it's hard to say what the problem is at the exit code is lost. Try to capture it. [ 34.054] systemd-importd[504]: (transfer18) Imported 92%. [ 34.118] systemd-importd[504]: (transfer18) Failed to decode and write: Broken pipe [ 34.119] systemd-importd[504]: (transfer18) Exiting. [ 34.121] systemd-importd[504]: (transfer18) Failed to allocate transient user namespace: Operation not permitted [ 34.121] systemd-importd[504]: Transfer process failed with exit code 1. Follow-up for b6e676ce41508e2aeea22202fc8f234126177f52 --- src/import/import-tar.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 4bd59788008e9..06ee56bd78f90 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -368,6 +368,17 @@ static int tar_import_process(TarImport *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"); + + /* Try to check the actual exit code from the child process, to make debugging easier */ + if (r == -EPIPE && pidref_is_set(&i->tar_pid)) { + int q = pidref_wait_for_terminate_and_check("tar", &i->tar_pid, WAIT_LOG); + pidref_done(&i->tar_pid); + if (q < 0) + r = q; + else if (q != EXIT_SUCCESS) + r = -EPROTO; + } + goto finish; } From 6e232bb565d275397359334421edc3c136a0e7ad Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 13 May 2026 15:17:39 +0100 Subject: [PATCH 1677/2155] mkosi: update debian commit reference to 8b9ea8981eee267a2fa493435f2869f7b2479350 * 8b9ea8981e Install new files for upstream build * b230cf0490 use dh-cruft to register & purge volatile files * 8f9b9952e1 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 f72e35d6584b0..8449b296c2f97 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=1302f123d9ab65bbaff5d95935eabfd659456550 + GIT_COMMIT=8b9ea8981eee267a2fa493435f2869f7b2479350 PKG_SUBDIR=debian From bd7a152345cf58b034ceefb0fe7ca58b8cf38e13 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 21:23:48 +0200 Subject: [PATCH 1678/2155] repart: Add debug logging for block_device_partition_add() --- src/fundamental/macro-fundamental.h | 7 +++++++ src/repart/repart.c | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 6ed6cf2f8a0a1..d38800d260747 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -278,6 +278,13 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); CONST_ISPOWEROF2(_x); \ })) +/* Returns the largest power of two that divides x (i.e. x's natural alignment in bytes), or 0 if x is 0. */ +#define NATURAL_ALIGNMENT(x) \ + ({ \ + const uint64_t _x = (x); \ + _x == 0 ? UINT64_C(0) : UINT64_C(1) << __builtin_ctzll(_x); \ + }) + #define ADD_SAFE(ret, a, b) (!__builtin_add_overflow(a, b, ret)) #define INC_SAFE(a, b) __INC_SAFE(UNIQ, a, b) #define __INC_SAFE(q, a, b) \ diff --git a/src/repart/repart.c b/src/repart/repart.c index 331b31b789991..0cdfef56be3a7 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5202,6 +5202,10 @@ static int partition_target_prepare( * keep them alive if we succeed, and the rescan will remove them if possible if * there is an error before writing the partition table. */ + log_debug("Adding partition '%s' (nr=%i, offset=%" PRIu64 " [align=%" PRIu64 "], size=%" PRIu64 " [align=%" PRIu64 "])", + part_node, nr, + p->offset, NATURAL_ALIGNMENT(p->offset), + size, NATURAL_ALIGNMENT(size)); r = block_device_add_partition(whole_fd, part_node, nr, p->offset, size); if (r < 0) return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); From f8b73a3baeadf94c86a17f8bf62f113195769d09 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 15:19:54 +0200 Subject: [PATCH 1679/2155] core: when figuring out whether to create orphanage units, consult vtable instead of allowlist As per https://github.com/systemd/systemd/pull/41986#pullrequestreview-4281939586 This also corrects the list of unit types a bit: 1. this removes the mount/automount unit type from the list, since for these types we do not allow aliases/renaming anyway. 2. this adds socket + swap units to the list, since they can change name, and for both of them we actually do fork off processes hence track resources. Follow-up for: #41986 --- src/core/manager-serialize.c | 4 ++-- src/core/mount.c | 4 ++-- src/core/scope.c | 1 + src/core/service.c | 4 ++-- src/core/socket.c | 1 + src/core/swap.c | 1 + src/core/unit.h | 4 ++++ 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 9a64be40397de..d3549d81294ea 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -270,8 +270,8 @@ static int manager_synthesize_orphaned_unit( "Cannot synthesize unit for '%s' (overridden by alias to '%s'): invalid unit type. Skipping stale state.", original_name, canonical_name); - /* Only transition units that track external resources, forget internal ones (eg: timers) */ - if (!IN_SET(t, UNIT_SERVICE, UNIT_SCOPE, UNIT_MOUNT, UNIT_AUTOMOUNT)) + /* Only transition units that track external resources and can be renamed/know aliases, forget internal ones (eg: timers) */ + if (!unit_vtable[t]->track_orphaned) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cannot synthesize unit for '%s' (overridden by alias to '%s'): unsupported unit type. Skipping stale state.", original_name, canonical_name); diff --git a/src/core/mount.c b/src/core/mount.c index c57f6e8a667cf..408274c3864be 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -2509,6 +2509,8 @@ const UnitVTable mount_vtable = { .can_transient = true, .can_fail = true, .exclude_from_switch_root_serialization = true, + .notify_plymouth = true, + .track_orphaned = true, .init = mount_init, .load = mount_load, @@ -2576,6 +2578,4 @@ const UnitVTable mount_vtable = { }, .test_startable = mount_test_startable, - - .notify_plymouth = true, }; diff --git a/src/core/scope.c b/src/core/scope.c index 21520d9d1943b..167d3a37bd9b5 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -744,6 +744,7 @@ const UnitVTable scope_vtable = { .can_fail = true, .once_only = true, .can_set_managed_oom = true, + .track_orphaned = true, .init = scope_init, .load = scope_load, diff --git a/src/core/service.c b/src/core/service.c index 4e1d1d38d03bd..445812f9522d5 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -6149,6 +6149,8 @@ const UnitVTable service_vtable = { .can_delegate = true, .can_fail = true, .can_set_managed_oom = true, + .notify_plymouth = true, + .track_orphaned = true, .init = service_init, .done = service_done, @@ -6213,8 +6215,6 @@ const UnitVTable service_vtable = { .test_startable = service_test_startable, - .notify_plymouth = true, - .audit_start_message_type = AUDIT_SERVICE_START, .audit_stop_message_type = AUDIT_SERVICE_STOP, }; diff --git a/src/core/socket.c b/src/core/socket.c index fbf0dfd9332ae..3c4742fba11eb 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -3726,6 +3726,7 @@ const UnitVTable socket_vtable = { .can_transient = true, .can_trigger = true, .can_fail = true, + .track_orphaned = true, .init = socket_init, .done = socket_done, diff --git a/src/core/swap.c b/src/core/swap.c index cedabc430dc0c..88e992bd75040 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -1616,6 +1616,7 @@ const UnitVTable swap_vtable = { .private_section = "Swap", .can_fail = true, + .track_orphaned = true, .init = swap_init, .load = swap_load, diff --git a/src/core/unit.h b/src/core/unit.h index d79bb7a98a57d..d20e46ab57927 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -771,6 +771,10 @@ typedef struct UnitVTable { /* If true, we'll notify a surrounding VMM/container manager about this unit becoming available */ bool notify_supervisor; + /* If true, we'll synthesize an 'orphaned' unit if a unit becomes an alias of another unit during a + * reload cycle, but still has resources assigned to it */ + bool track_orphaned; + /* The audit events to generate on start + stop (or 0 if none shall be generated) */ int audit_start_message_type; int audit_stop_message_type; From 0bd38f2258261d144139c62fa45493da39c0d415 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 13 May 2026 15:58:11 +0200 Subject: [PATCH 1680/2155] vmspawn: multifunction-pack pcie-root-ports on pcie.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-allocated pcie-root-port block in run_virtual_machine() places every port directly on pcie.0 with an auto-assigned PCI address. A minimal VM already costs 4 builtin + 10 hotplug spares = 14 pcie.0 slots, on top of 3 implicit virtio devices (virtio-rng-pci, virtio-balloon, virtio-serial-pci) for another 3. pcie.0 has 32 device-numbers; q35 reserves 0x00 (host bridge) and 0x1f (ICH9 LPC), leaving ~30 auto-assignable slots. TEST-64-UDEV-STORAGE- nvme_basic pushes 20 '-device nvme' lines through $SYSTEMD_VMSPAWN_QEMU_EXTRA, which vmspawn does not see — total demand 14 + 3 + 20 = 37 > 30. Bus realization fails after QEMU's chardev has already emitted the QMP greeting, and the monitor socket POLLHUPs while we are mid-feature-probe, reported as 'QMP connection dropped during feature probing'. Pack the root ports as multifunction devices, 8 per pcie.0 device- number (QEMU docs/pcie.txt:84, 117-120, 255-258). Function 0 of each group carries multifunction=on; functions 1-7 ride the same slot via addr=N.F. Each function remains independently hot-pluggable so vmspawn's QMP device_add machinery is unaffected. 14 ports collapse to 2 pcie.0 slots; the nvme_basic budget becomes 2 + 3 + 20 = 25. The chassis/slot properties (used for ACPI hotplug identity) stay as i+1 — they live in a uint8_t namespace independent of the PCI BDF and are still unique. Base PCI slot 0x10 sits above the auto-assigned virtio devices (which land at 0x01-0x03 in config order) and below the q35 LPC reservation at 0x1f. While here, rebuild the slot-count formula to match what assign_pcie_ports() actually allocates. The +1 'SCSI controller' term was bogus — virtio-scsi-pci comes from the hotplug-spares pool via hotplug_port_owner[] in vmspawn-qmp.c, never from a builtin port (see the comment in assign_pcie_ports()). The +1 'network' and +1 'vsock' terms are now conditional on arg_network_stack and use_vsock. Bind volumes were missing entirely. And the per-drive accounting now mirrors assign_pcie_ports()'s skip-SCSI behaviour: non-SCSI drives (root + extras + bind volumes) take one builtin port each, SCSI drives take none — they share a controller drawn from the hotplug pool at device-add time. Cap at 120 ports (15 device-numbers × 8) so we cannot run off the end of the 5-bit PCI device-number space — the usable range starting at 0x10 ends at 0x1e because ICH9 LPC sits at 0x1f.0 single-function, blocking the rest of that slot for multifunction packing. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 73 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 97920aecc0efc..7d520bbad5ca2 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -103,6 +103,13 @@ #define DISK_SERIAL_MAX_LEN_NVME 20 #define DISK_SERIAL_MAX_LEN_VIRTIO_BLK 20 +/* First and one-past-last pcie.0 device-numbers used for multifunction-packed + * pcie-root-ports. Sits above the auto-assigned virtio devices (0x01-0x03) and + * below 0x1f, which q35 reserves for ICH9 LPC at 0x1f.0 (single-function). */ +#define VMSPAWN_PCIE_PACK_BASE_SLOT 0x10 +#define VMSPAWN_PCIE_PACK_END_SLOT 0x1f +#define VMSPAWN_PCIE_PACK_MAX_PORTS ((VMSPAWN_PCIE_PACK_END_SLOT - VMSPAWN_PCIE_PACK_BASE_SLOT) * 8) + /* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable * NVRAM. */ typedef enum StateMode { @@ -3487,28 +3494,47 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { * 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 */ + /* Count the PCI devices that assign_pcie_ports() will place on a builtin port: + * one per non-SCSI drive (root + extras + bind volumes; SCSI drives share a + * virtio-scsi-pci controller drawn from the hotplug pool, see + * assign_pcie_ports()), one if network is configured, one per virtiofs entry, + * one if vsock is in use. Plus a fixed pool of hotplug spares for runtime + * device_add. */ + size_t n_drive_ports = 0; + if (!IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + FOREACH_ARRAY(d, arg_extra_drives.drives, arg_extra_drives.n_drives) { + DiskType dt = d->disk_type >= 0 ? d->disk_type : arg_image_disk_type; + if (!IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + } + FOREACH_ARRAY(bv, arg_bind_volumes.items, arg_bind_volumes.n_items) { + DiskType dt = disk_type_from_bind_volume_config((*bv)->config); + if (dt < 0) + continue; /* unreachable: parser rejects invalid configs */ + if (!IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + } + + size_t n_pcie_ports = + n_drive_ports + /* non-SCSI drives */ + (arg_network_stack != NETWORK_STACK_NONE ? 1 : 0) + /* network */ + (arg_directory ? 1 : 0) + /* rootdir virtiofs */ + arg_runtime_mounts.n_mounts + /* runtime virtiofs */ + (use_vsock ? 1 : 0) + /* vsock */ + VMSPAWN_PCIE_HOTPLUG_SPARES; /* hotplug pool */ /* 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) + /* Cap derived from the packing range: cannot exceed VMSPAWN_PCIE_PACK_MAX_PORTS + * (= 15 slots × 8 functions = 120) without running into the 0x1f LPC slot. */ + if (n_pcie_ports > VMSPAWN_PCIE_PACK_MAX_PORTS) return log_error_errno(SYNTHETIC_ERRNO(E2BIG), - "Too many PCIe root ports requested (%zu, max 255). " + "Too many PCIe root ports requested (%zu, max %u). " "Reduce the number of extra drives or runtime mounts.", - n_pcie_ports); + n_pcie_ports, (unsigned) VMSPAWN_PCIE_PACK_MAX_PORTS); size_t n_builtin_ports = n_pcie_ports - VMSPAWN_PCIE_HOTPLUG_SPARES; for (size_t i = 0; i < n_pcie_ports; i++) { @@ -3523,6 +3549,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + /* chassis/slot are the PCIe-chassis identity (ACPI hotplug paths), + * independent of the PCI bus address below. */ r = qemu_config_keyf(config_file, "chassis", "%zu", i + 1); if (r < 0) return r; @@ -3530,6 +3558,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = qemu_config_keyf(config_file, "slot", "%zu", i + 1); if (r < 0) return r; + + /* Pack 8 root ports per pcie.0 device-number as multifunction, so 14 + * ports cost 2 slots on pcie.0 instead of 14. Each function remains + * independently hot-pluggable (QEMU docs/pcie.txt §5.1). */ + size_t pci_slot = VMSPAWN_PCIE_PACK_BASE_SLOT + i / 8; + size_t pci_fn = i % 8; + assert(pci_slot < VMSPAWN_PCIE_PACK_END_SLOT); + r = qemu_config_keyf(config_file, "addr", "0x%zx.%zu", pci_slot, pci_fn); + if (r < 0) + return r; + if (pci_fn == 0) { + r = qemu_config_key(config_file, "multifunction", "on"); + if (r < 0) + return r; + } } } From a4424403153761c365d50522d075c1d0090d7503 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 22:21:57 +0200 Subject: [PATCH 1681/2155] blockdev-util: Drop name argument from BLKPG functions We don't use it, the kernel ignores it, let's just drop the argument. Saves callers from having to ensure the name they pass in fits in the 64 char buffer. --- src/repart/repart.c | 6 +++--- src/shared/blockdev-util.c | 26 +++----------------------- src/shared/blockdev-util.h | 4 ++-- src/shared/dissect-image.c | 2 +- src/shared/reread-partition-table.c | 6 +++--- 5 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 0cdfef56be3a7..dca90f2509e37 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5206,7 +5206,7 @@ static int partition_target_prepare( part_node, nr, p->offset, NATURAL_ALIGNMENT(p->offset), size, NATURAL_ALIGNMENT(size)); - r = block_device_add_partition(whole_fd, part_node, nr, p->offset, size); + r = block_device_add_partition(whole_fd, nr, p->offset, size); if (r < 0) return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); @@ -5215,7 +5215,7 @@ static int partition_target_prepare( dev_fd = open(part_node, O_RDWR|O_CLOEXEC|O_NOCTTY); if (dev_fd < 0) { r = -errno; - int q = block_device_remove_partition(whole_fd, part_node, nr); + int q = block_device_remove_partition(whole_fd, nr); if (q < 0) log_warning_errno(q, "Error while removing block device partition '%s', ignoring: %m", part_node); return log_error_errno(r, "Failed to open new partition '%s': %m", part_node); @@ -5225,7 +5225,7 @@ static int partition_target_prepare( b = new(BlockPartition, 1); if (!b) { - r = block_device_remove_partition(whole_fd, part_node, nr); + r = block_device_remove_partition(whole_fd, nr); if (r < 0) log_warning_errno(r, "Error while removing block device partition '%s', ignoring: %m", part_node); diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 83eb67c54152d..bff2047aa1edb 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -642,15 +642,9 @@ int path_get_whole_disk(const char *path, bool backing, dev_t *ret) { return fd_get_whole_disk(fd, backing, ret); } -int block_device_add_partition( - int fd, - const char *name, - int nr, - uint64_t start, - uint64_t size) { +int block_device_add_partition(int fd, int nr, uint64_t start, uint64_t size) { assert(fd >= 0); - assert(name); assert(nr > 0); struct blkpg_partition bp = { @@ -665,21 +659,12 @@ int block_device_add_partition( .datalen = sizeof(bp), }; - if (strlen(name) >= sizeof(bp.devname)) - return -EINVAL; - - strcpy(bp.devname, name); - return RET_NERRNO(ioctl(fd, BLKPG, &ba)); } -int block_device_remove_partition( - int fd, - const char *name, - int nr) { +int block_device_remove_partition(int fd, int nr) { assert(fd >= 0); - assert(name); assert(nr > 0); struct blkpg_partition bp = { @@ -692,11 +677,6 @@ int block_device_remove_partition( .datalen = sizeof(bp), }; - if (strlen(name) >= sizeof(bp.devname)) - return -EINVAL; - - strcpy(bp.devname, name); - return RET_NERRNO(ioctl(fd, BLKPG, &ba)); } @@ -824,7 +804,7 @@ int block_device_remove_all_partitions(sd_device *dev, int fd) { if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to forget btrfs device %s, ignoring: %m", devname); - r = block_device_remove_partition(fd, devname, nr); + r = block_device_remove_partition(fd, nr); if (r == -ENODEV) { log_debug("Kernel removed partition %s before us, ignoring", devname); continue; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 3a20a5307d2e5..e268783d5eb17 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -45,8 +45,8 @@ int path_is_encrypted(const char *path); int fd_get_whole_disk(int fd, bool backing, dev_t *ret); int path_get_whole_disk(const char *path, bool backing, dev_t *ret); -int block_device_add_partition(int fd, const char *name, int nr, uint64_t start, uint64_t size); -int block_device_remove_partition(int fd, const char *name, int nr); +int block_device_add_partition(int fd, int nr, uint64_t start, uint64_t size); +int block_device_remove_partition(int fd, int nr); int block_device_resize_partition(int fd, int nr, uint64_t start, uint64_t size); int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret); int block_device_remove_all_partitions(sd_device *dev, int fd); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index fcd809b0e61b1..23f804bd65943 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1278,7 +1278,7 @@ static int dissect_image( * partition already existent. */ if (FLAGS_SET(flags, DISSECT_IMAGE_ADD_PARTITION_DEVICES)) { - r = block_device_add_partition(fd, node, nr, (uint64_t) start * 512, (uint64_t) size * 512); + r = block_device_add_partition(fd, nr, (uint64_t) start * 512, (uint64_t) size * 512); if (r < 0) { if (r != -EBUSY) return log_debug_errno(r, "BLKPG_ADD_PARTITION failed: %m"); diff --git a/src/shared/reread-partition-table.c b/src/shared/reread-partition-table.c index 9d908ee948360..e1d51559d0434 100644 --- a/src/shared/reread-partition-table.c +++ b/src/shared/reread-partition-table.c @@ -179,7 +179,7 @@ static int process_partition( * just to make a point. */ partition = sd_device_unref(partition); - r = block_device_remove_partition(fd, subnode, nr); + r = block_device_remove_partition(fd, nr); if (r < 0) return log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' in order to recreate it: %m", subnode); @@ -189,7 +189,7 @@ static int process_partition( log_device_debug(d, "Adding partition %i...", nr); - r = block_device_add_partition(fd, subnode, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); + r = block_device_add_partition(fd, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); if (r < 0) return log_device_debug_errno(d, r, "Failed to add kernel partition device %i to partition table values: %m", nr); @@ -228,7 +228,7 @@ static int remove_partitions(sd_device *d, int fd, sd_device_enumerator *e, Set log_device_debug(partition, "Kernel knows partition %u which we didn't find, removing.", nr); - r = block_device_remove_partition(fd, devname, (int) nr); + r = block_device_remove_partition(fd, (int) nr); if (r < 0) /* NB: when logging we use the parent device below, after all the partition device ceased existing by now, most likely */ RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' that vanished from partition table: %m", devname)); else { From 95e7ead268736d8a0e71d2f6fb7a5e54247d82da Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 12:21:10 +0200 Subject: [PATCH 1682/2155] nsresourced: re-link GID delegation file after atomic UID file write userns_registry_remove() restores a sub-delegated UID range by writing the previous owner's data to u.delegate with WRITE_STRING_FILE_ATOMIC. Atomic writes go via a temp file and rename, which replaces the directory entry with a fresh inode and severs the hardlink to g.delegate. The stale GID side then keeps pointing at the prior inode with outdated owner and ancestor data, so subsequent lookups via GID return wrong results. Re-create the hardlink after the atomic write so the two views stay in sync, matching what userns_registry_store() already does after writing a new delegation. --- src/nsresourced/userns-registry.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 50efcd1153a5d..a9e3f82e59c22 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -838,9 +838,11 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { continue; } - _cleanup_free_ char *delegate_uid_fn = NULL; + _cleanup_free_ char *delegate_uid_fn = NULL, *delegate_gid_fn = NULL; if (asprintf(&delegate_uid_fn, "u" UID_FMT ".delegate", delegate->start_uid) < 0) return log_oom_debug(); + if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) + return log_oom_debug(); if (existing.n_ancestor_userns > 0) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *delegate_def = NULL, *ancestor_array = NULL; @@ -876,10 +878,18 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { return log_debug_errno(r, "Failed to format delegation JSON object: %m"); r = write_string_file_at(dir_fd, delegate_uid_fn, delegate_buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) + if (r < 0) { RET_GATHER(ret, log_debug_errno(r, "Failed to write restored delegation data to '%s' in registry: %m", delegate_uid_fn)); + continue; + } + + /* The atomic write above replaced the UID file with a new inode, so the + * hardlink to the GID file is now broken. Re-create it to keep the two in + * sync. */ + r = linkat_replace(dir_fd, delegate_uid_fn, dir_fd, delegate_gid_fn); + if (r < 0) + RET_GATHER(ret, log_debug_errno(r, "Failed to re-link '%s' to '%s' in registry: %m", delegate_uid_fn, delegate_gid_fn)); - /* GID link already points to the UID file, no need to update it */ continue; } @@ -891,10 +901,6 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_uid_fn)); - _cleanup_free_ char *delegate_gid_fn = NULL; - if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) - return log_oom_debug(); - r = RET_NERRNO(unlinkat(dir_fd, delegate_gid_fn, 0)); if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_gid_fn)); From f78e7a0949182420918aeb2dd1d44cb9ca534436 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 14:58:21 +0200 Subject: [PATCH 1683/2155] cpu-set-util: add cpu_set_count() helper Let's add a minor, simplifying helper for getting number of CPUs in a mask. --- src/shared/cpu-set-util.h | 8 ++++++++ src/shared/numa-util.c | 2 +- src/test/test-cpu-set-util.c | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h index a74e264e1267b..751e9add27f09 100644 --- a/src/shared/cpu-set-util.h +++ b/src/shared/cpu-set-util.h @@ -38,3 +38,11 @@ int cpu_set_to_dbus(const CPUSet *c, uint8_t **ret, size_t *ret_size); int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *ret); int cpus_in_affinity_mask(void); + +static inline size_t cpu_set_count(const CPUSet *c) { + if (!c) + return 0; + if (c->allocated <= 0) + return 0; + return CPU_COUNT_S(c->allocated, c->set); +} diff --git a/src/shared/numa-util.c b/src/shared/numa-util.c index c011c4381d422..228ea7ad2d14f 100644 --- a/src/shared/numa-util.c +++ b/src/shared/numa-util.c @@ -26,7 +26,7 @@ bool numa_policy_is_valid(const NUMAPolicy *policy) { if (policy->nodes.set && numa_policy_get_type(policy) == MPOL_PREFERRED && - CPU_COUNT_S(policy->nodes.allocated, policy->nodes.set) != 1) + cpu_set_count(&policy->nodes) != 1) return false; return true; diff --git a/src/test/test-cpu-set-util.c b/src/test/test-cpu-set-util.c index bd22e4ddb2aef..672451ff35576 100644 --- a/src/test/test-cpu-set-util.c +++ b/src/test/test-cpu-set-util.c @@ -11,7 +11,8 @@ #define ASSERT_CPUSET_COUNT(c, n) \ ASSERT_NOT_NULL(c.set); \ ASSERT_GE(c.allocated, CPU_ALLOC_SIZE(n)); \ - ASSERT_EQ(CPU_COUNT_S(c.allocated, c.set), (n)) + ASSERT_EQ(CPU_COUNT_S(c.allocated, c.set), (n)); \ + ASSERT_EQ(cpu_set_count(&c), (size_t) (n)) #define ASSERT_CPUSET_ISSET(c, i) \ ASSERT_TRUE(CPU_ISSET_S(i, c.allocated, c.set)); From 94ef901b1ce107551d2ea23700536a04d0b778f7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 14:59:35 +0200 Subject: [PATCH 1684/2155] cpu-set-util: introduce cpus_online(). Add a helper that tries to determine the number of installed CPUs. This borrows heavily from physical_memory(), i.e. uses the physical number, but caps by per-container cpuset. --- src/modules-load/modules-load.c | 15 ++++++----- src/shared/cpu-set-util.c | 48 +++++++++++++++++++++++++++++++++ src/shared/cpu-set-util.h | 2 ++ src/test/test-cpu-set-util.c | 7 +++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index 0917f800a1a84..eb58b70c7b06c 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -11,6 +11,7 @@ #include "build.h" #include "conf-files.h" #include "constants.h" +#include "cpu-set-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" @@ -314,13 +315,13 @@ static unsigned determine_num_worker_threads(unsigned n_modules) { if (n_threads == UINT_MAX) { /* By default, use a number of worker threads equal the number of online CPUs, * but clamp it to avoid a probing storm on machines with many CPUs. */ - long ncpus = sysconf(_SC_NPROCESSORS_ONLN); - if (ncpus < 0) { - log_warning_errno(errno, "Failed to get number of online CPUs, ignoring: %m"); - ncpus = 1; - } - - n_threads = CLAMP((unsigned)ncpus, 1U, 16U); + unsigned n_cpus; + r = cpus_online(&n_cpus); + if (r < 0) { + log_warning_errno(r, "Failed to get number of online CPUs, ignoring: %m"); + n_threads = 1; + } else + n_threads = CLAMP(n_cpus, 1U, 16U); } /* There's no reason to spawn more threads than the modules that need to be loaded */ diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index 9211dbe47e54a..85d0e11402386 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "bitfield.h" +#include "cgroup-util.h" #include "cpu-set-util.h" #include "extract-word.h" #include "log.h" @@ -400,3 +401,50 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *ret) { *ret = TAKE_STRUCT(c); return 0; } + +static int cgroup_cpus_effective(unsigned *ret) { + int r; + + assert(ret); + + _cleanup_free_ char *root = NULL; + r = cg_get_root_path(&root); + if (r < 0) + return log_debug_errno(r, "Failed to determine root cgroup: %m"); + + _cleanup_free_ char *value = NULL; + r = cg_get_attribute(root, "cpuset.cpus.effective", &value); + if (r < 0) + return log_debug_errno(r, "Failed to read cpuset.cpus.effective cgroup attribute: %m"); + + _cleanup_(cpu_set_done) CPUSet cpus = {}; + r = parse_cpu_set(value, &cpus); + if (r < 0) + return log_debug_errno(r, "Failed to parse cpuset.cpus.effective cgroup attribute: %m"); + + *ret = (unsigned) MIN(cpu_set_count(&cpus), UINT_MAX); + return 0; +} + +int cpus_online(unsigned *ret) { + int r; + + assert(ret); + + /* In order to support containers nicely that have a configured cpuset we'll take the minimum of the + * physically reported amount of CPUs and the limit configured for the root cgroup, if there is + * any. */ + + long sc = sysconf(_SC_NPROCESSORS_ONLN); + if (sc < 0) + return log_debug_errno(errno, "sysconf(_SC_NPROCESSORS_ONLN) failed: %m"); + + unsigned cg, lc = (unsigned) CLAMP((unsigned long) sc, 1U, UINT_MAX); + r = cgroup_cpus_effective(&cg); + if (r < 0) + *ret = lc; + else + *ret = CLAMP(cg, 1U, lc); + + return 0; +} diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h index 751e9add27f09..1f89ce2020701 100644 --- a/src/shared/cpu-set-util.h +++ b/src/shared/cpu-set-util.h @@ -46,3 +46,5 @@ static inline size_t cpu_set_count(const CPUSet *c) { return 0; return CPU_COUNT_S(c->allocated, c->set); } + +int cpus_online(unsigned *ret); diff --git a/src/test/test-cpu-set-util.c b/src/test/test-cpu-set-util.c index 672451ff35576..027e0ca2d4756 100644 --- a/src/test/test-cpu-set-util.c +++ b/src/test/test-cpu-set-util.c @@ -284,4 +284,11 @@ TEST(cpu_set_add_range) { ASSERT_OK(cpu_set_add_range(&c, 0, 8191)); } +TEST(cpus_online) { + unsigned n_cpus = 0; + ASSERT_OK(cpus_online(&n_cpus)); + ASSERT_GE(n_cpus, 1U); + log_info("Number of CPUs currently online: %u", n_cpus); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From e5f27e51f9f57f50944821fea31a8673154dfb7f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 15:00:08 +0200 Subject: [PATCH 1685/2155] report-basic: export PhysicalMemorybytes + CPUsOnline metrics --- src/report/report-basic.c | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 772a5d9f74222..43c9cb9774149 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -8,7 +8,9 @@ #include "alloc-util.h" #include "architecture.h" +#include "cpu-set-util.h" #include "hostname-setup.h" +#include "limits-util.h" #include "log.h" #include "metrics.h" #include "os-util.h" @@ -100,6 +102,37 @@ static int machine_id_generate(const MetricFamily *mf, sd_varlink *link, void *u /* fields= */ NULL); } +static int physical_memory_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + return metric_build_send_unsigned( + mf, + link, + /* object= */ NULL, + physical_memory(), + /* fields= */ NULL); +} + +static int cpus_online_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + int r; + + assert(mf && mf->name); + assert(link); + + unsigned n_cpus; + r = cpus_online(&n_cpus); + if (r < 0) + return r; + + return metric_build_send_unsigned( + mf, + link, + /* object= */ NULL, + n_cpus, + /* fields= */ NULL); +} + enum { FIELD_PRETTY_NAME, FIELD_NAME, @@ -203,6 +236,12 @@ static const MetricFamily metric_family_table[] = { METRIC_FAMILY_TYPE_STRING, .generate = boot_id_generate, }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "CPUsOnline", + "Number of CPUs currently online", + METRIC_FAMILY_TYPE_GAUGE, + .generate = cpus_online_generate, + }, { METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", "System hostname", @@ -239,6 +278,12 @@ static const MetricFamily metric_family_table[] = { OS_RELEASE_STANDARD_FIELD("SYSEXT_LEVEL"), OS_RELEASE_STANDARD_FIELD("CONFEXT_LEVEL"), /* Keep those ↑ in sync with os_release_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "PhysicalMemoryBytes", + "Installed physical memory in bytes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = physical_memory_generate, + }, { METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", "Virtualization type", From 29fbad2b7f806afcaf13b20b681d120a5516b1c9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 May 2026 15:06:57 +0200 Subject: [PATCH 1686/2155] update TODO --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index 422145e25b72f..024e2bcfb0687 100644 --- a/TODO.md +++ b/TODO.md @@ -136,6 +136,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later "nothing"/"invalid"/"zero"/"please-suppress". Then use that to reduce noise in systemd-report output. +- cgroup-metrics: add per-cgroup PSI metrics + - sysupdate: offer reading transfer files/components/features optionally from some JSON fragment rather than transfer files, so that we can update it independently from any DDI, and it needs no activation cycle. Why? so that From 43b53679da0a088dab6c8ff966a0ccbfcdeeafde Mon Sep 17 00:00:00 2001 From: glemco <32201227+glemco@users.noreply.github.com> Date: Sun, 10 May 2026 11:48:27 +0200 Subject: [PATCH 1687/2155] cgroup: Add CPUSetPartition= setting Add support for configuring cpuset partition type via the CPUSetPartition= unit file setting. This controls the kernel's cpuset.cpus.partition cgroup attribute. The setting takes one of "member", "root", or "isolated". This is useful for real-time workloads that require dedicated CPU resources without interference from other processes. When set, systemd will write the partition type to the cpuset.cpus.partition cgroup file. If the kernel rejects the value (e.g., due to partition hierarchy rules), a warning is logged and the unit continues with the kernel's default partition type. Co-developed-by: Claude Sonnet 4.5 Signed-off-by: Gabriele Monaco --- docs/TRANSIENT-SETTINGS.md | 1 + man/org.freedesktop.systemd1.xml | 66 ++++++++++++++++++++++----- man/systemd.resource-control.xml | 22 +++++++++ src/core/cgroup.c | 62 +++++++++++++++++++++++++ src/core/cgroup.h | 10 ++++ src/core/dbus-cgroup.c | 31 +++++++++++++ src/core/execute-serialize.c | 10 ++++ src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 1 + src/core/load-fragment.h | 1 + src/core/varlink-cgroup.c | 1 + src/shared/bus-unit-util.c | 1 + src/shared/varlink-io.systemd.Unit.c | 9 ++++ src/test/test-bus-unit-util.c | 5 ++ 14 files changed, 209 insertions(+), 12 deletions(-) diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 652ac3d95e64c..3ea51163499bb 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -307,6 +307,7 @@ All cgroup/resource control settings are available for transient units ✓ StartupAllowedCPUs= ✓ AllowedMemoryNodes= ✓ StartupAllowedMemoryNodes= +✓ CPUSetPartition= ✓ DisableControllers= ✓ Delegate= ✓ MemoryMin= diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 847e76f95c767..5474dd788ed6d 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -3010,6 +3010,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -3689,6 +3691,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4387,6 +4391,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -5294,6 +5300,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -5989,6 +5997,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6661,6 +6671,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -7391,6 +7403,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -8010,6 +8024,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8590,6 +8606,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -9453,6 +9471,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -10054,6 +10074,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10616,6 +10638,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -11332,6 +11356,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -11515,6 +11541,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11709,6 +11737,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11928,6 +11958,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -12125,6 +12157,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12343,6 +12377,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12750,8 +12786,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RefreshOnReload, and RootMStack were added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Socket Unit Objects @@ -12824,8 +12861,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RootMStack were added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Mount Unit Objects @@ -12893,8 +12931,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RootMStack were added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Swap Unit Objects @@ -12960,8 +12999,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RootMStack were added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Slice Unit Objects @@ -12997,8 +13037,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ BindNetworkInterface was added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Scope Unit Objects @@ -13032,8 +13073,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ BindNetworkInterface was added in version 260. CPUPressureThresholdUSec, CPUPressureWatch, - IOPressureThresholdUSec, and - IOPressureWatch were added in version 261. + IOPressureThresholdUSec, + IOPressureWatch, and + CPUSetPartition were added in version 261. Job Objects diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index b5d559849dc3a..fcad4b31839ea 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -521,6 +521,28 @@ CPUWeight=20 DisableControllers=cpu / \ + + CPUSetPartition= + + + Sets the partition type for the executed processes. Takes one + of member, root, or isolated. This setting + controls the cpuset.cpus.partition cgroup attribute. + + When set to member, the cpuset operates in normal mode. + root creates a partition root, which can further divide CPUs among child cgroups. + isolated provides full CPU isolation, useful for real-time workloads that + require dedicated CPU resources without interference from other processes. + Defaults to the kernel default, which is member. For more details about this + control group attribute, see + Control Groups v2. + + This setting requires AllowedCPUs= to also be set. + + + + + Process Accounting and Control diff --git a/src/core/cgroup.c b/src/core/cgroup.c index acf2e8147f41b..52d73b3747edb 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -178,6 +178,8 @@ void cgroup_context_init(CGroupContext *c) { .tasks_max = CGROUP_TASKS_MAX_UNSET, + .cpuset_partition = _CPUSET_PARTITION_INVALID, + .moom_swap = MANAGED_OOM_AUTO, .moom_mem_pressure = MANAGED_OOM_AUTO, .moom_preference = MANAGED_OOM_PREFERENCE_NONE, @@ -508,6 +510,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sStartupAllowedCPUs: %s\n" "%sAllowedMemoryNodes: %s\n" "%sStartupAllowedMemoryNodes: %s\n" + "%sCPUSetPartition: %s\n" "%sIOWeight: %" PRIu64 "\n" "%sStartupIOWeight: %" PRIu64 "\n" "%sMemoryMin: %" PRIu64 "%s\n" @@ -546,6 +549,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, strempty(startup_cpuset_cpus), prefix, strempty(cpuset_mems), prefix, strempty(startup_cpuset_mems), + prefix, strna(cpuset_partition_to_string(c->cpuset_partition)), prefix, c->io_weight, prefix, c->startup_io_weight, prefix, c->memory_min, format_cgroup_memory_limit_comparison(u, "MemoryMin", cda, sizeof(cda)), @@ -1132,6 +1136,53 @@ static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) { (void) set_attribute_and_warn(u, name, buf); } +static int cgroup_cpuset_partition_invalid(const char *partition) { + _cleanup_free_ char *part_str = NULL, *invalid = NULL; + int r; + + assert(partition); + + /* An invalid line looks like invalid () */ + r = extract_many_words(&partition, /* separators= */ NULL, /* flags= */ 0, &part_str, &invalid); + if (r < 0) + return r; + if (r < 2) + return false; + + return streq_ptr(invalid, "invalid"); +} + +static void cgroup_apply_cpuset_partition(Unit *u, const char *name, const char *partition) { + _cleanup_free_ char *buf = NULL; + CGroupRuntime *crt; + int r; + + assert(u); + assert(name); + assert(partition); + + if (set_attribute_and_warn(u, name, partition) < 0) + return; + + /* We are writing and then reading back, crt is already checked while writing */ + crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + + r = cg_get_attribute(crt->cgroup_path, name, &buf); + if (r < 0) { + log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition); + return; + } + + r = cgroup_cpuset_partition_invalid(buf); + if (r < 0) + log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition); + else if (r) + log_unit_warning(u, "Failed to set '%s' attribute on '%s' to '%.*s': %s", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition, buf); +} + static bool cgroup_context_has_io_config(CGroupContext *c) { assert(c); @@ -1464,6 +1515,9 @@ static void cgroup_context_apply( if ((apply_mask & CGROUP_MASK_CPUSET) && !is_local_root) { cgroup_apply_cpuset(u, cgroup_context_allowed_cpus(c, state), "cpuset.cpus"); cgroup_apply_cpuset(u, cgroup_context_allowed_mems(c, state), "cpuset.mems"); + + if (c->cpuset_partition >= 0) + cgroup_apply_cpuset_partition(u, "cpuset.cpus.partition", cpuset_partition_to_string(c->cpuset_partition)); } /* The 'io' controller attributes are not exported on the host's root cgroup (being a pure cgroup v2 @@ -4582,6 +4636,14 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); +static const char* const cpuset_partition_table[_CPUSET_PARTITION_MAX] = { + [CPUSET_PARTITION_MEMBER] = "member", + [CPUSET_PARTITION_ROOT] = "root", + [CPUSET_PARTITION_ISOLATED] = "isolated", +}; + +DEFINE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition); + static const char* const cgroup_pressure_watch_table[_CGROUP_PRESSURE_WATCH_MAX] = { [CGROUP_PRESSURE_WATCH_NO] = "no", [CGROUP_PRESSURE_WATCH_YES] = "yes", diff --git a/src/core/cgroup.h b/src/core/cgroup.h index d9a6ded110214..b7213d8d59494 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -47,6 +47,14 @@ typedef enum FreezerAction { _FREEZER_ACTION_INVALID = -EINVAL, } FreezerAction; +typedef enum CPUSetPartition { + CPUSET_PARTITION_MEMBER, + CPUSET_PARTITION_ROOT, + CPUSET_PARTITION_ISOLATED, + _CPUSET_PARTITION_MAX, + _CPUSET_PARTITION_INVALID = -EINVAL, +} CPUSetPartition; + typedef enum CGroupDevicePermissions { /* We reuse the same bit meanings the kernel's BPF_DEVCG_ACC_xyz definitions use */ CGROUP_DEVICE_MKNOD = 1 << 0, @@ -136,6 +144,7 @@ typedef struct CGroupContext { CPUSet startup_cpuset_cpus; CPUSet cpuset_mems; CPUSet startup_cpuset_mems; + CPUSetPartition cpuset_partition; uint64_t io_weight; uint64_t startup_io_weight; @@ -478,6 +487,7 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name); int unit_cgroup_freezer_action(Unit *u, FreezerAction action); DECLARE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction); +DECLARE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition); CGroupRuntime* cgroup_runtime_new(void); CGroupRuntime* cgroup_runtime_free(CGroupRuntime *crt); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 927c133dd9e47..6cecc8b9e7419 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -26,6 +26,7 @@ BUS_DEFINE_PROPERTY_GET(bus_property_get_tasks_max, "t", CGroupTasksMax, cgroup_ BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_cgroup_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cpuset_partition, cpuset_partition, CPUSetPartition); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_mode, managed_oom_mode, ManagedOOMMode); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_preference, managed_oom_preference, ManagedOOMPreference); @@ -385,6 +386,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("StartupAllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_cpus), 0), SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0), SD_BUS_PROPERTY("StartupAllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_mems), 0), + SD_BUS_PROPERTY("CPUSetPartition", "s", property_get_cpuset_partition, offsetof(CGroupContext, cpuset_partition), 0), SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), @@ -1493,6 +1495,35 @@ int bus_cgroup_set_property( return 1; + } else if (streq(name, "CPUSetPartition")) { + const char *partition_str; + CPUSetPartition p; + + r = sd_bus_message_read(message, "s", &partition_str); + if (r < 0) + return r; + + if (isempty(partition_str)) + p = _CPUSET_PARTITION_INVALID; + else { + p = cpuset_partition_from_string(partition_str); + if (p < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid CPUSetPartition value: %s", partition_str); + } + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->cpuset_partition = p; + unit_invalidate_cgroup(u, CGROUP_MASK_CPUSET); + + if (p == _CPUSET_PARTITION_INVALID) + unit_write_settingf(u, flags, name, "%s=", name); + else + unit_write_settingf(u, flags, name, "%s=%s", name, partition_str); + } + + return 1; + } else if (streq(name, "DeviceAllow")) { const char *path, *rwm; unsigned n = 0; diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 5d0ebfa37c0bb..5f205772fd81a 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -121,6 +121,12 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; + if (c->cpuset_partition >= 0) { + r = serialize_item(f, "exec-cgroup-context-cpuset-partition", cpuset_partition_to_string(c->cpuset_partition)); + if (r < 0) + return r; + } + if (c->io_weight != CGROUP_WEIGHT_INVALID) { r = serialize_item_format(f, "exec-cgroup-context-io-weight", "%" PRIu64, c->io_weight); if (r < 0) @@ -513,6 +519,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = parse_cpu_set(val, &c->startup_cpuset_mems); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-cpuset-partition="))) { + c->cpuset_partition = cpuset_partition_from_string(val); + if (c->cpuset_partition < 0) + return -EINVAL; } else if ((val = startswith(l, "exec-cgroup-context-io-weight="))) { r = safe_atou64(val, &c->io_weight); if (r < 0) diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 79551adad8b1a..b8d744c1f4959 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -215,6 +215,7 @@ {{type}}.StartupAllowedCPUs, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_cpus) {{type}}.AllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.cpuset_mems) {{type}}.StartupAllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_mems) +{{type}}.CPUSetPartition, config_parse_cpuset_partition, 0, offsetof({{type}}, cgroup_context.cpuset_partition) {{type}}.CPUAccounting, config_parse_warn_compat, DISABLED_LEGACY, 0 {{type}}.CPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.cpu_weight) {{type}}.StartupCPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.startup_cpu_weight) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d08e71f9c9782..2a268d813b5bb 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -131,6 +131,7 @@ DEFINE_CONFIG_PARSE(config_parse_socket_protocol, parse_socket_protocol); DEFINE_CONFIG_PARSE(config_parse_exec_secure_bits, secure_bits_from_string); DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy); +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_cpuset_partition, cpuset_partition, CPUSetPartition, _CPUSET_PARTITION_INVALID); DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc); DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset); diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index ed8060b0428ac..fafb00402830e 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -170,6 +170,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max); CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface); CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp); +CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index 3793975e8cc2e..9953707417d5d 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -258,6 +258,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_FINITE_USEC("CPUQuotaPeriodUSec", c->cpu_quota_period_usec), JSON_BUILD_PAIR_CALLBACK_NON_NULL("AllowedCPUs", cpuset_build_json, &c->cpuset_cpus), JSON_BUILD_PAIR_CALLBACK_NON_NULL("StartupAllowedCPUs", cpuset_build_json, &c->startup_cpuset_cpus), + JSON_BUILD_PAIR_ENUM("CPUSetPartition", cpuset_partition_to_string(c->cpuset_partition)), /* Memory Accounting and Control */ SD_JSON_BUILD_PAIR_BOOLEAN("MemoryAccounting", c->memory_accounting), diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 7076322d2d315..9b69ebd1a9361 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2403,6 +2403,7 @@ static const BusProperty cgroup_properties[] = { { "StartupAllowedCPUs", bus_append_parse_cpu_set }, { "AllowedMemoryNodes", bus_append_parse_cpu_set }, { "StartupAllowedMemoryNodes", bus_append_parse_cpu_set }, + { "CPUSetPartition", bus_append_string }, { "DisableControllers", bus_append_strv }, { "Delegate", bus_append_parse_delegate }, { "MemoryMin", bus_append_parse_resource_limit }, diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 883154b2f1951..e10bf76bf9b27 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -261,6 +261,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The device permissions"), SD_VARLINK_DEFINE_FIELD(permissions, SD_VARLINK_STRING, 0)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + CPUSetPartition, + SD_VARLINK_DEFINE_ENUM_VALUE(member), + SD_VARLINK_DEFINE_ENUM_VALUE(root), + SD_VARLINK_DEFINE_ENUM_VALUE(isolated)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( CGroupContext, @@ -281,6 +287,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(AllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#AllowedCPUs="), SD_VARLINK_DEFINE_FIELD(StartupAllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUSetPartition="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSetPartition, CPUSetPartition, SD_VARLINK_NULLABLE), /* Memory Accounting and Control * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Memory%20Accounting%20and%20Control */ @@ -1627,6 +1635,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupBPFProgram, &vl_type_CGroupController, &vl_type_CGroupDeviceAllow, + &vl_type_CPUSetPartition, SD_VARLINK_SYMBOL_COMMENT("CGroup context of a unit"), &vl_type_CGroupContext, SD_VARLINK_SYMBOL_COMMENT("CGroup runtime of a unit"), diff --git a/src/test/test-bus-unit-util.c b/src/test/test-bus-unit-util.c index a330bd80545d3..ae2cb19c9ce0a 100644 --- a/src/test/test-bus-unit-util.c +++ b/src/test/test-bus-unit-util.c @@ -117,6 +117,11 @@ TEST(cgroup_properties) { "StartupAllowedMemoryNodes=0", "StartupAllowedMemoryNodes=1-3", + "CPUSetPartition=member", + "CPUSetPartition=root", + "CPUSetPartition=isolated", + "CPUSetPartition=", + "DisableControllers=cpu", "DisableControllers= " " cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices " From 50857801db8e3b6be491101439d769170548c06d Mon Sep 17 00:00:00 2001 From: glemco <32201227+glemco@users.noreply.github.com> Date: Tue, 12 May 2026 19:47:43 +0200 Subject: [PATCH 1688/2155] cgroups: Refactor cgroup_apply_cpuset() argument order Cgroups function have the name key first and then the value, cgroup_apply_cpuset() has the opposite. Swap the name and value (cpuset) arguments. --- src/core/cgroup.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 52d73b3747edb..48b7df0e00c08 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -1124,7 +1124,7 @@ static void cgroup_apply_cpu_quota(Unit *u, usec_t quota, usec_t period) { (void) set_attribute_and_warn(u, "cpu.max", buf); } -static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) { +static void cgroup_apply_cpuset(Unit *u, const char *name, const CPUSet *cpus) { _cleanup_free_ char *buf = NULL; buf = cpu_set_to_range_string(cpus); @@ -1513,8 +1513,8 @@ static void cgroup_context_apply( } if ((apply_mask & CGROUP_MASK_CPUSET) && !is_local_root) { - cgroup_apply_cpuset(u, cgroup_context_allowed_cpus(c, state), "cpuset.cpus"); - cgroup_apply_cpuset(u, cgroup_context_allowed_mems(c, state), "cpuset.mems"); + cgroup_apply_cpuset(u, "cpuset.cpus", cgroup_context_allowed_cpus(c, state)); + cgroup_apply_cpuset(u, "cpuset.mems", cgroup_context_allowed_mems(c, state)); if (c->cpuset_partition >= 0) cgroup_apply_cpuset_partition(u, "cpuset.cpus.partition", cpuset_partition_to_string(c->cpuset_partition)); From 979ecbb14ee03150a277e021cdec902f7c40f481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:00:00 +0200 Subject: [PATCH 1689/2155] systemctl: split out helper for --state= and allow resetting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far we'd reject --state=, but it seems nicer to make it reset the setting as we generally do. The output variable is modified in place… Option parsing isn't atomic anyway, so I think it's fine to to that. --- man/systemctl.xml | 7 +++-- src/systemctl/systemctl.c | 59 ++++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index b8ba0f945e97b..355956c8adbdc 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2000,9 +2000,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err The argument is a comma-separated list of unit LOAD, SUB, or ACTIVE states. When listing - units with list-units, list-dependencies, show - or status, show only those in the specified states. Use - or to show only failed units. + units with list-units, list-dependencies, + show or status, show only those in the specified states. Use + or to show only failed units. Use + to reset the filter. As a special case, if one of the arguments is , a list of allowed values will be printed and the program will exit. diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 775188b5191cb..47f34e1ad3434 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -334,7 +334,7 @@ static void help_types(void) { DUMP_STRING_TABLE(unit_type, UnitType, _UNIT_TYPE_MAX); } -static void help_states(void) { +static int help_states(void) { if (arg_legend != 0) puts("Available unit load states:"); DUMP_STRING_TABLE(unit_load_state, UnitLoadState, _UNIT_LOAD_STATE_MAX); @@ -389,7 +389,38 @@ static void help_states(void) { if (arg_legend != 0) puts("\nAvailable timer unit substates:"); - DUMP_STRING_TABLE(timer_state, TimerState, _TIMER_STATE_MAX); + return DUMP_STRING_TABLE(timer_state, TimerState, _TIMER_STATE_MAX); +} + +static int parse_states_argument(const char *value, char ***states) { + int r; + + assert(value); + assert(states); + + if (isempty(value)) { + /* reset the setting */ + *states = strv_free(*states); + return 1; + } + + for (const char *p = value;;) { + _cleanup_free_ char *s = NULL; + + r = extract_first_word(&p, &s, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --state=: %m"); + if (r == 0) + break; + + if (streq(s, "help")) + return help_states(); + + if (strv_consume(states, TAKE_PTR(s)) < 0) + return log_oom(); + } + + return 1; } static int systemctl_parse_argv(int argc, char *argv[]) { @@ -869,27 +900,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_STATE: - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--state= requires arguments."); - - for (const char *p = optarg;;) { - _cleanup_free_ char *s = NULL; - - r = extract_first_word(&p, &s, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse state: %s", optarg); - if (r == 0) - break; - - if (streq(s, "help")) { - help_states(); - return 0; - } - - if (strv_consume(&arg_states, TAKE_PTR(s)) < 0) - return log_oom(); - } + r = parse_states_argument(optarg, &arg_states); + if (r <= 0) + return r; break; case 'r': From ae83ce85b9eda97ed4a8301558f7f34e8569c84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:10:00 +0200 Subject: [PATCH 1690/2155] systemctl: split out helper for --property= We explicitly handled --property= in a specific way, so preserve that behaviour. --- src/systemctl/systemctl.c | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 47f34e1ad3434..00da6d39b498c 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -327,6 +327,34 @@ static int systemctl_help(void) { return 0; } +static int parse_property_argument(const char *value, char ***properties) { + int r; + + assert(value); + assert(properties); + + if (isempty(value) && !*properties) { + /* Make sure that if the empty property list was specified, we won't show any properties. */ + *properties = strv_new(NULL); + if (!*properties) + return log_oom(); + } else + for (const char *p = value;;) { + _cleanup_free_ char *prop = NULL; + + r = extract_first_word(&p, &prop, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse property: '%s'", value); + if (r == 0) + break; + + if (strv_consume(properties, TAKE_PTR(prop)) < 0) + return log_oom(); + } + + return 0; +} + static void help_types(void) { if (arg_legend != 0) puts("Available unit types:"); @@ -613,25 +641,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) { _fallthrough_; case 'p': - /* Make sure that if the empty property list was specified, we won't show any - properties. */ - if (isempty(optarg) && !arg_properties) { - arg_properties = new0(char*, 1); - if (!arg_properties) - return log_oom(); - } else - for (const char *p = optarg;;) { - _cleanup_free_ char *prop = NULL; - - r = extract_first_word(&p, &prop, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse property: %s", optarg); - if (r == 0) - break; - - if (strv_consume(&arg_properties, TAKE_PTR(prop)) < 0) - return log_oom(); - } + r = parse_property_argument(optarg, &arg_properties); + if (r < 0) + return r; /* If the user asked for a particular property, show it, even if it is empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); From 04bf62b01270e8aeac0ee203b8be908b9814e5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:18:30 +0200 Subject: [PATCH 1691/2155] systemctl: split out helper for --type= and allow resetting Analogous to grandparent commit. --- man/systemctl.xml | 3 +- src/systemctl/systemctl.c | 101 +++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 355956c8adbdc..a6d295927ae59 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1988,7 +1988,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err The argument is a comma-separated list of unit types such as and . When units are listed with list-units, list-dependencies, show, or status, - only units of the specified types will be shown. By default, units of all types are shown. + only units of the specified types will be shown. By default, units of all types are shown. + Use to reset the filter. As a special case, if one of the arguments is , a list of allowed values will be printed and the program will exit. diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 00da6d39b498c..5c90092f2af38 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -355,13 +355,6 @@ static int parse_property_argument(const char *value, char ***properties) { return 0; } -static void help_types(void) { - if (arg_legend != 0) - puts("Available unit types:"); - - DUMP_STRING_TABLE(unit_type, UnitType, _UNIT_TYPE_MAX); -} - static int help_states(void) { if (arg_legend != 0) puts("Available unit load states:"); @@ -451,6 +444,60 @@ static int parse_states_argument(const char *value, char ***states) { return 1; } +static int help_types(void) { + if (arg_legend != 0) + puts("Available unit types:"); + + return DUMP_STRING_TABLE(unit_type, UnitType, _UNIT_TYPE_MAX); +} + +static int parse_types_argument(const char *value, char ***types, char ***states) { + int r; + + assert(value); + assert(types); + assert(states); + + if (isempty(value)) { + /* reset the setting */ + *types = strv_free(*types); + return 1; + } + + for (const char *p = value;;) { + _cleanup_free_ char *type = NULL; + + r = extract_first_word(&p, &type, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse -t/--type=: %m"); + if (r == 0) + break; + + if (streq(type, "help")) + return help_types(); + + if (unit_type_from_string(type) >= 0) { + if (strv_consume(types, TAKE_PTR(type)) < 0) + return log_oom(); + continue; + } + + /* It's much nicer to use --state= for load states, but let's support this in + * --types= too for compatibility with old versions */ + if (unit_load_state_from_string(type) >= 0) { + if (strv_consume(states, TAKE_PTR(type)) < 0) + return log_oom(); + continue; + } + + log_error("Unknown unit type or load state '%s'.", type); + return log_info_errno(SYNTHETIC_ERRNO(EINVAL), + "Use -t help to see a list of allowed values."); + } + + return 1; +} + static int systemctl_parse_argv(int argc, char *argv[]) { enum { ARG_FAIL = 0x100, /* compatibility only */ @@ -597,43 +644,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return version(); case 't': - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--type= requires arguments."); - - for (const char *p = optarg;;) { - _cleanup_free_ char *type = NULL; - - r = extract_first_word(&p, &type, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse type: %s", optarg); - if (r == 0) - break; - - if (streq(type, "help")) { - help_types(); - return 0; - } - - if (unit_type_from_string(type) >= 0) { - if (strv_consume(&arg_types, TAKE_PTR(type)) < 0) - return log_oom(); - continue; - } - - /* It's much nicer to use --state= for load states, but let's support this in - * --types= too for compatibility with old versions */ - if (unit_load_state_from_string(type) >= 0) { - if (strv_consume(&arg_states, TAKE_PTR(type)) < 0) - return log_oom(); - continue; - } - - log_error("Unknown unit type or load state '%s'.", type); - return log_info_errno(SYNTHETIC_ERRNO(EINVAL), - "Use -t help to see a list of allowed values."); - } - + r = parse_types_argument(optarg, &arg_types, &arg_states); + if (r <= 0) + return r; break; case 'P': From f01b3122e33f56b0e5d871d6e81a0a6711b9312b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:32:44 +0200 Subject: [PATCH 1692/2155] systemctl: split out helper for --what and allow resetting Analogous to parent commit. --- man/systemctl.xml | 10 +++--- src/systemctl/systemctl.c | 71 ++++++++++++++++++++++++--------------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index a6d295927ae59..ce485aee14fa7 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2578,10 +2578,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err all as a shortcut for specifying all six resource types. If this option is not specified defaults to the combination of cache, runtime and fdstore, i.e. the three kinds of resources that are generally considered - to be redundant and can be reconstructed on next invocation. Note that the explicit removal of the - fdstore resource type is only useful if the - FileDescriptorStorePreserve= option is enabled, since the file descriptor store - is otherwise cleaned automatically when the unit is stopped. + to be redundant and can be reconstructed on next invocation. The empty argument can be used to + reset to this default. + + Note that the explicit removal of the fdstore resource type is only + useful if the FileDescriptorStorePreserve= option is enabled, since the file + descriptor store is otherwise cleaned automatically when the unit is stopped. diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 5c90092f2af38..632e54256cb8d 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -498,6 +498,46 @@ static int parse_types_argument(const char *value, char ***types, char ***states return 1; } +static int parse_what_argument(const char *value, char ***clean_what) { + int r; + + assert(value); + assert(clean_what); + + if (isempty(value)) { + /* reset the setting */ + *clean_what = strv_free(*clean_what); + return 1; + } + + for (const char *p = value;;) { + _cleanup_free_ char *k = NULL; + + r = extract_first_word(&p, &k, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --what=: %m"); + if (r == 0) + break; + + if (streq(k, "help")) { + puts("runtime\n" + "state\n" + "cache\n" + "logs\n" + "configuration\n" + "fdstore\n" + "all"); + return 0; + } + + r = strv_consume(clean_what, TAKE_PTR(k)); + if (r < 0) + return log_oom(); + } + + return 1; +} + static int systemctl_parse_argv(int argc, char *argv[]) { enum { ARG_FAIL = 0x100, /* compatibility only */ @@ -967,34 +1007,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_WHAT: - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--what= requires arguments (see --what=help)."); - - for (const char *p = optarg;;) { - _cleanup_free_ char *k = NULL; - - r = extract_first_word(&p, &k, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse directory type: %s", optarg); - if (r == 0) - break; - - if (streq(k, "help")) { - puts("runtime\n" - "state\n" - "cache\n" - "logs\n" - "configuration\n" - "fdstore\n" - "all"); - return 0; - } - - r = strv_consume(&arg_clean_what, TAKE_PTR(k)); - if (r < 0) - return log_oom(); - } - + r = parse_what_argument(optarg, &arg_clean_what); + if (r <= 0) + return r; break; case ARG_REBOOT_ARG: From 137736dd645ea245e9a8f5fd6db3141bc4acb5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:39:26 +0200 Subject: [PATCH 1693/2155] systemctl: reorder cases in parse_argv() to match order in --help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compatibility-only options (--fail, --irreversible, --ignore-dependencies, --no-legend) are grouped at the end alongside the '.' / '?' error handlers. The case 'P': … _fallthrough_; case 'p': pair is kept intact and placed at -p's slot in --help, so -P sits immediately before -p in the source. Co-developed-by: Claude Opus 4.7 --- src/systemctl/systemctl.c | 371 +++++++++++++++++++------------------- 1 file changed, 186 insertions(+), 185 deletions(-) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 632e54256cb8d..799249fe42d13 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -683,12 +683,55 @@ static int systemctl_parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + r = parse_machine_argument(optarg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + case 't': r = parse_types_argument(optarg, &arg_types, &arg_states); if (r <= 0) return r; break; + case ARG_STATE: + r = parse_states_argument(optarg, &arg_states); + if (r <= 0) + return r; + break; + + case ARG_FAILED: + if (strv_extend(&arg_states, "failed") < 0) + return log_oom(); + + break; + case 'P': SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); _fallthrough_; @@ -708,13 +751,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_all = true; break; - case ARG_REVERSE: - arg_dependency = DEPENDENCY_REVERSE; + case 'l': + arg_full = true; break; - case ARG_AFTER: - arg_dependency = DEPENDENCY_AFTER; - arg_jobs_after = true; + case 'r': + if (geteuid() != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "--recursive requires root privileges."); + + arg_recursive = true; + break; + + case ARG_REVERSE: + arg_dependency = DEPENDENCY_REVERSE; break; case ARG_BEFORE: @@ -722,107 +772,100 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_jobs_before = true; break; - case ARG_SHOW_TYPES: - arg_show_types = true; + case ARG_AFTER: + arg_dependency = DEPENDENCY_AFTER; + arg_jobs_after = true; break; - case ARG_VALUE: - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + case ARG_WITH_DEPENDENCIES: + arg_with_dependencies = true; break; case ARG_JOB_MODE: _arg_job_mode = optarg; break; - case ARG_FAIL: - _arg_job_mode = "fail"; - break; - - case ARG_IRREVERSIBLE: - _arg_job_mode = "replace-irreversibly"; + case 'T': + arg_show_transaction = true; break; - case ARG_IGNORE_DEPENDENCIES: - _arg_job_mode = "ignore-dependencies"; + case ARG_SHOW_TYPES: + arg_show_types = true; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + case ARG_VALUE: + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + case ARG_CHECK_INHIBITORS: + r = parse_tristate_argument_with_auto("--check-inhibitors=", optarg, &arg_check_inhibitors); + if (r < 0) + return r; break; - case ARG_GLOBAL: - arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; + case 'i': + arg_check_inhibitors = 0; break; - case 'C': - r = capsule_name_is_valid(optarg); - if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); - - arg_host = optarg; - arg_transport = BUS_TRANSPORT_CAPSULE; - arg_runtime_scope = RUNTIME_SCOPE_USER; + case 's': + r = parse_signal_argument(optarg, &arg_signal); + if (r <= 0) + return r; break; - case ARG_WAIT: - arg_wait = true; + case ARG_KILL_WHOM: + arg_kill_whom = optarg; break; - case ARG_NO_BLOCK: - arg_no_block = true; - break; + case ARG_KILL_VALUE: { + unsigned u; - case ARG_NO_LEGEND: - arg_legend = false; - break; + if (isempty(optarg)) { + arg_kill_value_set = false; + return 0; + } - case ARG_LEGEND: - r = parse_boolean_argument("--legend", optarg, NULL); + /* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */ + r = safe_atou_full(optarg, 0, &u); if (r < 0) - return r; - arg_legend = r; - break; + /* If this didn't work, try as signed integer, without those prefixes */ + r = safe_atoi(optarg, &arg_kill_value); + else if (u > INT_MAX) + r = -ERANGE; + else + arg_kill_value = (int) u; + if (r < 0) + return log_error_errno(r, "Unable to parse signal queue value: %s", optarg); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + arg_kill_value_set = true; break; + } - case ARG_NO_WALL: - arg_no_wall = true; - break; + case ARG_KILL_SUBGROUP: { + if (empty_or_root(optarg)) { + arg_kill_subgroup = mfree(arg_kill_subgroup); + break; + } - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); - if (r < 0) - return r; - break; + _cleanup_free_ char *p = NULL; + if (path_simplify_alloc(optarg, &p) < 0) + return log_oom(); - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); - if (r < 0) - return r; - break; + if (!path_is_safe(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Control group sub-path '%s' is not valid.", p); - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); - if (r < 0) - return r; + free_and_replace(arg_kill_subgroup, p); break; + } - case 'l': - arg_full = true; + case ARG_WHAT: + r = parse_what_argument(optarg, &arg_clean_what); + if (r <= 0) + return r; break; - case ARG_FAILED: - if (strv_extend(&arg_states, "failed") < 0) - return log_oom(); - + case ARG_NOW: + arg_now = true; break; case ARG_DRY_RUN: @@ -841,65 +884,85 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_verbose = true; break; - case 'f': - arg_force++; + case ARG_NO_WARN: + arg_no_warn = true; break; - case ARG_NO_RELOAD: - arg_no_reload = true; + case ARG_WAIT: + arg_wait = true; break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + case ARG_NO_BLOCK: + arg_no_block = true; break; - case ARG_KILL_VALUE: { - unsigned u; - - if (isempty(optarg)) { - arg_kill_value_set = false; - return 0; - } + case ARG_NO_WALL: + arg_no_wall = true; + break; - /* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */ - r = safe_atou_full(optarg, 0, &u); - if (r < 0) - /* If this didn't work, try as signed integer, without those prefixes */ - r = safe_atoi(optarg, &arg_kill_value); - else if (u > INT_MAX) - r = -ERANGE; - else - arg_kill_value = (int) u; - if (r < 0) - return log_error_errno(r, "Unable to parse signal queue value: %s", optarg); + case ARG_MESSAGE: + if (strv_extend(&arg_wall, optarg) < 0) + return log_oom(); + break; - arg_kill_value_set = true; + case ARG_NO_RELOAD: + arg_no_reload = true; break; - } - case 's': - r = parse_signal_argument(optarg, &arg_signal); - if (r <= 0) + case ARG_LEGEND: + r = parse_boolean_argument("--legend", optarg, NULL); + if (r < 0) return r; + arg_legend = r; + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + case ARG_GLOBAL: + arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + case ARG_RUNTIME: + arg_runtime = true; + break; + + case 'f': + arg_force++; + break; + + case ARG_PRESET_MODE: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); + + arg_preset_mode = unit_file_preset_mode_from_string(optarg); + if (arg_preset_mode < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse preset mode: %s.", optarg); + + break; + + case ARG_ROOT: + r = parse_path_argument(optarg, false, &arg_root); if (r < 0) return r; break; - case ARG_RUNTIME: - arg_runtime = true; + case ARG_IMAGE: + r = parse_path_argument(optarg, false, &arg_image); + if (r < 0) + return r; + break; + + case ARG_IMAGE_POLICY: + r = parse_image_policy_argument(optarg, &arg_image_policy); + if (r < 0) + return r; break; case 'n': @@ -925,20 +988,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } break; - case 'i': - arg_check_inhibitors = 0; - break; - - case ARG_CHECK_INHIBITORS: - r = parse_tristate_argument_with_auto("--check-inhibitors=", optarg, &arg_check_inhibitors); - if (r < 0) - return r; - break; - - case ARG_PLAIN: - arg_plain = true; - break; - case ARG_FIRMWARE_SETUP: arg_firmware_setup = true; break; @@ -964,54 +1013,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_boot_loader_entry = empty_to_null(optarg); break; - case ARG_STATE: - r = parse_states_argument(optarg, &arg_states); - if (r <= 0) - return r; - break; - - case 'r': - if (geteuid() != 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "--recursive requires root privileges."); - - arg_recursive = true; - break; - - case ARG_PRESET_MODE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); - - arg_preset_mode = unit_file_preset_mode_from_string(optarg); - if (arg_preset_mode < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse preset mode: %s.", optarg); - - break; - - case ARG_NOW: - arg_now = true; - break; - - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) - return log_oom(); - break; - - case 'T': - arg_show_transaction = true; - break; - - case ARG_WITH_DEPENDENCIES: - arg_with_dependencies = true; - break; - - case ARG_WHAT: - r = parse_what_argument(optarg, &arg_clean_what); - if (r <= 0) - return r; - break; - case ARG_REBOOT_ARG: arg_reboot_argument = optarg; break; @@ -1031,6 +1032,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return r; break; + case ARG_PLAIN: + arg_plain = true; + break; + case ARG_TIMESTAMP_STYLE: if (streq(optarg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); @@ -1054,10 +1059,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_marked = true; break; - case ARG_NO_WARN: - arg_no_warn = true; - break; - case ARG_DROP_IN: arg_drop_in = optarg; break; @@ -1092,22 +1093,22 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_stdin = true; break; - case ARG_KILL_SUBGROUP: { - if (empty_or_root(optarg)) { - arg_kill_subgroup = mfree(arg_kill_subgroup); - break; - } + /* Compatibility-only options, not shown in --help. */ + case ARG_FAIL: + _arg_job_mode = "fail"; + break; - _cleanup_free_ char *p = NULL; - if (path_simplify_alloc(optarg, &p) < 0) - return log_oom(); + case ARG_IRREVERSIBLE: + _arg_job_mode = "replace-irreversibly"; + break; - if (!path_is_safe(p)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Control group sub-path '%s' is not valid.", p); + case ARG_IGNORE_DEPENDENCIES: + _arg_job_mode = "ignore-dependencies"; + break; - free_and_replace(arg_kill_subgroup, p); + case ARG_NO_LEGEND: + arg_legend = false; break; - } case '.': /* Output an error mimicking getopt, and print a hint afterwards */ From 91249f8004479d59a196151de631bf1d8e50187b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 17:50:24 +0200 Subject: [PATCH 1694/2155] systemctl: convert parse_argv to OPTION macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verbs[] table still lives in systemctl-main.c — only the option parsing side is migrated. systemctl_dispatch_parse_argv() gains a remaining_args out-param so run() can pass the parsed positional args to systemctl_main(), which dispatches via _dispatch_verb_with_args() instead of dispatch_verb(). The Options section of --help now renders from the OPTION declarations; the verb sections still use raw printfs and will be converted alongside the verbs[] migration. Co-developed-by: Claude Opus 4.7 --- src/systemctl/fuzz-systemctl-parse-argv.c | 2 +- src/systemctl/systemctl-main.c | 15 +- src/systemctl/systemctl.c | 553 ++++++++-------------- src/systemctl/systemctl.h | 2 +- 4 files changed, 195 insertions(+), 377 deletions(-) diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index d30b9d8fed040..38c0decd9490d 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -56,7 +56,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { optind = 0; /* this tells the getopt machinery to reinitialize */ arg_transport = BUS_TRANSPORT_LOCAL; - r = systemctl_dispatch_parse_argv(strv_length(argv), argv); + r = systemctl_dispatch_parse_argv(strv_length(argv), argv, /* remaining_args= */ NULL); if (r < 0) log_error_errno(r, "Failed to parse args: %m"); else diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 4d1830071f47e..27f74519115ee 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "dissect-image.h" @@ -11,6 +10,7 @@ #include "main-func.h" #include "mount-util.h" #include "stat-util.h" +#include "strv.h" #include "systemctl.h" #include "systemctl-add-dependency.h" #include "systemctl-cancel-job.h" @@ -47,7 +47,7 @@ #include "verbs.h" #include "virt.h" -static int systemctl_main(int argc, char *argv[]) { +static int systemctl_main(char **args) { static const Verb verbs[] = { { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_units }, { "list-unit-files", VERB_ANY, VERB_ANY, 0, verb_list_unit_files }, @@ -135,24 +135,25 @@ static int systemctl_main(int argc, char *argv[]) { {} }; - const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1); + const Verb *verb = verbs_find_verb(args[0], 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=.", - argv[optind] ?: verb->verb); + args[0] ?: verb->verb); - return dispatch_verb(argc, argv, verbs, NULL); + return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); } static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = STRV_EMPTY; int r; setlocale(LC_ALL, ""); log_setup(); - r = systemctl_dispatch_parse_argv(argc, argv); + r = systemctl_dispatch_parse_argv(argc, argv, &args); if (r <= 0) goto finish; @@ -200,7 +201,7 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_SYSTEMCTL: - r = systemctl_main(argc, argv); + r = systemctl_main(args); break; /* Legacy command aliases set arg_action. They provide some fallbacks, e.g. to tell sysvinit to diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 799249fe42d13..8a241495c0111 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,9 +9,11 @@ #include "bus-util.h" #include "capsule-util.h" #include "extract-word.h" +#include "format-table.h" #include "help-util.h" #include "image-policy.h" #include "install.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" @@ -108,9 +109,16 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { + _cleanup_(table_unrefp) Table *options_table = NULL; + int r; + + r = option_parser_get_help_table_ns("systemctl", &options_table); + if (r < 0) + return r; + pager_open(arg_pager_flags); - help_cmdline("[OPTIONS...] COMMAND ..."); + help_cmdline("[OPTIONS…] COMMAND …"); help_abstract("Query or send control commands to the system manager."); help_section("Unit Commands"); @@ -227,100 +235,9 @@ static int systemctl_help(void) { " 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" - " -C --capsule=NAME Connect to service manager of specified capsule\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on a local container\n" - " -t --type=TYPE List units of a particular type\n" - " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" - " --failed Shortcut for --state=failed\n" - " -p --property=NAME Show only properties by this name\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -a --all Show all properties/all units currently in memory,\n" - " including dead/empty ones. To list all units installed\n" - " on the system, use 'list-unit-files' instead.\n" - " -l --full Don't ellipsize unit names on output\n" - " -r --recursive Show unit list of host and local containers\n" - " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --before Show units ordered before with 'list-dependencies'\n" - " --after Show units ordered after with 'list-dependencies'\n" - " --with-dependencies Show unit dependencies with 'status', 'cat',\n" - " 'list-units', and 'list-unit-files'.\n" - " --job-mode=MODE Specify how to deal with already queued jobs, when\n" - " queueing a new job\n" - " -T --show-transaction When enqueuing a unit job, show full transaction\n" - " --show-types When showing sockets, explicitly show their type\n" - " --value When showing properties, only print the value\n" - " --check-inhibitors=MODE\n" - " Whether to check inhibitors before shutting down,\n" - " sleeping, or hibernating\n" - " -i Shortcut for --check-inhibitors=no\n" - " -s --signal=SIGNAL Which signal to send\n" - " --kill-whom=WHOM Whom to send signal to\n" - " --kill-value=INT Signal value to enqueue\n" - " --kill-subgroup=PATH\n" - " Send signal to sub-control group only\n" - " --what=RESOURCES Which types of resources to remove\n" - " --now Start or stop unit after enabling or disabling it\n" - " --dry-run Only print what would be done\n" - " Currently supported by verbs: halt, poweroff, reboot,\n" - " kexec, soft-reboot, suspend, hibernate, \n" - " suspend-then-hibernate, hybrid-sleep, default,\n" - " rescue, emergency, and exit.\n" - " -q --quiet Suppress output\n" - " -v --verbose Show unit logs while executing operation\n" - " --no-warn Suppress several warnings shown by default\n" - " --wait For (re)start, wait until service stopped again\n" - " For is-system-running, wait until startup is completed\n" - " For kill, wait until service stopped\n" - " --no-block Do not wait until operation finished\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --message=MESSAGE Specify human-readable reason for system shutdown\n" - " --no-reload Don't reload daemon after en-/dis-abling unit files\n" - " --legend=BOOL Enable/disable the legend (column headers and hints)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not ask for system passwords\n" - " --global Edit/enable/disable/mask default user unit files\n" - " globally\n" - " --runtime Edit/enable/disable/mask unit files temporarily until\n" - " next reboot\n" - " -f --force When enabling unit files, override existing symlinks\n" - " When shutting down, execute action immediately\n" - " --preset-mode= Apply only enable, only disable, or all presets\n" - " --root=PATH Edit/enable/disable/mask unit files in the specified\n" - " root directory\n" - " --image=PATH Edit/enable/disable/mask unit files in the specified\n" - " disk image\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " verbose, export, json, json-pretty, json-sse, cat)\n" - " --firmware-setup Tell the firmware to show the setup menu on next boot\n" - " --boot-loader-menu=TIME\n" - " Boot into boot loader menu on next boot\n" - " --boot-loader-entry=NAME\n" - " 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" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before mounting, if missing\n" - " --marked Restart/reload previously marked units\n" - " --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"); + r = table_print_or_warn(options_table); + if (r < 0) + return r; help_man_page_reference("systemctl", "1"); @@ -538,134 +455,8 @@ static int parse_what_argument(const char *value, char ***clean_what) { return 1; } -static int systemctl_parse_argv(int argc, char *argv[]) { - enum { - ARG_FAIL = 0x100, /* compatibility only */ - ARG_REVERSE, - ARG_AFTER, - ARG_BEFORE, - ARG_CHECK_INHIBITORS, - ARG_DRY_RUN, - ARG_SHOW_TYPES, - ARG_IRREVERSIBLE, /* compatibility only */ - ARG_IGNORE_DEPENDENCIES, /* compatibility only */ - ARG_VALUE, - ARG_VERSION, - ARG_USER, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_NO_BLOCK, - ARG_LEGEND, - ARG_NO_LEGEND, /* compatibility only */ - ARG_NO_PAGER, - ARG_NO_WALL, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_NO_RELOAD, - ARG_KILL_WHOM, - ARG_KILL_VALUE, - ARG_NO_ASK_PASSWORD, - ARG_FAILED, - ARG_RUNTIME, - ARG_PLAIN, - ARG_STATE, - ARG_JOB_MODE, - ARG_PRESET_MODE, - ARG_FIRMWARE_SETUP, - ARG_BOOT_LOADER_MENU, - ARG_BOOT_LOADER_ENTRY, - ARG_NOW, - ARG_MESSAGE, - ARG_WITH_DEPENDENCIES, - ARG_WAIT, - ARG_WHAT, - ARG_REBOOT_ARG, - ARG_KERNEL_CMDLINE, - ARG_TIMESTAMP_STYLE, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_MARKED, - ARG_NO_WARN, - ARG_DROP_IN, - ARG_WHEN, - ARG_STDIN, - ARG_KILL_SUBGROUP, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, - { "full", no_argument, NULL, 'l' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ - { "ignore-inhibitors", no_argument, NULL, 'i' }, /* compatibility only */ - { "check-inhibitors", required_argument, NULL, ARG_CHECK_INHIBITORS }, - { "value", no_argument, NULL, ARG_VALUE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "capsule", required_argument, NULL, 'C' }, - { "wait", no_argument, NULL, ARG_WAIT }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, /* compatibility only */ - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, 'v' }, - { "no-warn", no_argument, NULL, ARG_NO_WARN }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "force", no_argument, NULL, 'f' }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "kill-value", required_argument, NULL, ARG_KILL_VALUE }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { "state", required_argument, NULL, ARG_STATE }, - { "recursive", no_argument, NULL, 'r' }, - { "with-dependencies", no_argument, NULL, ARG_WITH_DEPENDENCIES }, - { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, - { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, - { "boot-loader-menu", required_argument, NULL, ARG_BOOT_LOADER_MENU }, - { "boot-loader-entry", required_argument, NULL, ARG_BOOT_LOADER_ENTRY }, - { "now", no_argument, NULL, ARG_NOW }, - { "message", required_argument, NULL, ARG_MESSAGE }, - { "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 }, - { "marked", no_argument, NULL, ARG_MARKED }, - { "drop-in", required_argument, NULL, ARG_DROP_IN }, - { "when", required_argument, NULL, ARG_WHEN }, - { "stdin", no_argument, NULL, ARG_STDIN }, - { "kill-subgroup", required_argument, NULL, ARG_KILL_SUBGROUP }, - {} - }; - - int c, r; +static int systemctl_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); @@ -673,182 +464,191 @@ static int systemctl_parse_argv(int argc, char *argv[]) { /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ arg_ask_password = true; - while ((c = getopt_long(argc, argv, "hC:t:p:P:alqvfs:H:M:n:o:iTr.::", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "systemctl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemctl"): {} + + OPTION_COMMON_HELP: return systemctl_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to the system service manager"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to the user service manager"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION('C', "capsule", "NAME", "Connect to service manager of specified capsule"): + r = capsule_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 't': - r = parse_types_argument(optarg, &arg_types, &arg_states); + OPTION('t', "type", "TYPE", "List units of a particular type"): + r = parse_types_argument(opts.arg, &arg_types, &arg_states); if (r <= 0) return r; break; - case ARG_STATE: - r = parse_states_argument(optarg, &arg_states); + OPTION_LONG("state", "STATE", "List units with particular LOAD or SUB or ACTIVE state"): + r = parse_states_argument(opts.arg, &arg_states); if (r <= 0) return r; break; - case ARG_FAILED: + OPTION_LONG("failed", NULL, "Shortcut for --state=failed"): if (strv_extend(&arg_states, "failed") < 0) return log_oom(); - break; - case 'P': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - _fallthrough_; - - case 'p': - r = parse_property_argument(optarg, &arg_properties); + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = parse_property_argument(opts.arg, &arg_properties); if (r < 0) return r; /* If the user asked for a particular property, show it, even if it is empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + if (opts.opt->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; - case 'a': + OPTION('a', "all", NULL, + "Show all properties/all units currently in memory, including dead/empty ones. " + "To list all units installed on the system, use 'list-unit-files' instead"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); arg_all = true; break; - case 'l': + OPTION('l', "full", NULL, "Don't ellipsize unit names on output"): arg_full = true; break; - case 'r': + OPTION('r', "recursive", NULL, "Show unit list of host and local containers"): if (geteuid() != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "--recursive requires root privileges."); + "--recursive requires root privileges"); arg_recursive = true; break; - case ARG_REVERSE: + OPTION_LONG("reverse", NULL, "Show reverse dependencies with 'list-dependencies'"): arg_dependency = DEPENDENCY_REVERSE; break; - case ARG_BEFORE: + OPTION_LONG("before", NULL, "Show units ordered before with 'list-dependencies'"): arg_dependency = DEPENDENCY_BEFORE; arg_jobs_before = true; break; - case ARG_AFTER: + OPTION_LONG("after", NULL, "Show units ordered after with 'list-dependencies'"): arg_dependency = DEPENDENCY_AFTER; arg_jobs_after = true; break; - case ARG_WITH_DEPENDENCIES: + OPTION_LONG("with-dependencies", NULL, + "Show unit dependencies with 'status', 'cat', 'list-units', and 'list-unit-files'"): arg_with_dependencies = true; break; - case ARG_JOB_MODE: - _arg_job_mode = optarg; + OPTION_LONG("job-mode", "MODE", + "Specify how to deal with already queued jobs, when queueing a new job"): + _arg_job_mode = opts.arg; break; - case 'T': + OPTION('T', "show-transaction", NULL, "When enqueuing a unit job, show full transaction"): arg_show_transaction = true; break; - case ARG_SHOW_TYPES: + OPTION_LONG("show-types", NULL, "When showing sockets, explicitly show their type"): arg_show_types = true; break; - 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 ARG_CHECK_INHIBITORS: - r = parse_tristate_argument_with_auto("--check-inhibitors=", optarg, &arg_check_inhibitors); + OPTION_LONG("check-inhibitors", "MODE", + "Whether to check inhibitors before shutting down, sleeping, or hibernating"): + r = parse_tristate_argument_with_auto("--check-inhibitors=", opts.arg, &arg_check_inhibitors); if (r < 0) return r; break; - case 'i': + OPTION_LONG("ignore-inhibitors", NULL, /* help= */ NULL): {} + + OPTION_SHORT('i', NULL, "Shortcut for --check-inhibitors=no"): arg_check_inhibitors = 0; break; - case 's': - r = parse_signal_argument(optarg, &arg_signal); + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); if (r <= 0) return r; break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case ARG_KILL_VALUE: { + OPTION_LONG("kill-value", "INT", "Signal value to enqueue"): { unsigned u; - if (isempty(optarg)) { + if (isempty(opts.arg)) { arg_kill_value_set = false; return 0; } /* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */ - r = safe_atou_full(optarg, 0, &u); + r = safe_atou_full(opts.arg, 0, &u); if (r < 0) /* If this didn't work, try as signed integer, without those prefixes */ - r = safe_atoi(optarg, &arg_kill_value); + r = safe_atoi(opts.arg, &arg_kill_value); else if (u > INT_MAX) r = -ERANGE; else arg_kill_value = (int) u; if (r < 0) - return log_error_errno(r, "Unable to parse signal queue value: %s", optarg); + return log_error_errno(r, "Unable to parse signal queue value: %s", opts.arg); arg_kill_value_set = true; break; } - case ARG_KILL_SUBGROUP: { - if (empty_or_root(optarg)) { + OPTION_LONG("kill-subgroup", "PATH", "Send signal to sub-control group only"): { + if (empty_or_root(opts.arg)) { arg_kill_subgroup = mfree(arg_kill_subgroup); break; } _cleanup_free_ char *p = NULL; - if (path_simplify_alloc(optarg, &p) < 0) + if (path_simplify_alloc(opts.arg, &p) < 0) return log_oom(); if (!path_is_safe(p)) @@ -858,21 +658,24 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; } - case ARG_WHAT: - r = parse_what_argument(optarg, &arg_clean_what); + OPTION_LONG("what", "RESOURCES", "Which types of resources to remove"): + r = parse_what_argument(opts.arg, &arg_clean_what); if (r <= 0) return r; break; - case ARG_NOW: + OPTION_LONG("now", NULL, "Start or stop unit after enabling or disabling it"): arg_now = true; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, + "Only print what would be done. Currently supported by verbs: halt, poweroff, " + "reboot, kexec, soft-reboot, suspend, hibernate, suspend-then-hibernate, " + "hybrid-sleep, default, rescue, emergency, and exit."): arg_dry_run = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; if (arg_legend < 0) @@ -880,107 +683,118 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; - case 'v': + OPTION('v', "verbose", NULL, "Show unit logs while executing operation"): arg_verbose = true; break; - case ARG_NO_WARN: + OPTION_LONG("no-warn", NULL, "Suppress several warnings shown by default"): arg_no_warn = true; break; - case ARG_WAIT: + OPTION_LONG("wait", NULL, + "For (re)start, wait until service stopped again. " + "For is-system-running, wait until startup is completed. " + "For kill, wait until service stopped."): arg_wait = true; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - case ARG_NO_WALL: + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): arg_no_wall = true; break; - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) + OPTION_LONG("message", "MESSAGE", "Specify human-readable reason for system shutdown"): + if (strv_extend(&arg_wall, opts.arg) < 0) return log_oom(); break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, "Don't reload daemon after en-/dis-abling unit files"): arg_no_reload = true; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend", optarg, NULL); + OPTION_LONG("legend", "BOOL", "Enable/disable the legend (column headers and hints)"): + r = parse_boolean_argument("--legend", opts.arg, NULL); if (r < 0) return r; arg_legend = r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_GLOBAL: + OPTION_LONG("global", NULL, "Edit/enable/disable/mask default user unit files globally"): arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; - case ARG_RUNTIME: + OPTION_LONG("runtime", NULL, + "Edit/enable/disable/mask unit files temporarily until next reboot"): arg_runtime = true; break; - case 'f': + OPTION('f', "force", NULL, + "When enabling unit files, override existing symlinks. " + "When shutting down, execute action immediately."): arg_force++; break; - case ARG_PRESET_MODE: - if (streq(optarg, "help")) + OPTION_LONG("preset-mode", "MODE", "Apply only enable, only disable, or all presets"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); - arg_preset_mode = unit_file_preset_mode_from_string(optarg); + arg_preset_mode = unit_file_preset_mode_from_string(opts.arg); if (arg_preset_mode < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse preset mode: %s.", optarg); + "Failed to parse preset mode: %s.", opts.arg); break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", + "Edit/enable/disable/mask unit files in the specified root directory"): + r = parse_path_argument(opts.arg, false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", + "Edit/enable/disable/mask unit files in the specified disk image"): + r = parse_path_argument(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", - optarg); + opts.arg); break; - case 'o': - if (streq(optarg, "help")) + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, short-delta, verbose, export, json, " + "json-pretty, json-sse, cat)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(opts.arg); if (arg_output < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown output '%s'.", - optarg); + opts.arg); if (OUTPUT_MODE_IS_JSON(arg_output)) { arg_legend = false; @@ -988,21 +802,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } break; - case ARG_FIRMWARE_SETUP: + OPTION_LONG("firmware-setup", NULL, "Tell the firmware to show the setup menu on next boot"): arg_firmware_setup = true; break; - case ARG_BOOT_LOADER_MENU: - - r = parse_sec(optarg, &arg_boot_loader_menu); + OPTION_LONG("boot-loader-menu", "TIME", "Boot into boot loader menu on next boot"): + r = parse_sec(opts.arg, &arg_boot_loader_menu); if (r < 0) - return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", opts.arg); break; - case ARG_BOOT_LOADER_ENTRY: - - if (streq(optarg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ + OPTION_LONG("boot-loader-entry", "NAME", + "Boot into a specific boot loader entry on next boot"): + if (streq(opts.arg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ r = help_boot_loader_entry(); if (r < 0) return r; @@ -1010,119 +823,117 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return 0; } - arg_boot_loader_entry = empty_to_null(optarg); + arg_boot_loader_entry = empty_to_null(opts.arg); break; - case ARG_REBOOT_ARG: - arg_reboot_argument = optarg; + OPTION_LONG("reboot-argument", "ARG", "Specify argument string to pass to reboot()"): + arg_reboot_argument = opts.arg; break; - case ARG_KERNEL_CMDLINE: - if (isempty(optarg)) { + OPTION_LONG("kernel-cmdline", "CMDLINE", + "Append to the kernel command line when loading the kernel " + "from the booted boot loader entry"): + if (isempty(opts.arg)) { arg_kernel_cmdline = mfree(arg_kernel_cmdline); break; } - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--kernel-cmdline= argument contains invalid characters: %s", optarg); + "--kernel-cmdline= argument contains invalid characters: %s", opts.arg); - r = free_and_strdup_warn(&arg_kernel_cmdline, optarg); + r = free_and_strdup_warn(&arg_kernel_cmdline, opts.arg); if (r < 0) return r; break; - case ARG_PLAIN: + OPTION_LONG("plain", NULL, "Print unit dependencies as a list instead of a tree"): arg_plain = true; break; - case ARG_TIMESTAMP_STYLE: - if (streq(optarg, "help")) + OPTION_LONG("timestamp", "FORMAT", + "Change format of printed timestamps (pretty, unix, us, utc, us+utc)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); - arg_timestamp_style = timestamp_style_from_string(optarg); + arg_timestamp_style = timestamp_style_from_string(opts.arg); if (arg_timestamp_style < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid value: %s.", optarg); + "Invalid value: %s.", opts.arg); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create read-only bind mount"): arg_read_only = true; break; - case ARG_MKDIR: + OPTION_LONG("mkdir", NULL, "Create directory before mounting, if missing"): arg_mkdir = true; break; - case ARG_MARKED: + OPTION_LONG("marked", NULL, "Restart/reload previously marked units"): arg_marked = true; break; - case ARG_DROP_IN: - arg_drop_in = optarg; + OPTION_LONG("drop-in", "NAME", "Edit unit files using the specified drop-in file name"): + arg_drop_in = opts.arg; break; - case ARG_WHEN: - if (streq(optarg, "show")) { + OPTION_LONG("when", "TIME", + "Schedule halt/power-off/reboot/kexec action after a certain timestamp"): + if (streq(opts.arg, "show")) { arg_action = ACTION_SYSTEMCTL_SHOW_SHUTDOWN; return 1; } - if (STR_IN_SET(optarg, "", "cancel")) { + if (STR_IN_SET(opts.arg, "", "cancel")) { arg_action = ACTION_CANCEL_SHUTDOWN; return 1; } - if (streq(optarg, "auto")) { + if (streq(opts.arg, "auto")) { arg_when = USEC_INFINITY; /* logind chooses on server side */ break; } - r = parse_timestamp(optarg, &arg_when); + r = parse_timestamp(opts.arg, &arg_when); if (r < 0) - return log_error_errno(r, "Failed to parse --when= argument '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --when= argument '%s': %m", opts.arg); if (!timestamp_is_set(arg_when)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid timestamp '%s' specified for --when=.", optarg); + "Invalid timestamp '%s' specified for --when=.", opts.arg); break; - case ARG_STDIN: + OPTION_LONG("stdin", NULL, "Read new contents of edited file from stdin"): arg_stdin = true; break; /* Compatibility-only options, not shown in --help. */ - case ARG_FAIL: + OPTION_LONG("fail", NULL, /* help= */ NULL): _arg_job_mode = "fail"; break; - case ARG_IRREVERSIBLE: + OPTION_LONG("irreversible", NULL, /* help= */ NULL): _arg_job_mode = "replace-irreversibly"; break; - case ARG_IGNORE_DEPENDENCIES: + OPTION_LONG("ignore-dependencies", NULL, /* help= */ NULL): _arg_job_mode = "ignore-dependencies"; break; - case ARG_NO_LEGEND: + OPTION_LONG("no-legend", NULL, /* help= */ NULL): arg_legend = false; break; - case '.': + OPTION_SHORT_FLAGS(OPTION_OPTIONAL_ARG, '.', "ARG", /* help= */ NULL): /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); log_notice("Hint: to specify units starting with a dash, use \"--\":\n" - " %s [OPTIONS...] COMMAND -- -.%s ...", - program_invocation_name, optarg ?: "mount"); - _fallthrough_; - - case '?': + " %s [OPTIONS…] COMMAND -- -.%s …", + program_invocation_name, opts.arg ?: "mount"); return -EINVAL; - - default: - assert_not_reached(); } /* If we are in --user mode, there's no point in talking to PolicyKit or the infra to query system @@ -1142,17 +953,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--wait may not be combined with --no-block."); - bool do_reload_or_restart = streq_ptr(argv[optind], "reload-or-restart"); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + bool do_reload_or_restart = streq_ptr(args[0], "reload-or-restart"); if (arg_marked) { if (!do_reload_or_restart) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--marked may only be used with 'reload-or-restart'."); - if (optind + 1 < argc) + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No additional arguments allowed with 'reload-or-restart --marked'."); } else if (do_reload_or_restart) { - if (optind + 1 >= argc) + if (n_args <= 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "List of units to restart/reload is required."); } @@ -1161,10 +975,12 @@ static int systemctl_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."); + if (remaining_args) + *remaining_args = args; return 1; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]) { +int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); @@ -1183,8 +999,9 @@ int systemctl_dispatch_parse_argv(int argc, char *argv[]) { } else if (invoked_as(argv, "shutdown")) { arg_action = ACTION_POWEROFF; return shutdown_parse_argv(argc, argv); + } else { + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv, remaining_args); } - arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv); } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index bc52e96fe3bfd..3789fbb5ea082 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -102,4 +102,4 @@ static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]); +int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args); From 52185521f06e35b62f5df1eed452afbc387c4d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 22:24:14 +0200 Subject: [PATCH 1695/2155] systemctl: convert verbs to VERB macros systemctl_main() is moved to systemctl.c to allow fuzz-systemctl-parse-argv to compile. It needs systemctl_help(), which needs the verb table, with the expected groups. Once we provide that, the linker needs all the verb_* functions. So add dummy implementations in fuzz-systemctl-parse-argv to allow the link to happen. The alternative would be to provide an empty option table, but that seems to be more complicated, and also can simulate parsing of the whole command line with the full verb set, so it seems better to test with the real verb table. $ nm build/fuzz-systemctl-parse-argv | rg 0000000000418885 0000000000418885 T verb_add_dependency 0000000000418885 T verb_bind 0000000000418885 T verb_cancel 0000000000418885 T verb_cat 0000000000418885 T verb_clean_or_freeze 0000000000418885 T verb_edit 0000000000418885 T verb_enable 0000000000418885 T verb_get_default 0000000000418885 T verb_import_environment 0000000000418885 T verb_is_active 0000000000418885 T verb_is_enabled 0000000000418885 T verb_is_failed 0000000000418885 T verb_is_system_running 0000000000418885 T verb_kill 0000000000418885 T verb_list_automounts 0000000000418885 T verb_list_dependencies 0000000000418885 T verb_list_jobs 0000000000418885 T verb_list_machines 0000000000418885 T verb_list_paths 0000000000418885 T verb_list_sockets 0000000000418885 T verb_list_timers 0000000000418885 T verb_list_unit_files 0000000000418885 T verb_list_units 0000000000418885 T verb_log_setting 0000000000418885 T verb_mount_image 0000000000418885 t verb_noop 0000000000418885 T verb_preset_all 0000000000418885 T verb_reset_failed 0000000000418885 T verb_service_log_setting 0000000000418885 T verb_service_watchdogs 0000000000418885 T verb_set_default 0000000000418885 T verb_set_environment 0000000000418885 T verb_set_property 0000000000418885 T verb_show 0000000000418885 T verb_show_environment 0000000000418885 T verb_start_special 0000000000418885 T verb_start_system_special 0000000000418885 T verb_switch_root 0000000000418885 T verb_trivial_method 0000000000418885 T verb_whoami --- src/fundamental/macro-fundamental.h | 1 + src/systemctl/fuzz-systemctl-parse-argv.c | 42 +++ src/systemctl/systemctl-main.c | 129 +-------- src/systemctl/systemctl.c | 336 ++++++++++++++-------- src/systemctl/systemctl.h | 1 + 5 files changed, 269 insertions(+), 240 deletions(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 6ed6cf2f8a0a1..29b25338849bf 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -72,6 +72,7 @@ #define REENABLE_WARNING \ _Pragma("GCC diagnostic pop") +#define _alias_(x) typeof(x) __attribute__((alias(#x))) #define _align_(x) __attribute__((__aligned__(x))) #define _alignas_(x) __attribute__((__aligned__(alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void *)))) diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index 38c0decd9490d..c57faa6771e45 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -15,6 +15,48 @@ #include "systemctl.h" #include "systemctl-util.h" +static int verb_noop(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +_alias_(verb_noop) + verb_add_dependency, + verb_bind, + verb_cancel, + verb_cat, + verb_clean_or_freeze, + verb_edit, + verb_enable, + verb_get_default, + verb_import_environment, + verb_is_active, + verb_is_enabled, + verb_is_failed, + verb_is_system_running, + verb_kill, + verb_list_automounts, + verb_list_dependencies, + verb_list_jobs, + verb_list_machines, + verb_list_paths, + verb_list_sockets, + verb_list_timers, + verb_list_unit_files, + verb_list_units, + verb_log_setting, + verb_mount_image, + verb_preset_all, + verb_reset_failed, + verb_service_log_setting, + verb_service_watchdogs, + verb_set_default, + verb_set_environment, + verb_set_property, + verb_show, + verb_show_environment, + verb_start_special, + verb_start_system_special, + verb_switch_root, + verb_trivial_method, + verb_whoami; + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_strv_free_ char **argv = NULL; _cleanup_close_ int orig_stdout_fd = -EBADF; diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 27f74519115ee..395874d5637c0 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -11,139 +11,12 @@ #include "mount-util.h" #include "stat-util.h" #include "strv.h" -#include "systemctl.h" -#include "systemctl-add-dependency.h" -#include "systemctl-cancel-job.h" -#include "systemctl-clean-or-freeze.h" #include "systemctl-compat-halt.h" -#include "systemctl-daemon-reload.h" -#include "systemctl-edit.h" -#include "systemctl-enable.h" -#include "systemctl-is-active.h" -#include "systemctl-is-enabled.h" -#include "systemctl-is-system-running.h" -#include "systemctl-kill.h" -#include "systemctl-list-dependencies.h" -#include "systemctl-list-jobs.h" -#include "systemctl-list-machines.h" -#include "systemctl-list-unit-files.h" -#include "systemctl-list-units.h" -#include "systemctl-log-setting.h" #include "systemctl-logind.h" -#include "systemctl-mount.h" -#include "systemctl-preset-all.h" -#include "systemctl-reset-failed.h" -#include "systemctl-service-watchdogs.h" -#include "systemctl-set-default.h" -#include "systemctl-set-environment.h" -#include "systemctl-set-property.h" -#include "systemctl-show.h" -#include "systemctl-start-special.h" -#include "systemctl-start-unit.h" -#include "systemctl-switch-root.h" -#include "systemctl-trivial-method.h" #include "systemctl-util.h" -#include "systemctl-whoami.h" -#include "verbs.h" +#include "systemctl.h" #include "virt.h" -static int systemctl_main(char **args) { - static const Verb verbs[] = { - { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_units }, - { "list-unit-files", VERB_ANY, VERB_ANY, 0, verb_list_unit_files }, - { "list-automounts", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_automounts }, - { "list-paths", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_paths }, - { "list-sockets", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_sockets }, - { "list-timers", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_timers }, - { "list-jobs", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_jobs }, - { "list-machines", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_machines }, - { "clear-jobs", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_trivial_method }, - { "cancel", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_cancel }, - { "start", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "stop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "condstop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with ALTLinux */ - { "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", 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 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 */ - { "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with RH */ - { "isolate", 2, 2, VERB_ONLINE_ONLY, verb_start }, - { "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_kill }, - { "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "freeze", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "thaw", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_is_active }, - { "check", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_is_active }, /* deprecated alias of is-active */ - { "is-failed", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_is_failed }, - { "show", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "cat", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_cat }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "help", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "daemon-reload", 1, 1, VERB_ONLINE_ONLY, verb_daemon_reload }, - { "daemon-reexec", 1, 1, VERB_ONLINE_ONLY, verb_daemon_reload }, - { "log-level", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_log_setting }, - { "log-target", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_log_setting }, - { "service-log-level", 2, 3, VERB_ONLINE_ONLY, verb_service_log_setting }, - { "service-log-target", 2, 3, VERB_ONLINE_ONLY, verb_service_log_setting }, - { "service-watchdogs", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_service_watchdogs }, - { "show-environment", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_show_environment }, - { "set-environment", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_set_environment }, - { "unset-environment", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_set_environment }, - { "import-environment", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_import_environment }, - { "halt", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "poweroff", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "kexec", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "soft-reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "suspend", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "hibernate", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "hybrid-sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "suspend-then-hibernate",VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "default", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_special }, - { "rescue", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "emergency", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "exit", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_start_special }, - { "reset-failed", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_reset_failed }, - { "enable", 2, VERB_ANY, 0, verb_enable }, - { "disable", 2, VERB_ANY, 0, verb_enable }, - { "is-enabled", 2, VERB_ANY, 0, verb_is_enabled }, - { "reenable", 2, VERB_ANY, 0, verb_enable }, - { "preset", 2, VERB_ANY, 0, verb_enable }, - { "preset-all", VERB_ANY, 1, 0, verb_preset_all }, - { "mask", 2, VERB_ANY, 0, verb_enable }, - { "unmask", 2, VERB_ANY, 0, verb_enable }, - { "link", 2, VERB_ANY, 0, verb_enable }, - { "revert", 2, VERB_ANY, 0, verb_enable }, - { "switch-root", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_switch_root }, - { "list-dependencies", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_dependencies }, - { "set-default", 2, 2, 0, verb_set_default }, - { "get-default", VERB_ANY, 1, 0, verb_get_default }, - { "set-property", 3, VERB_ANY, VERB_ONLINE_ONLY, verb_set_property }, - { "is-system-running", VERB_ANY, 1, 0, verb_is_system_running }, - { "add-wants", 3, VERB_ANY, 0, verb_add_dependency }, - { "add-requires", 3, VERB_ANY, 0, verb_add_dependency }, - { "edit", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_edit }, - { "bind", 3, 4, VERB_ONLINE_ONLY, verb_bind }, - { "mount-image", 4, 5, VERB_ONLINE_ONLY, verb_mount_image }, - { "whoami", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_whoami }, - {} - }; - - const Verb *verb = verbs_find_verb(args[0], 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=.", - args[0] ?: verb->verb); - - return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); -} - static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 8a241495c0111..817ee719807aa 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -28,6 +28,7 @@ #include "systemctl-compat-shutdown.h" #include "systemctl-logind.h" #include "time-util.h" +#include "verbs.h" char **arg_types = NULL; char **arg_states = NULL; @@ -109,130 +110,47 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { + static const char *const vgroups[] = { + "Unit Commands", + "Unit File Commands", + "Machine Commands", + "Job Commands", + "Environment Commands", + "Manager State Commands", + "System Commands", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); _cleanup_(table_unrefp) Table *options_table = NULL; int r; + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } + r = option_parser_get_help_table_ns("systemctl", &options_table); if (r < 0) return r; + assert_cc(ELEMENTSOF(vtables) == 7); + (void) table_sync_column_widths(0, options_table, + vtables[0], vtables[1], vtables[2], vtables[3], + vtables[4], vtables[5], vtables[6]); + pager_open(arg_pager_flags); help_cmdline("[OPTIONS…] COMMAND …"); help_abstract("Query or send control commands to the system manager."); - 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" - " ordered by path\n" - " list-sockets [PATTERN...] List socket units currently in memory,\n" - " ordered by address\n" - " list-timers [PATTERN...] List timer units currently in memory,\n" - " ordered by next elapse\n" - " is-active PATTERN... Check whether units are active\n" - " is-failed [PATTERN...] Check whether units are failed or\n" - " system is in degraded state\n" - " status [PATTERN...|PID...] Show runtime status of one or more units\n" - " show [PATTERN...|JOB...] Show properties of one or more\n" - " units/jobs or the manager\n" - " cat PATTERN... Show files and drop-ins of specified units\n" - " help PATTERN...|PID... Show manual for one or more units\n" - " list-dependencies [UNIT...] Recursively show units which are required\n" - " or wanted by the units or by which those\n" - " units are required or wanted\n" - " start UNIT... Start (activate) one or more units\n" - " stop UNIT... Stop (deactivate) one or more units\n" - " 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 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" - " if supported, otherwise restart\n" - " isolate UNIT Start one unit and stop all others\n" - " kill UNIT... Send signal to processes of a unit\n" - " clean UNIT... Clean runtime, cache, state, logs or\n" - " configuration of unit\n" - " freeze PATTERN... Freeze execution of unit processes\n" - " thaw PATTERN... Resume execution of a frozen unit\n" - " set-property UNIT PROPERTY=VALUE... Sets one or more properties of a unit\n" - " bind UNIT PATH [PATH] Bind-mount a path from the host into a\n" - " unit's namespace\n" - " mount-image UNIT PATH [PATH [OPTS]] Mount an image from the host into a\n" - " unit's namespace\n" - " service-log-level SERVICE [LEVEL] Get/set logging threshold for service\n" - " service-log-target SERVICE [TARGET] Get/set logging target for service\n" - " 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"); - - 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" - " preset UNIT... Enable/disable one or more unit files\n" - " based on preset configuration\n" - " preset-all Enable/disable all unit files based on\n" - " preset configuration\n" - " is-enabled UNIT... Check whether unit files are enabled\n" - " mask UNIT... Mask one or more units\n" - " unmask UNIT... Unmask one or more units\n" - " link PATH... Link one or more units files into\n" - " the search path\n" - " revert UNIT... Revert one or more unit files to vendor\n" - " version\n" - " add-wants TARGET UNIT... Add 'Wants' dependency for the target\n" - " on specified one or more units\n" - " add-requires TARGET UNIT... Add 'Requires' dependency for the target\n" - " 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"); - - 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"); - - 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"); - - 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" - " halt Shut down and halt the system\n" - " poweroff Shut down and power-off the system\n" - " reboot Shut down and reboot the system\n" - " kexec Shut down and reboot the system with kexec\n" - " soft-reboot Shut down and reboot userspace\n" - " exit [EXIT_CODE] Request user instance or container exit\n" - " switch-root [ROOT [INIT]] Change to a different root file system\n" - " sleep Put the system to sleep (through one of\n" - " the operations below)\n" - " suspend Suspend the system\n" - " 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"); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } help_section("Options"); r = table_print_or_warn(options_table); @@ -1003,5 +921,199 @@ int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args arg_action = ACTION_SYSTEMCTL; return systemctl_parse_argv(argc, argv, remaining_args); } +} + +VERB_GROUP("Unit Commands"); + +VERB_SCOPE(, verb_list_units, "list-units", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List units currently in memory"); +VERB_SCOPE(, verb_list_automounts, "list-automounts", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List automount units currently in memory, ordered by path"); +VERB_SCOPE(, verb_list_paths, "list-paths", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List path units currently in memory, ordered by path"); +VERB_SCOPE(, verb_list_sockets, "list-sockets", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List socket units currently in memory, ordered by address"); +VERB_SCOPE(, verb_list_timers, "list-timers", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List timer units currently in memory, ordered by next elapse"); +VERB_SCOPE(, verb_is_active, "is-active", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Check whether units are active"); +VERB_SCOPE(, verb_is_failed, "is-failed", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Check whether units are failed or system is in degraded state"); +VERB_SCOPE(, verb_show, "status", "[PATTERN…|PID…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show runtime status of one or more units"); +VERB_SCOPE(, verb_show, "show", "[PATTERN…|JOB…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show properties of one or more units/jobs or the manager"); +VERB_SCOPE(, verb_cat, "cat", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Show files and drop-ins of specified units"); +VERB_SCOPE(, verb_show, "help", "PATTERN…|PID…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show manual for one or more units"); +VERB_SCOPE(, verb_list_dependencies, "list-dependencies", "[UNIT…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Recursively show units which are required or wanted by the units " + "or by which those units are required or wanted"); +VERB_SCOPE(, verb_start, "start", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Start (activate) one or more units"); +VERB_SCOPE(, verb_start, "stop", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Stop (deactivate) one or more units"); +VERB_SCOPE(, verb_start, "reload", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Reload one or more units"); +VERB_SCOPE(, verb_start, "restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Start or restart one or more units"); +VERB_SCOPE(, verb_start, "try-restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Restart one or more units if active"); +VERB_SCOPE(, verb_start, "enqueue-marked", NULL, 1, 1, VERB_ONLINE_ONLY, + "Enqueue jobs for all marked units"); +VERB_SCOPE(, verb_start, "reload-or-restart", "UNIT…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Reload one or more units if possible, otherwise start or restart"); +VERB_SCOPE(, verb_start, "try-reload-or-restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "If active, reload one or more units, if supported, otherwise restart"); +VERB_SCOPE(, verb_start, "isolate", "UNIT", 2, 2, VERB_ONLINE_ONLY, + "Start one unit and stop all others"); +VERB_SCOPE(, verb_kill, "kill", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Send signal to processes of a unit"); +VERB_SCOPE(, verb_clean_or_freeze, "clean", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Clean runtime, cache, state, logs or configuration of unit"); +VERB_SCOPE(, verb_clean_or_freeze, "freeze", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Freeze execution of unit processes"); +VERB_SCOPE(, verb_clean_or_freeze, "thaw", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Resume execution of a frozen unit"); +VERB_SCOPE(, verb_set_property, "set-property", "UNIT PROPERTY=VALUE…", 3, VERB_ANY, VERB_ONLINE_ONLY, + "Sets one or more properties of a unit"); +VERB_SCOPE(, verb_bind, "bind", "UNIT PATH [PATH]", 3, 4, VERB_ONLINE_ONLY, + "Bind-mount a path from the host into a unit's namespace"); +VERB_SCOPE(, verb_mount_image, "mount-image", "UNIT PATH [PATH [OPTS]]", 4, 5, VERB_ONLINE_ONLY, + "Mount an image from the host into a unit's namespace"); +VERB_SCOPE(, verb_service_log_setting, "service-log-level", "SERVICE [LEVEL]", 2, 3, VERB_ONLINE_ONLY, + "Get/set logging threshold for service"); +VERB_SCOPE(, verb_service_log_setting, "service-log-target", "SERVICE [TARGET]", 2, 3, VERB_ONLINE_ONLY, + "Get/set logging target for service"); +VERB_SCOPE(, verb_reset_failed, "reset-failed", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Reset failed state for all, one, or more units"); +VERB_SCOPE(, verb_whoami, "whoami", "[PID…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Return unit caller or specified PIDs are part of"); + +VERB_GROUP("Unit File Commands"); + +VERB_SCOPE(, verb_list_unit_files, "list-unit-files", "[PATTERN…]", VERB_ANY, VERB_ANY, 0, + "List installed unit files"); +VERB_SCOPE(, verb_enable, "enable", "[UNIT…|PATH…]", 2, VERB_ANY, 0, + "Enable one or more unit files"); +VERB_SCOPE(, verb_enable, "disable", "UNIT…", 2, VERB_ANY, 0, + "Disable one or more unit files"); +VERB_SCOPE(, verb_enable, "reenable", "UNIT…", 2, VERB_ANY, 0, + "Reenable one or more unit files"); +VERB_SCOPE(, verb_enable, "preset", "UNIT…", 2, VERB_ANY, 0, + "Enable/disable one or more unit files based on preset configuration"); +VERB_SCOPE(, verb_preset_all, "preset-all", NULL, VERB_ANY, 1, 0, + "Enable/disable all unit files based on preset configuration"); +VERB_SCOPE(, verb_is_enabled, "is-enabled", "UNIT…", 2, VERB_ANY, 0, + "Check whether unit files are enabled"); +VERB_SCOPE(, verb_enable, "mask", "UNIT…", 2, VERB_ANY, 0, + "Mask one or more units"); +VERB_SCOPE(, verb_enable, "unmask", "UNIT…", 2, VERB_ANY, 0, + "Unmask one or more units"); +VERB_SCOPE(, verb_enable, "link", "PATH…", 2, VERB_ANY, 0, + "Link one or more units files into the search path"); +VERB_SCOPE(, verb_enable, "revert", "UNIT…", 2, VERB_ANY, 0, + "Revert one or more unit files to vendor version"); +VERB_SCOPE(, verb_add_dependency, "add-wants", "TARGET UNIT…", 3, VERB_ANY, 0, + "Add 'Wants' dependency for the target on specified one or more units"); +VERB_SCOPE(, verb_add_dependency, "add-requires", "TARGET UNIT…", 3, VERB_ANY, 0, + "Add 'Requires' dependency for the target on specified one or more units"); +VERB_SCOPE(, verb_edit, "edit", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Edit one or more unit files"); +VERB_SCOPE(, verb_get_default, "get-default", NULL, VERB_ANY, 1, 0, + "Get the name of the default target"); +VERB_SCOPE(, verb_set_default, "set-default", "TARGET", 2, 2, 0, + "Set the default target"); + +VERB_GROUP("Machine Commands"); + +VERB_SCOPE(, verb_list_machines, "list-machines", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List local containers and host"); + +VERB_GROUP("Job Commands"); + +VERB_SCOPE(, verb_list_jobs, "list-jobs", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List jobs"); +VERB_SCOPE(, verb_cancel, "cancel", "[JOB…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Cancel all, one, or more jobs"); + +VERB_GROUP("Environment Commands"); + +VERB_SCOPE(, verb_show_environment, "show-environment", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Dump environment"); +VERB_SCOPE(, verb_set_environment, "set-environment", "VARIABLE=VALUE…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Set one or more environment variables"); +VERB_SCOPE(, verb_set_environment, "unset-environment", "VARIABLE…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Unset one or more environment variables"); +VERB_SCOPE(, verb_import_environment, "import-environment", "VARIABLE…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Import all or some environment variables"); + +VERB_GROUP("Manager State Commands"); + +VERB_SCOPE(, verb_daemon_reload, "daemon-reload", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reload systemd manager configuration"); +VERB_SCOPE(, verb_daemon_reload, "daemon-reexec", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reexecute systemd manager"); +VERB_SCOPE(, verb_log_setting, "log-level", "[LEVEL]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set logging threshold for manager"); +VERB_SCOPE(, verb_log_setting, "log-target", "[TARGET]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set logging target for manager"); +VERB_SCOPE(, verb_service_watchdogs, "service-watchdogs", "[BOOL]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set service watchdog state"); + +VERB_GROUP("System Commands"); + +VERB_SCOPE(, verb_is_system_running, "is-system-running", NULL, VERB_ANY, 1, 0, + "Check whether system is fully running"); +VERB_SCOPE(, verb_start_special, "default", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system default mode"); +VERB_SCOPE(, verb_start_system_special, "rescue", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system rescue mode"); +VERB_SCOPE(, verb_start_system_special, "emergency", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system emergency mode"); +VERB_SCOPE(, verb_start_system_special, "halt", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and halt the system"); +VERB_SCOPE(, verb_start_system_special, "poweroff", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and power-off the system"); +VERB_SCOPE(, verb_start_system_special, "reboot", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot the system"); +VERB_SCOPE(, verb_start_system_special, "kexec", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot the system with kexec"); +VERB_SCOPE(, verb_start_system_special, "soft-reboot", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot userspace"); +VERB_SCOPE(, verb_start_special, "exit", "[EXIT_CODE]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Request user instance or container exit"); +VERB_SCOPE(, verb_switch_root, "switch-root", "[ROOT [INIT]]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Change to a different root file system"); +VERB_SCOPE(, verb_start_system_special, "sleep", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Put the system to sleep (through one of the operations below)"); +VERB_SCOPE(, verb_start_system_special, "suspend", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Suspend the system"); +VERB_SCOPE(, verb_start_system_special, "hibernate", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Hibernate the system"); +VERB_SCOPE(, verb_start_system_special, "hybrid-sleep", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Hibernate and suspend the system"); +VERB_SCOPE(, verb_start_system_special, "suspend-then-hibernate", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Suspend the system, wake after a period of time, and hibernate"); + +/* Compatibility aliases / deprecated verbs hidden from --help. */ +VERB_SCOPE(, verb_trivial_method, "clear-jobs", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, /* help= */ NULL); /* systemctl < 4 */ +VERB_SCOPE(, verb_start, "condstop", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* ALTLinux */ +VERB_SCOPE(, verb_start, "reload-or-try-restart", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* systemctl <= 228 */ +VERB_SCOPE(, verb_start, "force-reload", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* SysV */ +VERB_SCOPE(, verb_start, "condreload", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* ALTLinux */ +VERB_SCOPE(, verb_start, "condrestart", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* RH */ +VERB_SCOPE(, verb_is_active, "check", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* deprecated alias of is-active */ + +int systemctl_main(char **args) { + const Verb *verb = verbs_find_verb(args[0], + ALIGN_PTR(__start_SYSTEMD_VERBS), + __stop_SYSTEMD_VERBS); + 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=.", + args[0] ?: verb->verb); + return dispatch_verb_with_args(args, NULL); } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 3789fbb5ea082..58ad1e5876117 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -103,3 +103,4 @@ static inline const char* arg_job_mode(void) { } int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args); +int systemctl_main(char **args); From 45828705d6718e14ad85abd08fabb132da19d9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 22:56:25 +0200 Subject: [PATCH 1696/2155] systemctl: convert halt_parse_argv to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/systemctl/systemctl-compat-halt.c | 123 +++++++++++--------------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index cebdfb2413f16..3f19a263ff00f 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-daemon.h" -#include "alloc-util.h" +#include "ansi-color.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "process-util.h" #include "reboot-util.h" #include "systemctl.h" @@ -17,126 +17,103 @@ #include "utmp-wtmp.h" static int halt_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("halt", "8", &link); + r = option_parser_get_help_table_ns("halt", &options); if (r < 0) - return log_oom(); + return r; /* Note: if you are tempted to add new command line switches here, please do not. Let this * compatibility command rest in peace. Its interface is not even owned by us as much as it is by * sysvinit. If you add something new, add it to "systemctl halt", "systemctl reboot", "systemctl * poweroff" instead. */ - printf("%s [OPTIONS...]%s\n" - "\n%s%s the system.%s\n" - "\nOptions:\n" - " --help Show this help\n" - " --halt Halt the machine\n" - " -p --poweroff Switch off the machine\n" - " --reboot Reboot the machine\n" - " -f --force Force immediate halt/power-off/reboot\n" - " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" - " -d --no-wtmp Don't write wtmp record\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - "\n%sThis is a compatibility interface, please use the more powerful 'systemctl %s' command instead.%s\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - arg_action == ACTION_REBOOT ? " [ARG]" : "", - ansi_highlight(), arg_action == ACTION_REBOOT ? "Reboot" : - arg_action == ACTION_POWEROFF ? "Power off" : - "Halt", ansi_normal(), - ansi_highlight_red(), arg_action == ACTION_REBOOT ? "reboot" : - arg_action == ACTION_POWEROFF ? "poweroff" : - "halt", ansi_normal(), - link); + help_cmdline(arg_action == ACTION_REBOOT ? "[OPTIONS…] [ARG]" : "[OPTIONS…]"); + help_abstract(arg_action == ACTION_REBOOT ? "Reboot the system." : + arg_action == ACTION_POWEROFF ? "Power off the system." : + "Halt the system."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sThis is a compatibility interface, please use the more powerful 'systemctl %s' command instead.%s\n", + ansi_highlight_red(), + arg_action == ACTION_REBOOT ? "reboot" : + arg_action == ACTION_POWEROFF ? "poweroff" : + "halt", + ansi_normal()); + help_man_page_reference("halt", "8"); return 0; } int halt_parse_argv(int argc, char *argv[]) { - enum { - ARG_HELP = 0x100, - ARG_HALT, - ARG_REBOOT, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, ARG_HALT }, - { "poweroff", no_argument, NULL, 'p' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "force", no_argument, NULL, 'f' }, - { "wtmp-only", no_argument, NULL, 'w' }, - { "no-wtmp", no_argument, NULL, 'd' }, - { "no-sync", no_argument, NULL, 'n' }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "halt" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_HELP: + OPTION_NAMESPACE("halt"): {} + + OPTION_LONG("help", NULL, "Show this help"): return halt_help(); - case ARG_HALT: + OPTION_LONG("halt", NULL, "Halt the machine"): arg_action = ACTION_HALT; break; - case 'p': + OPTION('p', "poweroff", NULL, "Switch off the machine"): if (arg_action != ACTION_REBOOT) arg_action = ACTION_POWEROFF; break; - case ARG_REBOOT: + OPTION_LONG("reboot", NULL, "Reboot the machine"): arg_action = ACTION_REBOOT; break; - case 'f': + OPTION('f', "force", NULL, "Force immediate halt/power-off/reboot"): arg_force = 2; break; - case 'w': + OPTION('w', "wtmp-only", NULL, "Don't halt/power-off/reboot, just write wtmp record"): arg_dry_run = true; break; - case 'd': + OPTION('d', "no-wtmp", NULL, "Don't write wtmp record"): arg_no_wtmp = true; break; - case 'n': - arg_no_sync = true; + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): + arg_no_wall = true; break; - case ARG_NO_WALL: - arg_no_wall = true; + /* Hidden compat-only options. */ + OPTION('n', "no-sync", NULL, /* help= */ NULL): + arg_no_sync = true; break; - case 'i': - case 'h': + OPTION_SHORT('i', NULL, /* help= */ NULL): {} + OPTION_SHORT('h', NULL, /* help= */ NULL): /* Compatibility nops */ break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { - r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL, false); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (arg_action == ACTION_REBOOT && n_args <= 1) { + r = update_reboot_parameter_and_warn(args[0], false); if (r < 0) return r; - } else if (optind < argc) + } else if (n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); From 23ba690cca95e9f0226d55f9a0fbf42decb256c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 23:02:48 +0200 Subject: [PATCH 1697/2155] systemctl: convert shutdown_parse_argv to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/systemctl/systemctl-compat-shutdown.c | 137 +++++++++------------- 1 file changed, 55 insertions(+), 82 deletions(-) diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index cfc917073ae0e..f4dbd80473aad 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - -#include "alloc-util.h" +#include "ansi-color.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "reboot-util.h" #include "string-util.h" #include "strv.h" @@ -14,38 +14,31 @@ #include "time-util.h" static int shutdown_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("shutdown", "8", &link); + r = option_parser_get_help_table_ns("shutdown", &options); if (r < 0) - return log_oom(); + return r; /* Note: if you are tempted to add new command line switches here, please do not. Let this * compatibility command rest in peace. Its interface is not even owned by us as much as it is by * sysvinit. If you add something new, add it to "systemctl halt", "systemctl reboot", "systemctl * poweroff" instead. */ - printf("%s [OPTIONS...] [TIME] [WALL...]\n" - "\n%sShut down the system.%s\n" - "\nOptions:\n" - " --help Show this help\n" - " -H --halt Halt the machine\n" - " -P --poweroff Power-off the machine\n" - " -r --reboot Reboot the machine\n" - " -h Equivalent to --poweroff, overridden by --halt\n" - " -k Don't halt/power-off/reboot, just send warnings\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " -c Cancel a pending shutdown\n" - " --show Show pending shutdown\n" - "\n%sThis is a compatibility interface, please use the more powerful 'systemctl halt',\n" - "'systemctl poweroff', 'systemctl reboot' commands instead.%s\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), ansi_normal(), - ansi_highlight_red(), ansi_normal(), - link); + help_cmdline("[OPTIONS…] [TIME] [WALL…]"); + help_abstract("Shut down the system."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sThis is a compatibility interface, please use the more powerful 'systemctl halt',\n" + "'systemctl poweroff', 'systemctl reboot' commands instead.%s\n", + ansi_highlight_red(), ansi_normal()); + help_man_page_reference("shutdown", "8"); return 0; } @@ -124,104 +117,86 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { } int shutdown_parse_argv(int argc, char *argv[]) { - enum { - ARG_HELP = 0x100, - ARG_NO_WALL, - ARG_SHOW - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, 'H' }, - { "poweroff", no_argument, NULL, 'P' }, - { "reboot", no_argument, NULL, 'r' }, - { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "show", no_argument, NULL, ARG_SHOW }, - {} - }; - - char **wall = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "HPrhkKat:fFc", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "shutdown" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_HELP: + OPTION_NAMESPACE("shutdown"): {} + + OPTION_LONG("help", NULL, "Show this help"): return shutdown_help(); - case 'H': + OPTION('H', "halt", NULL, "Halt the machine"): arg_action = ACTION_HALT; break; - case 'P': + OPTION('P', "poweroff", NULL, "Power-off the machine"): arg_action = ACTION_POWEROFF; break; - case 'r': + OPTION('r', "reboot", NULL, "Reboot the machine"): if (kexec_loaded()) arg_action = ACTION_KEXEC; else arg_action = ACTION_REBOOT; break; - case 'K': - arg_action = ACTION_KEXEC; - break; - - case 'h': + OPTION_SHORT('h', NULL, "Equivalent to --poweroff, overridden by --halt"): if (arg_action != ACTION_HALT) arg_action = ACTION_POWEROFF; break; - case 'k': + OPTION_SHORT('k', NULL, "Don't halt/power-off/reboot, just send warnings"): arg_dry_run = true; break; - case ARG_NO_WALL: + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): arg_no_wall = true; break; - case 'a': - case 't': /* Note that we also ignore any passed argument to -t, not just the -t itself */ - case 'f': - case 'F': - /* Compatibility nops */ - break; - - case 'c': + OPTION_SHORT('c', NULL, "Cancel a pending shutdown"): arg_action = ACTION_CANCEL_SHUTDOWN; break; - case ARG_SHOW: + OPTION_LONG("show", NULL, "Show pending shutdown"): arg_action = ACTION_SHOW_SHUTDOWN; break; - case '?': - return -EINVAL; + /* Hidden compat options. */ + OPTION('K', "kexec", NULL, /* help= */ NULL): + arg_action = ACTION_KEXEC; + break; - default: - assert_not_reached(); + OPTION_SHORT('a', NULL, /* help= */ NULL): {} /* compatibility noops */ + OPTION_SHORT('f', NULL, /* help= */ NULL): {} + OPTION_SHORT('F', NULL, /* help= */ NULL): {} + OPTION_SHORT('t', "ARG", /* help= */ NULL): + break; } - if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { - r = parse_shutdown_time_spec(argv[optind], &arg_when); - if (r < 0) { - log_error("Failed to parse time specification: %s", argv[optind]); - return r; - } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args > 0 && arg_action != ACTION_CANCEL_SHUTDOWN) { + r = parse_shutdown_time_spec(args[0], &arg_when); + if (r < 0) + return log_error_errno(r, "Failed to parse time specification: %s", args[0]); } else arg_when = USEC_INFINITY; /* logind chooses on server side */ - if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) + char **wall = NULL; + if (n_args > 0 && arg_action == ACTION_CANCEL_SHUTDOWN) /* No time argument for shutdown cancel */ - wall = argv + optind; - else if (argc > optind + 1) + wall = args; + else if (n_args > 1) /* We skip the time argument */ - wall = argv + optind + 1; + wall = args + 1; if (wall) { char **copy = strv_copy(wall); @@ -230,7 +205,5 @@ int shutdown_parse_argv(int argc, char *argv[]) { strv_free_and_replace(arg_wall, copy); } - optind = argc; - return 1; } From 0a33b37e93bc8163120ade659b5e1b18e2a9ba7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 13 May 2026 23:29:46 +0200 Subject: [PATCH 1698/2155] shared/options: implement the equivalent of 'opterr' All log messages during option parsing are emitted using log_full, and the level is set as LOG_ERR + state->log_level_shift. The default shift is 0, but if set to e.g. 4, we log at LOG_DEBUG, and if set to 5 or higher, logging is effectively suppressed. (Unless compiled with LOG_TRACE, when it'd be suppressed if the shift if set to 6 or higher.) So this gives something like 'opterr', except that without global state and potentially more flexible. --- src/shared/options.c | 55 ++++++++++++++++++++++++++++---------------- src/shared/options.h | 3 +++ 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 34d02d2850e9c..ac36fe07653ec 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -39,6 +39,7 @@ static void shift_arg(char* argv[], int target, int source) { } static int partial_match_error( + const OptionParser *state, const Option options[], const Option options_end[], const char *optname, @@ -57,15 +58,17 @@ static int partial_match_error( r = strv_extendf(&s, "--%s", option->long_code); if (r < 0) - return log_error_errno(r, "Failed to format message: %m"); + return log_full_errno(LOG_ERR + state->log_level_shift, 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)); + return log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' is ambiguous; possibilities: %s", + program_invocation_short_name, optname, strnull(p)); } int option_parse( @@ -84,9 +87,14 @@ int option_parse( case OPTION_PARSER_INIT: { assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); + /* Make sure the shifted log level is between LOG_EMERG/0 and LOG_DEBUG/7. */ + assert(state->log_level_shift >= LOG_EMERG - LOG_ERR && + state->log_level_shift <= LOG_DEBUG - LOG_ERR); if (state->argc < 1) { - r = log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EUCLEAN), + "argv cannot be empty"); goto fail; } @@ -134,7 +142,9 @@ int option_parse( goto done; case OPTION_PARSER_FAILED: - return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Option parser failed before, refusing."); + return log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(ESTALE), + "Option parser failed before, refusing."); default: assert_not_reached(); @@ -203,7 +213,7 @@ int option_parse( if (eq) { optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]); if (!_optname) { - r = log_oom(); + r = log_oom_full(LOG_ERR + state->log_level_shift); goto fail; } @@ -219,13 +229,15 @@ int option_parse( for (option = state->namespace_start;; option++) { if (option >= state->namespace_end) { if (n_partial_matches == 0) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: unrecognized option '%s'", - program_invocation_short_name, optname); + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); goto fail; } if (n_partial_matches > 1) { r = partial_match_error( + state, state->namespace_start, state->namespace_end, optname, @@ -261,16 +273,17 @@ int option_parse( char optchar = state->argv[state->optind][state->short_option_offset]; if (asprintf(&_optname, "-%c", optchar) < 0) { - r = log_oom(); + r = log_oom_full(LOG_ERR + state->log_level_shift); goto fail; } optname = _optname; for (option = state->namespace_start;; option++) { if (option >= state->namespace_end) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: unrecognized option '%s'", - program_invocation_short_name, optname); + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); goto fail; } @@ -295,16 +308,18 @@ int option_parse( assert(option); if (!handling_positional_arg && optval && !option_takes_arg(option)) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: option '%s' doesn't allow an argument", - program_invocation_short_name, optname); + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' doesn't allow an argument", + program_invocation_short_name, optname); goto fail; } if (!handling_positional_arg && !optval && option_arg_required(option)) { if (!state->argv[state->optind + 1]) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: option '%s' requires an argument", - program_invocation_short_name, optname); + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' requires an argument", + program_invocation_short_name, optname); goto fail; } optval = state->argv[state->optind + 1]; diff --git a/src/shared/options.h b/src/shared/options.h index f88275821a6e8..d60c9afe81bfe 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -200,6 +200,9 @@ typedef struct OptionParser { char **argv; /* The argv array, possibly reordered. */ OptionParserMode mode; const char *namespace; /* The namespace, may be NULL. */ + int log_level_shift; /* The log level difference from the default of LOG_ERR. + * Allowed values are -3..4. + * Use 4 == LOG_DEBUG - LOG_ERR to log at debug level. */ const Option *namespace_start, *namespace_end; /* The range of options that are part of our namespace. */ From 4a30028c85e137996fd87dff236dd037d14acad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 00:21:25 +0200 Subject: [PATCH 1699/2155] fuzz-systemctl-parse-argv: update suppression of logging and resetting of state There's certainly more than one way to skin this particular cat, so I'm keeping this as a separate commit. --- src/systemctl/fuzz-systemctl-parse-argv.c | 8 +++----- src/systemctl/systemctl-compat-halt.c | 8 ++++++-- src/systemctl/systemctl-compat-halt.h | 3 +-- src/systemctl/systemctl-compat-shutdown.c | 8 ++++++-- src/systemctl/systemctl-compat-shutdown.h | 2 +- src/systemctl/systemctl-main.c | 2 +- src/systemctl/systemctl.c | 21 +++++++++++++-------- src/systemctl/systemctl.h | 2 +- 8 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index c57faa6771e45..9d487f7c6ea64 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "bus-util.h" @@ -88,17 +87,16 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { log_warning_errno(orig_stdout_fd, "Failed to duplicate fd 1: %m"); else assert_se(freopen("/dev/null", "w", stdout)); - - opterr = 0; /* do not print errors */ } /* We need to reset some global state manually here since libfuzzer feeds a single process with * multiple inputs, so we might carry over state from previous invocations that can trigger * certain asserts. */ - optind = 0; /* this tells the getopt machinery to reinitialize */ arg_transport = BUS_TRANSPORT_LOCAL; - r = systemctl_dispatch_parse_argv(strv_length(argv), argv, /* remaining_args= */ NULL); + r = systemctl_dispatch_parse_argv(strv_length(argv), argv, + /* log_level_shift= */ LOG_DEBUG - LOG_ERR, + /* remaining_args= */ NULL); if (r < 0) log_error_errno(r, "Failed to parse args: %m"); else diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 3f19a263ff00f..217487b64350c 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -50,13 +50,17 @@ static int halt_help(void) { return 0; } -int halt_parse_argv(int argc, char *argv[]) { +int halt_parse_argv(int argc, char *argv[], int log_level_shift) { int r; assert(argc >= 0); assert(argv); - OptionParser opts = { argc, argv, .namespace = "halt" }; + OptionParser opts = { + argc, argv, + .namespace = "halt", + .log_level_shift = log_level_shift, + }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { diff --git a/src/systemctl/systemctl-compat-halt.h b/src/systemctl/systemctl-compat-halt.h index 85b9dda0e4a0e..3658e52e1aa21 100644 --- a/src/systemctl/systemctl-compat-halt.h +++ b/src/systemctl/systemctl-compat-halt.h @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int halt_parse_argv(int argc, char *argv[]); - +int halt_parse_argv(int argc, char *argv[], int log_level_shift); int halt_main(void); diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index f4dbd80473aad..d93fa91ca09ec 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -116,13 +116,17 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { return 0; } -int shutdown_parse_argv(int argc, char *argv[]) { +int shutdown_parse_argv(int argc, char *argv[], int log_level_shift) { int r; assert(argc >= 0); assert(argv); - OptionParser opts = { argc, argv, .namespace = "shutdown" }; + OptionParser opts = { + argc, argv, + .namespace = "shutdown", + .log_level_shift = log_level_shift, + }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { diff --git a/src/systemctl/systemctl-compat-shutdown.h b/src/systemctl/systemctl-compat-shutdown.h index 7acf9414c9ab9..9181a31f862f9 100644 --- a/src/systemctl/systemctl-compat-shutdown.h +++ b/src/systemctl/systemctl-compat-shutdown.h @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int shutdown_parse_argv(int argc, char *argv[]); +int shutdown_parse_argv(int argc, char *argv[], int log_level_shift); diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 395874d5637c0..8e2b1c170d5f5 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -26,7 +26,7 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = systemctl_dispatch_parse_argv(argc, argv, &args); + r = systemctl_dispatch_parse_argv(argc, argv, /* log_level_shift= */ 0, &args); if (r <= 0) goto finish; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 817ee719807aa..d5473cdc0af4b 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -373,7 +373,7 @@ static int parse_what_argument(const char *value, char ***clean_what) { return 1; } -static int systemctl_parse_argv(int argc, char *argv[], char ***remaining_args) { +static int systemctl_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args) { int r; assert(argc >= 0); @@ -382,7 +382,11 @@ static int systemctl_parse_argv(int argc, char *argv[], char ***remaining_args) /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ arg_ask_password = true; - OptionParser opts = { argc, argv, .namespace = "systemctl" }; + OptionParser opts = { + argc, argv, + .namespace = "systemctl", + .log_level_shift = log_level_shift, + }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { @@ -898,28 +902,29 @@ static int systemctl_parse_argv(int argc, char *argv[], char ***remaining_args) return 1; } -int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args) { +int systemctl_dispatch_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args) { assert(argc >= 0); assert(argv); if (invoked_as(argv, "halt")) { arg_action = ACTION_HALT; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "poweroff")) { arg_action = ACTION_POWEROFF; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "reboot")) { arg_action = ACTION_REBOOT; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "shutdown")) { arg_action = ACTION_POWEROFF; - return shutdown_parse_argv(argc, argv); + return shutdown_parse_argv(argc, argv, log_level_shift); + } else { arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv, remaining_args); + return systemctl_parse_argv(argc, argv, log_level_shift, remaining_args); } } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 58ad1e5876117..e157ab12f067f 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -102,5 +102,5 @@ static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; } -int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args); +int systemctl_dispatch_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args); int systemctl_main(char **args); From 440d701f3c930b53ea054b25cd859388f3cd9a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 00:34:03 +0200 Subject: [PATCH 1700/2155] fuzz-systemctl-parse-argv: add two corpus files to test compat parsers Looking at the corpus examples, I'm not sure the fuzzer even went into the compat parsers. None of the files have argv[0] that'd cause invoked_as() to go into the compat paths. So add the files to provide a quick test and possibly bias the fuzzer search into the right direction. --- test/fuzz/fuzz-systemctl-parse-argv/halt.input | Bin 0 -> 104 bytes .../fuzz/fuzz-systemctl-parse-argv/shutdown.input | Bin 0 -> 102 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/fuzz/fuzz-systemctl-parse-argv/halt.input create mode 100644 test/fuzz/fuzz-systemctl-parse-argv/shutdown.input diff --git a/test/fuzz/fuzz-systemctl-parse-argv/halt.input b/test/fuzz/fuzz-systemctl-parse-argv/halt.input new file mode 100644 index 0000000000000000000000000000000000000000..26b15bd1833ba3be7d053ba0c52f49bab6691b49 GIT binary patch literal 104 zcmXYnK@NZ*3dhYo@QpVB}xxOg{3X*Y8Y(D*%#vjSdR=#>>T}S Z1eNKo`0}?XvhALl0@XmpyXq6Ao-en2Af5mK literal 0 HcmV?d00001 diff --git a/test/fuzz/fuzz-systemctl-parse-argv/shutdown.input b/test/fuzz/fuzz-systemctl-parse-argv/shutdown.input new file mode 100644 index 0000000000000000000000000000000000000000..cc62a983c1f25e07416044f920abe642a6481121 GIT binary patch literal 102 zcmWlQK@NZ*3 Date: Fri, 24 Apr 2026 13:02:25 +0200 Subject: [PATCH 1701/2155] shared/format-table: shorten code a bit Define variables at point of initialization so the whole thing is easier to read. --- src/shared/format-table.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index f678a6722cec9..360fdf596cfc8 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2249,11 +2249,7 @@ int _table_sync_column_widths(size_t column, Table *a, ...) { } 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; _cleanup_free_ size_t *sorted = NULL; - uint64_t *column_weight, weight_sum; int r; assert(t); @@ -2264,7 +2260,7 @@ int table_print_full(Table *t, FILE *f, bool flush) { /* Ensure we have no incomplete rows */ assert(t->n_cells % t->n_columns == 0); - n_rows = t->n_cells / t->n_columns; + size_t n_rows = t->n_cells / t->n_columns; assert(n_rows > 0); /* at least the header row must be complete */ if (t->sort_map) { @@ -2280,17 +2276,14 @@ int table_print_full(Table *t, FILE *f, bool flush) { typesafe_qsort_r(sorted, n_rows, table_data_compare, t); } - if (t->display_map) - display_columns = t->n_display_map; - else - display_columns = t->n_columns; - + size_t display_columns = t->display_map ? t->n_display_map : t->n_columns; assert(display_columns > 0); - minimum_width = newa(size_t, display_columns); - maximum_width = newa(size_t, display_columns); - requested_width = newa(size_t, display_columns); - column_weight = newa0(uint64_t, display_columns); + size_t *minimum_width = newa(size_t, display_columns), + *maximum_width = newa(size_t, display_columns), + *requested_width = newa(size_t, display_columns), + *width = NULL; + uint64_t *column_weight = newa0(uint64_t, display_columns); for (size_t j = 0; j < display_columns; j++) { minimum_width[j] = 1; @@ -2373,10 +2366,11 @@ int table_print_full(Table *t, FILE *f, bool flush) { } /* One space between each column */ + size_t table_requested_width, table_minimum_width, table_maximum_width, table_effective_width; table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1; /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */ - weight_sum = 0; + uint64_t weight_sum = 0; for (size_t j = 0; j < display_columns; j++) { weight_sum += column_weight[j]; From 42e7fd40e03ca7c2c6d6328d49013694c15c9b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 11:23:40 +0200 Subject: [PATCH 1702/2155] shared/verbs: split verbs in two lines when the synopsis is > 25 characters The help tests would not pass because in cases where the verb synopsis is very long, we'd format the table badly if the terminal is fairly narrow. I experimented with a few solutions, but overall, it's hard to achieve very good layout with the automatic formatting. I think the approach in this commit works the best: we end up with an two- or three-line verb synopis, which is similar to what we did manually before. $ COLUMNS=80 build/localectl -h ... Commands: [status] Show current locale settings set-locale LOCALE... Set system locale list-locales Show known locales set-keymap MAP [MAP] Set console and X11 keyboard mappings list-keymaps Show known virtual console keyboard mappings set-x11-keymap LAYOUT [MODEL Set X11 and console keyboard mappings [VARIANT [OPTIONS]]] list-x11-keymap-models Show known X11 keyboard mapping models list-x11-keymap-layouts Show known X11 keyboard mapping layouts list-x11-keymap-variants Show known X11 keyboard mapping variants [LAYOUT] list-x11-keymap-options Show known X11 keyboard mapping options I think that almost nobody actually uses an 80 column terminal, and if they do, they probably don't spend too much time looking at our --help output there. So the goal here is to do something reasonable and robust and get the tests to pass. We can use strjoina here because the strings are fully under our control. --- src/shared/verbs.c | 89 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index e488af63fecd4..aab24c068199a 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -7,6 +7,7 @@ #include "log.h" #include "string-util.h" #include "strv.h" +#include "terminal-util.h" #include "verbs.h" #include "virt.h" @@ -160,6 +161,72 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); } +#define VERB_SYNOPSIS_WIDTH_SANE 25 + +static const char* find_point_to_break(const char *s, size_t max_width) { + /* Locate the first space, preferably after max_width, or the last space otherwise. + * Return the part after the space. */ + + if (strlen(s) <= max_width) + return NULL; + + const char *p = strchr(s + max_width, ' ') ?: strrchr(s, ' '); + return p ? p + 1 : NULL; +} + +static int verb_add_help_one(Table *table, const Verb *verb) { + assert(table); + assert(verb); + + bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT); + int r; + + /* 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 = strjoin(" ", + is_default ? "[" : "", + verb->verb, + verb->argspec ? " " : "", + strempty(verb->argspec), + is_default ? "]" : ""); + if (!s) + return log_oom(); + + const char *ss = NULL; + if (columns() < VERB_SYNOPSIS_WIDTH_SANE * 4) { + /* If the synopsis is very wide, try to split it up. But do this only if the terminal + * is not very wide. If it _is_ wide, the broken up synopsis would look silly. */ + const char *p = find_point_to_break(s, VERB_SYNOPSIS_WIDTH_SANE), *p2 = NULL; + if (p) { + const char *s1 = strndupa_safe(s, p - s), *s2 = NULL; + + p2 = find_point_to_break(p, VERB_SYNOPSIS_WIDTH_SANE - 4); /* we indent by two spaces more */ + if (p2) + s2 = strndupa_safe(p, p2 - p); + + if (s2) + ss = strjoina(s1, "\n ", s2, "\n ", p2); + else + ss = strjoina(s1, "\n ", p); + } + } + + r = table_add_cell(table, NULL, TABLE_STRING, ss ?: s); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **t = strv_split(verb->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); + + return 0; +} + int _verbs_get_help_table( const Verb verbs[], const Verb verbs_end[], @@ -192,27 +259,9 @@ int _verbs_get_help_table( /* No help string — we do not show the verb */ continue; - bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT); - - /* 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%s%s", - is_default ? "[" : "", - verb->verb, - verb->argspec ? " " : "", - strempty(verb->argspec), - is_default ? "]" : ""); - if (r < 0) - return table_log_add_error(r); - - _cleanup_strv_free_ char **s = strv_split(verb->help, /* separators= */ NULL); - if (!s) - return log_oom(); - - r = table_add_many(table, TABLE_STRV_WRAPPED, s); + r = verb_add_help_one(table, verb); if (r < 0) - return table_log_add_error(r); + return r; } table_set_header(table, false); From 92fbc11f6af9ed15ebc0ba8f8e308ac6113db0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 11:51:56 +0200 Subject: [PATCH 1703/2155] shared/options: introduce OPTION_COMMON_{ENTRY_TOKEN,MAKE_ENTRY_DIRECTORY} --- src/bootctl/bootctl.c | 6 ++---- src/shared/options.h | 9 +++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 881bdfb60ffe0..4bb9742a4703d 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -554,15 +554,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_quiet = true; break; - OPTION_LONG("entry-token", "TOKEN", - "Entry token to use for this installation" - " (machine-id, os-id, os-image-id, auto, literal:…)"): + OPTION_COMMON_ENTRY_TOKEN: r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - OPTION_LONG("make-entry-directory", "yes|no|auto", "Create $BOOT/ENTRY-TOKEN/ directory"): {} + OPTION_COMMON_MAKE_ENTRY_DIRECTORY: {} OPTION_LONG("make-machine-id-directory", "BOOL", /* help= */ NULL): /* Compatibility alias */ if (streq(opts.arg, "auto")) /* retained for backwards compatibility */ arg_make_entry_directory = -1; /* yes if machine-id is permanent */ diff --git a/src/shared/options.h b/src/shared/options.h index d60c9afe81bfe..5ba276eaee8d0 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -137,6 +137,15 @@ typedef struct Option { OPTION_SHORT('j', NULL, \ "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") +#define OPTION_COMMON_ENTRY_TOKEN \ + OPTION_LONG("entry-token", "TOKEN", \ + "Entry token to use for this installation " \ + "(machine-id, os-id, os-image-id, auto, literal:…)") + +#define OPTION_COMMON_MAKE_ENTRY_DIRECTORY \ + OPTION_LONG("make-entry-directory", \ + "BOOL|auto", "Create $BOOT/ENTRY-TOKEN/ directory") + #define OPTION_COMMON_PRIVATE_KEY(purpose) \ OPTION_LONG("private-key", "PATH|URI", purpose) From 71c3123b17e54841bd5e37f51b10212375f8b6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:25:49 +0200 Subject: [PATCH 1704/2155] kernel-install: convert to the new option and verb parsers The verb synopses are very long because of the many parameters. Previously were shown without help and occupied all available columns. With the autogenerated help format, this doesn't work great. So the verbs and options tables are not synced, so that help for options can use more columns. I think in this case this is better than the alternatives. Co-developed-by: Claude Opus 4.6 --- src/kernel-install/kernel-install.c | 235 +++++++++++----------------- 1 file changed, 95 insertions(+), 140 deletions(-) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 331923e9e5578..0bd4e84da0ad0 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1,10 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include +#include "ansi-color.h" #include "argv-util.h" #include "boot-entry.h" #include "bootspec.h" @@ -22,6 +22,7 @@ #include "find-esp.h" #include "format-table.h" #include "fs-util.h" +#include "help-util.h" #include "id128-util.h" #include "image-policy.h" #include "kernel-config.h" @@ -29,9 +30,9 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" -#include "pretty-print.h" #include "recurse-dir.h" #include "rm-rf.h" #include "stat-util.h" @@ -1190,6 +1191,8 @@ static int do_add( return context_execute(c); } +VERB(verb_add, "add", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, 0, + "Add a kernel and initrd images to the boot partition"); static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *version, *kernel; char **initrds; @@ -1219,6 +1222,8 @@ static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) { return do_add(&c, version, kernel, initrds); } +VERB_NOARG(verb_add_all, "add-all", + "Add all kernels found in /usr/lib/modules/"); static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; size_t n = 0; @@ -1299,16 +1304,18 @@ static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) return ret; } -static int run_as_installkernel(int argc, char *argv[]) { +static int run_as_installkernel(char **args) { /* kernel's install.sh invokes us as * /sbin/installkernel * We ignore the last two arguments. */ - if (optind + 2 > argc) + if (strv_length(args) < 2) 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]), /* data= */ 0, /* userdata= */ NULL); + return verb_add(3, STRV_MAKE("add", args[0], args[1]), /* data= */ 0, /* userdata= */ NULL); } +VERB(verb_remove, "remove", "KERNEL-VERSION", 2, VERB_ANY, 0, + "Remove a kernel from the boot partition"); static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -1345,6 +1352,8 @@ static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) return context_execute(&c); } +VERB(verb_inspect, "inspect", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, VERB_DEFAULT, + "Print details about the installation"); static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *vmlinuz = NULL; @@ -1458,6 +1467,8 @@ static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); } +VERB_NOARG(verb_list, "list", + "List installed kernels"); static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; int r; @@ -1526,128 +1537,87 @@ static int verb_list(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("kernel-install", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + /* Note: column widths are not synced, because the verbs table is very wide. */ + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Add and remove kernel and initrd images to and from the boot partition."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sAdd and remove kernel and initrd images to and from the boot partition.%6$s\n" - "\n%3$sUsage:%4$s\n" - " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n" - " kernel-install [OPTIONS...] add-all\n" - " kernel-install [OPTIONS...] remove KERNEL-VERSION\n" - " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n" - " [INITRD ...]]\n" - " kernel-install [OPTIONS...] list\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -v --verbose Increase verbosity\n" - " --esp-path=PATH Path to the EFI System Partition (ESP)\n" - " --boot-path=PATH Path to the $BOOT partition\n" - " --make-entry-directory=yes|no|auto\n" - " Create $BOOT/ENTRY-TOKEN/ directory\n" - " --entry-type=type1|type2|all\n" - " Operate only on the specified bootloader\n" - " entry type\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Entry token to be used for this installation\n" - " --no-pager Do not pipe inspect output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --no-legend Do not show the headers and footers\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" - "\n" + printf("\n" "This program may also be invoked as 'installkernel':\n" - " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n" - "(The optional arguments are passed by kernel build system, but ignored.)\n" - "\n" - "See the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n" + "(The optional arguments are passed by kernel build system, but ignored.)\n"); + + help_man_page_reference("kernel-install", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_LEGEND, - ARG_ESP_PATH, - ARG_BOOT_PATH, - ARG_MAKE_ENTRY_DIRECTORY, - ARG_ENTRY_TOKEN, - ARG_NO_PAGER, - ARG_JSON, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_BOOT_ENTRY_TYPE, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "verbose", no_argument, NULL, 'v' }, - { "esp-path", required_argument, NULL, ARG_ESP_PATH }, - { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, - { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "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 }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "entry-type", required_argument, NULL, ARG_BOOT_ENTRY_TYPE }, - {} - }; - int t, r; +VERB_COMMON_HELP(help); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0) - switch (t) { - case 'h': + OptionParser opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'v': + OPTION('v', "verbose", NULL, "Increase verbosity"): log_set_max_level(LOG_DEBUG); arg_verbose = true; break; - case ARG_ESP_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_esp_path); + OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_esp_path); if (r < 0) return log_oom(); break; - case ARG_BOOT_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_xbootldr_path); + OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_xbootldr_path); if (r < 0) return log_oom(); break; - case ARG_MAKE_ENTRY_DIRECTORY: - if (streq(optarg, "auto")) + OPTION_COMMON_MAKE_ENTRY_DIRECTORY: + if (streq(opts.arg, "auto")) arg_make_entry_directory = -1; else { - r = parse_boolean_argument("--make-entry-directory=", optarg, NULL); + r = parse_boolean_argument("--make-entry-directory=", opts.arg, NULL); if (r < 0) return r; @@ -1655,85 +1625,70 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_COMMON_ENTRY_TOKEN: + r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_LONG("entry-type", "TYPE", + "Operate only on the specified bootloader entry type (type1, type2, all)"): { + if (isempty(opts.arg) || streq(opts.arg, "all")) { + arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; + break; + } + + BootEntryType e = boot_entry_type_from_string(opts.arg); + if (e < 0) + return log_error_errno(e, "Invalid entry type: %s", opts.arg); + arg_boot_entry_type = e; + break; + } + + 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(opts.arg, &arg_json_format_flags); 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(opts.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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - - case ARG_BOOT_ENTRY_TYPE: { - if (isempty(optarg) || streq(optarg, "all")) { - arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; - break; - } - - BootEntryType e = boot_entry_type_from_string(optarg); - if (e < 0) - return log_error_errno(e, "Invalid entry type: %s", optarg); - arg_boot_entry_type = e; - 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."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } -static int kernel_install_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "add", 1, VERB_ANY, 0, verb_add }, - { "add-all", 1, 1, 0, verb_add_all }, - { "remove", 2, VERB_ANY, 0, verb_remove }, - { "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect }, - { "list", 1, 1, 0, verb_list }, - {} - }; - - return dispatch_verb(argc, argv, verbs, /* userdata= */ 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; @@ -1762,9 +1717,9 @@ static int run(int argc, char* argv[]) { } if (invoked_as(argv, "installkernel")) - return run_as_installkernel(argc, argv); + return run_as_installkernel(args); - return kernel_install_main(argc, argv); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From b333fcf9dae48df14480775c009e7c13de6ca5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:38:06 +0200 Subject: [PATCH 1705/2155] localectl: convert to the new option and verb parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verb synopses are long, so they got broken up: =============================================================================== > localectl [OPTIONS…] COMMAND … Query or change system locale and keyboard settings. Commands: [status] Show current locale settings set-locale LOCALE... Set system locale list-locales Show known locales set-keymap MAP [MAP] Set console and X11 keyboard mappings list-keymaps Show known virtual console keyboard mappings set-x11-keymap LAYOUT [MODEL Set X11 and console keyboard mappings [VARIANT [OPTIONS]]] list-x11-keymap-models Show known X11 keyboard mapping models list-x11-keymap-layouts Show known X11 keyboard mapping layouts list-x11-keymap-variants Show known X11 keyboard mapping variants [LAYOUT] list-x11-keymap-options Show known X11 keyboard mapping options Options: -h --help Show this help --version Show package version -l --full Do not ellipsize output --no-pager Do not start a pager --no-ask-password Do not prompt for password -H --host=[USER@]HOST Operate on remote host -M --machine=CONTAINER Operate on local container --no-convert Don't convert keyboard mappings See the localectl(1) man page for details. =============================================================================== But I think this is OK. Everything is readable. On a more normal terminal, everything fits nicely. Co-developed-by: Claude Opus 4.6 --- src/locale/localectl.c | 166 ++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 100 deletions(-) diff --git a/src/locale/localectl.c b/src/locale/localectl.c index e80cd96c86ee8..e20d5da9a0fc1 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -1,9 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" +#include "ansi-color.h" #include "alloc-util.h" #include "build.h" #include "bus-error.h" @@ -13,15 +12,16 @@ #include "fd-util.h" #include "fileio.h" #include "format-table.h" +#include "help-util.h" #include "kbd-util.h" #include "locale-setup.h" #include "main-func.h" #include "memory-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "string-util.h" #include "strv.h" @@ -147,6 +147,8 @@ static int print_status_info(StatusInfo *i) { return table_print_or_warn(table); } +VERB(verb_show_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show current locale settings"); 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[] = { @@ -179,6 +181,8 @@ static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userd return print_status_info(&info); } +VERB(verb_set_locale, "set-locale", "LOCALE...", 2, VERB_ANY, 0, + "Set system locale"); 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; @@ -207,6 +211,8 @@ static int verb_set_locale(int argc, char *argv[], uintptr_t _data, void *userda return 0; } +VERB_NOARG(verb_list_locales, "list-locales", + "Show known locales"); static int verb_list_locales(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -221,6 +227,8 @@ static int verb_list_locales(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_set_vconsole_keymap, "set-keymap", "MAP [MAP]", 2, 3, 0, + "Set console and X11 keyboard mappings"); 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; @@ -245,6 +253,8 @@ static int verb_set_vconsole_keymap(int argc, char *argv[], uintptr_t _data, voi return 0; } +VERB_NOARG(verb_list_vconsole_keymaps, "list-keymaps", + "Show known virtual console keyboard mappings"); static int verb_list_vconsole_keymaps(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -260,6 +270,8 @@ static int verb_list_vconsole_keymaps(int argc, char *argv[], uintptr_t _data, v return 0; } +VERB(verb_set_x11_keymap, "set-x11-keymap", "LAYOUT [MODEL [VARIANT [OPTIONS]]]", 2, 5, 0, + "Set X11 and console keyboard mappings"); 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; @@ -295,6 +307,14 @@ static const char* xkb_directory(void) { return cached; } +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-models", + "Show known X11 keyboard mapping models"); +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-layouts", + "Show known X11 keyboard mapping layouts"); +VERB(verb_list_x11_keymaps, "list-x11-keymap-variants", "[LAYOUT]", VERB_ANY, 2, 0, + "Show known X11 keyboard mapping variants"); +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-options", + "Show known X11 keyboard mapping options"); 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; @@ -402,143 +422,88 @@ static int verb_list_x11_keymaps(int argc, char *argv[], uintptr_t _data, void * } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; - r = terminal_urlify_man("localectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sQuery or change system locale and keyboard settings.%s\n" - "\nCommands:\n" - " status Show current locale settings\n" - " set-locale LOCALE... Set system locale\n" - " list-locales Show known locales\n" - " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n" - " list-keymaps Show known virtual console keyboard mappings\n" - " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n" - " Set X11 and console keyboard mappings\n" - " list-x11-keymap-models Show known X11 keyboard mapping models\n" - " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n" - " list-x11-keymap-variants [LAYOUT]\n" - " Show known X11 keyboard mapping variants\n" - " list-x11-keymap-options Show known X11 keyboard mapping options\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -l --full Do not ellipsize output\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" - " --no-convert Don't convert keyboard mappings\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - return 0; -} + (void) table_sync_column_widths(0, verbs, options); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Query or change system locale and keyboard settings."); -static int parse_argv(int argc, char *argv[]) { + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_CONVERT, - ARG_NO_ASK_PASSWORD - }; + help_section("Options"); + 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 }, - { "full", no_argument, NULL, 'l' }, - { "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 }, - { "no-convert", no_argument, NULL, ARG_NO_CONVERT }, - {} - }; + help_man_page_reference("localectl", "1"); - int r, c; + return 0; +} +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, "hlH:M:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_NO_CONVERT: - arg_convert = false; - break; - - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("no-convert", NULL, "Don't convert keyboard mappings"): + arg_convert = false; + break; } + *remaining_args = option_parser_get_args(&opts); return 1; } -static int localectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const Verb verbs[] = { - { "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. */ - {} - }; - - 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; @@ -546,7 +511,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; @@ -554,7 +520,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return localectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); From d0f67f257d62c51dcc2d48f15e3793ad16856b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:42:41 +0200 Subject: [PATCH 1706/2155] busctl: reorder option cases to match --help output Co-developed-by: Claude Opus 4.6 --- src/busctl/busctl.c | 68 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index a895c3fe91edd..1e6fb64e96464 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -2220,12 +2220,34 @@ static int parse_argv(int argc, char *argv[]) { arg_full = true; break; + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + case ARG_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + r = parse_machine_argument(optarg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; break; case ARG_ADDRESS: @@ -2272,34 +2294,23 @@ static int parse_argv(int argc, char *argv[]) { arg_list = true; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + case 'q': + arg_quiet = true; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + case ARG_VERBOSE: + arg_verbose = true; break; - case 'C': - r = capsule_name_is_valid(optarg); - if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); - - arg_host = optarg; - arg_transport = BUS_TRANSPORT_CAPSULE; - break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; - case 'q': - arg_quiet = true; break; - case ARG_VERBOSE: - arg_verbose = true; + case 'j': + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; case ARG_XML_INTERFACE: @@ -2349,17 +2360,6 @@ static int parse_argv(int argc, char *argv[]) { return r; 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); - if (r <= 0) - return r; - - break; - case ARG_DESTINATION: arg_destination = optarg; break; From d2aafc33eaeb3756ca2167a041d2334989bbaf58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 12:56:01 +0200 Subject: [PATCH 1707/2155] busctl: convert to the new option and verb parsers The conversion doesn't work great, because some of the verbs take many arguments and the first column is extermely wide. So similarly to kernel-install, I dropped the sync of column widths. This allows the help for options to use most of the available space. -C/--capsule is now documented, fixup for 00431b2b66cb59540deda4ea018170a289673585. Verb functions are renamed to match verb names. The missing first param is added to the synopsis of "wait". It now matches the man page. Co-developed-by: Claude Opus 4.6 --- src/busctl/busctl.c | 341 ++++++++++++++++---------------------------- 1 file changed, 119 insertions(+), 222 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 1e6fb64e96464..79ee1c55e8ecd 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -9,6 +8,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "bitfield.h" #include "build.h" #include "bus-dump.h" @@ -26,16 +26,17 @@ #include "fileio.h" #include "format-table.h" #include "glyph-util.h" +#include "help-util.h" #include "log.h" #include "logarithm.h" #include "main-func.h" #include "memstream-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "set.h" #include "string-util.h" @@ -175,7 +176,9 @@ 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[], uintptr_t _data, void *userdata) { +VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "List bus names"); +static int verb_list(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; @@ -537,6 +540,8 @@ static int tree_one(sd_bus *bus, const char *service) { return r; } +VERB(verb_tree, "tree", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Show object tree of service"); 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; @@ -996,6 +1001,8 @@ static int members_flags_to_string(const Member *m, char **ret) { return 0; } +VERB(verb_introspect, "introspect", "SERVICE OBJECT [INTERFACE]", 3, 4, 0, + "Introspect an object"); static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { static const XMLIntrospectOps ops = { .on_interface = on_interface, @@ -1377,10 +1384,14 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f } } +VERB(verb_monitor, "monitor", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Show bus traffic"); 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); } +VERB(verb_capture, "capture", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Capture bus traffic as pcap"); static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *osname = NULL; static const char info[] = @@ -1408,6 +1419,8 @@ static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata) return r; } +VERB(verb_status, "status", "[SERVICE]", VERB_ANY, 2, 0, + "Show bus service, process, or bus owner credentials"); 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; @@ -1778,6 +1791,8 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) { return 0; } +VERB(verb_call, "call", "SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT…]]", 5, VERB_ANY, 0, + "Call a method"); 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; @@ -1845,7 +1860,9 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { return bus_message_dump(reply, /* flags= */ 0); } -static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_emit, "emit", "OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT…]]", 4, VERB_ANY, 0, + "Emit a signal"); +static int verb_emit(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; @@ -1890,6 +1907,8 @@ static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_get_property, "get-property", "SERVICE OBJECT INTERFACE PROPERTY…", 5, VERB_ANY, 0, + "Get property value"); 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; @@ -1948,7 +1967,9 @@ 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[], uintptr_t _data, void *userdata) { +VERB(verb_wait, "wait", "[SERVICE] OBJECT INTERFACE SIGNAL", 4, 5, 0, + "Wait for a signal"); +static int verb_wait(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; @@ -1996,6 +2017,8 @@ static int verb_wait_signal(int argc, char *argv[], uintptr_t _data, void *userd return sd_event_loop(e); } +VERB(verb_set_property, "set-property", "SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT…", 6, VERB_ANY, 0, + "Set property value"); 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; @@ -2052,235 +2075,131 @@ static int verb_set_property(int argc, char *argv[], uintptr_t _data, void *user } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; - r = terminal_urlify_man("busctl", "1", &link); + pager_open(arg_pager_flags); + + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - pager_open(arg_pager_flags); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sIntrospect the D-Bus IPC bus.%6$s\n" - "\n%3$sCommands%4$s:\n" - " list List bus names\n" - " status [SERVICE] Show bus service, process or bus owner credentials\n" - " monitor [SERVICE...] Show bus traffic\n" - " capture [SERVICE...] Capture bus traffic as pcap\n" - " tree [SERVICE...] Show object tree of service\n" - " introspect SERVICE OBJECT [INTERFACE]\n" - " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" - " Call a method\n" - " emit OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT...]]\n" - " Emit a signal\n" - " wait OBJECT INTERFACE SIGNAL\n" - " Wait for a signal\n" - " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" - " Get property value\n" - " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" - " Set property value\n" - " help Show this help\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" - " -l --full Do not ellipsize output\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --address=ADDRESS Connect to bus specified by address\n" - " --show-machine Show machine ID column in list\n" - " --unique Only show unique names\n" - " --acquired Only show acquired names\n" - " --activatable Only show activatable names\n" - " --match=MATCH Only show matching messages\n" - " --size=SIZE Maximum length of captured packet\n" - " --list Don't show tree, but simple object path list\n" - " -q --quiet Don't show method call reply\n" - " --verbose Show result values in long format\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --xml-interface Dump the XML description in introspect command\n" - " --expect-reply=BOOL Expect a method call reply\n" - " --auto-start=BOOL Auto-start destination service\n" - " --allow-interactive-authorization=BOOL\n" - " Allow interactive authorization for operation\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n" - " --watch-bind=BOOL Wait for bus AF_UNIX socket to be bound in the file\n" - " system\n" - " --destination=SERVICE Destination service of a signal\n" - " -N --limit-messages=NUMBER\n" - " Stop monitoring after receiving the specified number\n" - " of messages\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + /* Note: column widths are not synced, because the verbs table is very wide. */ - return 0; -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Introspect the D-Bus IPC bus."); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYSTEM, - ARG_USER, - ARG_ADDRESS, - ARG_MATCH, - ARG_SHOW_MACHINE, - ARG_UNIQUE, - ARG_ACQUIRED, - ARG_ACTIVATABLE, - ARG_SIZE, - ARG_LIST, - ARG_VERBOSE, - ARG_XML_INTERFACE, - ARG_EXPECT_REPLY, - ARG_AUTO_START, - ARG_ALLOW_INTERACTIVE_AUTHORIZATION, - ARG_TIMEOUT, - ARG_AUGMENT_CREDS, - ARG_WATCH_BIND, - ARG_JSON, - ARG_DESTINATION, - }; + help_man_page_reference("busctl", "1"); - 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 }, - { "full", no_argument, NULL, 'l' }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "address", required_argument, NULL, ARG_ADDRESS }, - { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, - { "unique", no_argument, NULL, ARG_UNIQUE }, - { "acquired", no_argument, NULL, ARG_ACQUIRED }, - { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "capsule", required_argument, NULL, 'C' }, - { "size", required_argument, NULL, ARG_SIZE }, - { "list", no_argument, NULL, ARG_LIST }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, ARG_VERBOSE }, - { "xml-interface", no_argument, NULL, ARG_XML_INTERFACE }, - { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, - { "auto-start", required_argument, NULL, ARG_AUTO_START }, - { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "augment-creds", required_argument, NULL, ARG_AUGMENT_CREDS }, - { "watch-bind", required_argument, NULL, ARG_WATCH_BIND }, - { "json", required_argument, NULL, ARG_JSON }, - { "destination", required_argument, NULL, ARG_DESTINATION }, - { "limit-messages", required_argument, NULL, 'N' }, - {}, - }; + return 0; +} - int c, r; +VERB_COMMON_HELP(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, "hH:M:C:J:qjlN:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system bus"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user bus"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION('C', "capsule", "NAME", "Operate on capsule"): + r = capsule_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; break; - case ARG_ADDRESS: - arg_address = optarg; + OPTION_LONG("address", "ADDRESS", "Connect to bus specified by address"): + arg_address = opts.arg; break; - case ARG_SHOW_MACHINE: + OPTION_LONG("show-machine", NULL, "Show machine ID column in list"): arg_show_machine = true; break; - case ARG_UNIQUE: + OPTION_LONG("unique", NULL, "Only show unique names"): arg_unique = true; break; - case ARG_ACQUIRED: + OPTION_LONG("acquired", NULL, "Only show acquired names"): arg_acquired = true; break; - case ARG_ACTIVATABLE: + OPTION_LONG("activatable", NULL, "Only show activatable names"): arg_activatable = true; break; - case ARG_MATCH: - if (strv_extend(&arg_matches, optarg) < 0) + OPTION_LONG("match", "MATCH", "Only show matching messages"): + if (strv_extend(&arg_matches, opts.arg) < 0) return log_oom(); break; - case ARG_SIZE: { + OPTION_LONG("size", "SIZE", "Maximum length of captured packet"): { uint64_t sz; - r = parse_size(optarg, 1024, &sz); + r = parse_size(opts.arg, 1024, &sz); if (r < 0) - return log_error_errno(r, "Failed to parse size '%s': %m", optarg); + return log_error_errno(r, "Failed to parse size '%s': %m", opts.arg); if ((uint64_t) (size_t) sz != sz) return log_error_errno(SYNTHETIC_ERRNO(E2BIG), @@ -2290,145 +2209,123 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LIST: + OPTION_LONG("list", NULL, "Don't show tree, but simple object path list"): arg_list = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Don't show method call reply"): arg_quiet = true; break; - case ARG_VERBOSE: + OPTION_LONG("verbose", NULL, "Show result values in long format"): arg_verbose = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.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 ARG_XML_INTERFACE: + OPTION_LONG("xml-interface", NULL, "Dump the XML description in introspect command"): arg_xml_interface = true; break; - case ARG_EXPECT_REPLY: - r = parse_boolean_argument("--expect-reply=", optarg, &arg_expect_reply); + OPTION_LONG("expect-reply", "BOOL", "Expect a method call reply"): + r = parse_boolean_argument("--expect-reply=", opts.arg, &arg_expect_reply); if (r < 0) return r; break; - case ARG_AUTO_START: - r = parse_boolean_argument("--auto-start=", optarg, &arg_auto_start); + OPTION_LONG("auto-start", "BOOL", "Auto-start destination service"): + r = parse_boolean_argument("--auto-start=", opts.arg, &arg_auto_start); if (r < 0) return r; break; - case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: - r = parse_boolean_argument("--allow-interactive-authorization=", optarg, + OPTION_LONG("allow-interactive-authorization", "", + "Allow interactive authorization for operation"): + r = parse_boolean_argument("--allow-interactive-authorization=", opts.arg, &arg_allow_interactive_authorization); if (r < 0) return r; break; - case ARG_TIMEOUT: - if (isempty(optarg)) { + OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"): + if (isempty(opts.arg)) { arg_timeout = 0; /* Reset to default */ break; } - r = parse_sec(optarg, &arg_timeout); + r = parse_sec(opts.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", opts.arg); break; - case ARG_AUGMENT_CREDS: - r = parse_boolean_argument("--augment-creds=", optarg, &arg_augment_creds); + OPTION_LONG("augment-creds", "BOOL", + "Extend credential data with data read from /proc/$PID"): + r = parse_boolean_argument("--augment-creds=", opts.arg, &arg_augment_creds); if (r < 0) return r; break; - case ARG_WATCH_BIND: - r = parse_boolean_argument("--watch-bind=", optarg, &arg_watch_bind); + OPTION_LONG("watch-bind", "BOOL", + "Wait for bus AF_UNIX socket to be bound in the file system"): + r = parse_boolean_argument("--watch-bind=", opts.arg, &arg_watch_bind); if (r < 0) return r; break; - case ARG_DESTINATION: - arg_destination = optarg; + OPTION_LONG("destination", "SERVICE", "Destination service of a signal"): + arg_destination = opts.arg; break; - case 'N': - if (isempty(optarg)) { + OPTION('N', "limit-messages", "NUMBER", + "Stop monitoring after receiving the specified number of messages"): + if (isempty(opts.arg)) { /* Reset to default */ arg_limit_messages = UINT64_MAX; arg_limit_signals = 1; break; } - if (streq(optarg, "infinity")) { + if (streq(opts.arg, "infinity")) { arg_limit_signals = arg_limit_messages = UINT64_MAX; break; } - r = safe_atou64(optarg, &arg_limit_messages); + r = safe_atou64(opts.arg, &arg_limit_messages); if (r < 0) - return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", opts.arg); if (arg_limit_messages == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--limit-messages= parameter cannot be 0"); arg_limit_signals = arg_limit_messages; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_full < 0) arg_full = terminal_is_dumb(); + *remaining_args = option_parser_get_args(&opts); return 1; } -static int busctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "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 }, - {} - }; - - 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 busctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 587c522487664a1b3b42765c61b38aaf43e812f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 21:43:19 +0200 Subject: [PATCH 1708/2155] various: fix duplicated logging from parse_path_argument As pointed out in review, parse_path_argument can fail for non-oom reasons. But the function already logs, so the correct thing to do is to just propagate the error. --- src/bootctl/bootctl-install.c | 2 +- src/home/homectl.c | 6 +++--- src/kernel-install/kernel-install.c | 4 ++-- src/keyutil/keyutil.c | 4 ++-- src/measure/measure-tool.c | 2 +- src/sbsign/sbsign.c | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 20958d0b0bc6f..2ad76264fa390 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1509,7 +1509,7 @@ static int load_secure_boot_auto_enroll( if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( diff --git a/src/home/homectl.c b/src/home/homectl.c index 80ee6e2155a94..e9161dfb751e5 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -4648,7 +4648,7 @@ static int parse_argv(int argc, char *argv[]) { if (!eq) { /* --blob=/some/path replaces the blob dir */ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", optarg); + return r; break; } @@ -4664,7 +4664,7 @@ static int parse_argv(int argc, char *argv[]) { r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", eq + 1); + return r; } else { const char *well_known_filename = c == ARG_AVATAR ? "avatar" : @@ -4678,7 +4678,7 @@ static int parse_argv(int argc, char *argv[]) { r = parse_path_argument(optarg, /* suppress_root= */ false, &path); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", optarg); + return r; } if (path) { diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 0bd4e84da0ad0..27e1a7426e4d1 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1604,13 +1604,13 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_esp_path); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_xbootldr_path); if (r < 0) - return log_oom(); + return r; break; OPTION_COMMON_MAKE_ENTRY_DIRECTORY: diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 2a66fabb19542..abdec1591d01c 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -193,7 +193,7 @@ static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -259,7 +259,7 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index eeb001f3fed4a..d5bb5bea2351c 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -960,7 +960,7 @@ static int build_policy_digest(bool sign) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index f5a88b2849fe2..9d163624a4997 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -461,7 +461,7 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( From 7330e9c7892d72d88f342371400071af0f2c815d Mon Sep 17 00:00:00 2001 From: Emanuele Rocca Date: Thu, 14 May 2026 13:31:24 +0200 Subject: [PATCH 1709/2155] test-fs-util: check for CAP_DAC_OVERRIDE in xopenat_auto_rw_ro When running test_xopenat_auto_rw_ro under a non-root user with the CAP_DAC_OVERRIDE capability, the test currently fails. As the comment already says, root bypasses mode bits via CAP_DAC_OVERRIDE so let's check for that instead of the effective user ID. Signed-off-by: Emanuele Rocca --- src/test/test-fs-util.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 23aa5a5815fd1..6fea526d67f73 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "argv-util.h" +#include "capability-util.h" #include "copy.h" #include "fd-util.h" #include "fs-util.h" @@ -851,9 +852,9 @@ TEST(xopenat_auto_rw_ro) { /* 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. */ + * this when running as root, or as a user with CAP_DAC_OVERRIDE. */ - if (geteuid() != 0) { + if (have_effective_cap(CAP_DAC_OVERRIDE) <= 0) { fd = openat(tfd, "ro", O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0444); assert_se(fd >= 0); fd = safe_close(fd); From 7d1b6b7b96ae3606d121d55956cdc774683041e8 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 14 May 2026 13:05:02 +0200 Subject: [PATCH 1710/2155] profile: bail out early if promptvars is disabled We need promptvars, otherwise the prompt strings won't undergo parameter expansion and we'd print them literally: $ shopt -u promptvars $ echo foo $(__systemd_osc_context_ps0)foo Resolves: #40620 --- profile.d/80-systemd-osc-context.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/profile.d/80-systemd-osc-context.sh b/profile.d/80-systemd-osc-context.sh index 7d4b9a77f1b39..35bbb924cb720 100644 --- a/profile.d/80-systemd-osc-context.sh +++ b/profile.d/80-systemd-osc-context.sh @@ -21,6 +21,10 @@ # Treat missing $TERM same as "dumb". [ "${TERM:-dumb}" = "dumb" ] && return 0 +# We need promptvars, otherwise the prompt strings won't undergo parameter expansion +# and we'd print them literally +shopt -q promptvars || return 0 + __systemd_osc_context_escape() { # Escape according to the OSC 3008 spec. Since this requires shelling out # to 'sed' we'll only do it where it's strictly necessary, and skip it when From 6649a47edaafa4805324ddd18cb9cb44132d9654 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 12 May 2026 13:03:49 +0000 Subject: [PATCH 1711/2155] nspawn: split boot parameters into env vars and argv When the kernel hands the command line to PID 1, any KEY=VALUE assignment whose KEY does not contain a '.' is exported as an environment variable (with '-' replaced by '_') rather than passed as an argument. Mimic the same split in --boot mode so kernel-cmdline-style arguments passed after the container path behave as they would on a real boot. --- man/systemd-nspawn.xml | 8 ++++++ src/nspawn/nspawn.c | 41 +++++++++++++++++++++++++++++ test/units/TEST-13-NSPAWN.nspawn.sh | 40 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 54d6f83915c7a..c93fad377627e 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -602,6 +602,14 @@ Note that is the default mode of operation if the systemd-nspawn@.service template unit file is used. + + When is used, the passed parameters are processed the same way the + kernel processes its command line before handing it to PID 1: any KEY=VALUE + assignment whose KEY does not contain a . is exported as + an environment variable for PID 1 (with - in the key replaced by + _, as if specified via ), while all other + parameters (including systemd.*= assignments) are passed as arguments to the + init program. diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index efe927f36e9b6..0e532cf7b0699 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1639,6 +1639,43 @@ static int verify_arguments(void) { return 0; } +static int split_boot_parameters(void) { + _cleanup_strv_free_ char **kept = NULL; + int r; + + /* When the kernel hands the command line to PID 1, any KEY=VALUE assignment whose KEY does not + * contain a '.' is exported as an environment variable (with '-' replaced by '_'), rather than + * passed as an argument. Mimic the same split here so users can pass kernel-cmdline-style + * arguments after the container path and get the behavior they'd get on a real boot. */ + + if (arg_start_mode != START_BOOT) + return 0; + + STRV_FOREACH(p, arg_parameters) { + _cleanup_free_ char *key = NULL, *value = NULL; + + if (split_pair(*p, "=", &key, &value) >= 0 && !strchr(key, '.')) { + string_replace_char(key, '-', '_'); + + if (env_name_is_valid(key) && env_value_is_valid(value)) { + r = strv_env_assign(&arg_setenv, key, value); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable: %m"); + + arg_settings_mask |= SETTING_ENVIRONMENT; + continue; + } + } + + r = strv_extend(&kept, *p); + if (r < 0) + return log_oom(); + } + + strv_free_and_replace(arg_parameters, kept); + return 0; +} + static int verify_network_interfaces_initialized(void) { int r; r = test_network_interfaces_initialized(arg_network_interfaces); @@ -6076,6 +6113,10 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + r = split_boot_parameters(); + if (r < 0) + goto finish; + r = resolve_network_interface_names(arg_network_interfaces); if (r < 0) goto finish; diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index 0332a12f64665..822a008732990 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -1561,6 +1561,46 @@ testcase_volatile_link_journal_no_userns() { rm -fr "$root" "$journal_dir" } +testcase_boot_param_split() { + local root outdir + + root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.boot-param-split.XXX)" + outdir="$(mktemp -d)" + create_dummy_container "$root" + + # Replace the init binary with a stub that records the argv and environment nspawn passes to it, + # so we can verify that kernel-cmdline-style KEY=VALUE arguments are split between PID 1's + # environment and argv the same way the kernel splits them. + cat >"$root/usr/lib/systemd/systemd" <<'EOF' +#!/bin/bash +set -e +printf '%s\n' "$@" >/output/argv +env >/output/env +EOF + chmod +x "$root/usr/lib/systemd/systemd" + + # Cover the assignments that should land in env (FOO=bar, baz-qux=hello → baz_qux), the + # dotted assignments that should stay as argv (systemd.unit=…, some.thing=…), and the malformed + # entries that look env-like but must also stay as argv: empty key (=value), key starting with + # a digit (123=foo), key with characters that aren't valid in an env var name (foo!=bar). + systemd-nspawn --register=no \ + --directory="$root" \ + --bind="$outdir:/output" \ + --boot \ + FOO=bar baz-qux=hello systemd.unit=foo.target some.thing=yes plain-arg \ + =empty-key 123=leading-digit 'foo!=bad-char' + + diff <(printf 'systemd.unit=foo.target\nsome.thing=yes\nplain-arg\n=empty-key\n123=leading-digit\nfoo!=bad-char\n') "$outdir/argv" + grep '^FOO=bar$' >/dev/null "$outdir/env" + grep '^baz_qux=hello$' >/dev/null "$outdir/env" + (! grep -E '^(systemd\.unit|some\.thing)=' >/dev/null "$outdir/env") + (! grep -E '^(FOO|baz_qux)=' >/dev/null "$outdir/argv") + (! grep -E '^(123|foo!?)=' >/dev/null "$outdir/env") + (! grep -E '^=' >/dev/null "$outdir/env") + + rm -fr "$root" "$outdir" +} + testcase_cap_net_bind_service() { local root From 868223f78caa93d9c6c6f1212602c99fb2683e6b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 21:58:24 +0200 Subject: [PATCH 1712/2155] btrfs: Beef up btrfs_subvol_make() Let's make sure we handle AT_FDCWD and XAT_FDROOT properly by using xopenat(). --- src/basic/btrfs.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/basic/btrfs.c b/src/basic/btrfs.c index 7981c670e338f..81c9839450734 100644 --- a/src/basic/btrfs.c +++ b/src/basic/btrfs.c @@ -8,6 +8,7 @@ #include "btrfs.h" #include "errno-util.h" #include "fd-util.h" +#include "fs-util.h" #include "path-util.h" #include "string-util.h" @@ -47,7 +48,7 @@ int btrfs_subvol_make(int dir_fd, const char *path) { _cleanup_close_ int fd = -EBADF; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(!isempty(path)); r = extract_subvolume_name(path, &subvolume); @@ -55,24 +56,16 @@ int btrfs_subvol_make(int dir_fd, const char *path) { return r; r = path_extract_directory(path, &parent); - if (r < 0) { - if (r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */ - return r; - - dir_fd = fd_reopen_condition(dir_fd, O_CLOEXEC, O_PATH, &fd); /* drop O_PATH if it is set */ - if (dir_fd < 0) - return dir_fd; - } else { - fd = openat(dir_fd, parent, O_DIRECTORY|O_RDONLY|O_CLOEXEC, 0); - if (fd < 0) - return -errno; + if (r < 0 && r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */ + return r; - dir_fd = fd; - } + fd = xopenat(dir_fd, parent ?: ".", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (fd < 0) + return fd; strncpy(args.name, subvolume, sizeof(args.name)-1); - return RET_NERRNO(ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args)); + return RET_NERRNO(ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args)); } int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode) { From bb233dcb4be131990e2222c8a65e63f6771e28fa Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 17 Mar 2026 23:55:51 +0000 Subject: [PATCH 1713/2155] coredump: add JSON output support to coredumpctl info Implement support for the --json= flag in the info subcommand (issue #38844). Previously, coredumpctl info always produced human-readable text output regardless of --json=. Add a CoredumpFields struct that holds all journal fields extracted for a coredump entry, along with coredump_fields_done() to release member resources and coredump_fields_load() to populate the struct from a journal entry. Both print_info() and the new print_info_json() use this shared loader, eliminating the duplicate RETRIEVE loop. print_info_json() builds a JSON object with the same fields shown by print_info(). Missing fields are omitted via SD_JSON_BUILD_PAIR_CONDITION, matching the tolerant behavior of print_info() rather than skipping the entry entirely. Signal/Reason handling mirrors print_info(): normal coredumps (MESSAGE_ID == SD_MESSAGE_COREDUMP_STR) emit a numeric Signal field; non-normal entries (kernel oops, etc.) emit a Reason field with the raw text from COREDUMP_SIGNAL. Co-developed-by: Claude Opus 4.6 --- src/coredump/coredumpctl.c | 430 +++++++++++++------- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 13 + 2 files changed, 296 insertions(+), 147 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index d89da78755f35..7bb1fd175213c 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -579,239 +579,293 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { return 0; } -static int print_info(FILE *file, sd_journal *j, bool need_space) { - _cleanup_free_ char - *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, - *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, - *unit = NULL, *user_unit = NULL, *session = NULL, - *boot_id = NULL, *machine_id = NULL, *hostname = NULL, - *slice = NULL, *cgroup = NULL, *owner_uid = NULL, - *message = NULL, *timestamp = NULL, *filename = NULL, - *truncated = NULL, - *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL, - *tid = NULL, *thread_name = NULL; +typedef enum CoredumpField { + COREDUMP_FIELD_MID, + COREDUMP_FIELD_PID, + COREDUMP_FIELD_UID, + COREDUMP_FIELD_GID, + COREDUMP_FIELD_SGNL, + COREDUMP_FIELD_EXE, + COREDUMP_FIELD_COMM, + COREDUMP_FIELD_CMDLINE, + COREDUMP_FIELD_HOSTNAME, + COREDUMP_FIELD_UNIT, + COREDUMP_FIELD_USER_UNIT, + COREDUMP_FIELD_SESSION, + COREDUMP_FIELD_OWNER_UID, + COREDUMP_FIELD_SLICE, + COREDUMP_FIELD_CGROUP, + COREDUMP_FIELD_TIMESTAMP, + COREDUMP_FIELD_FILENAME, + COREDUMP_FIELD_TRUNCATED, + COREDUMP_FIELD_PKGMETA_NAME, + COREDUMP_FIELD_PKGMETA_VERSION, + COREDUMP_FIELD_PKGMETA_JSON, + COREDUMP_FIELD_TID, + COREDUMP_FIELD_THREAD_NAME, + COREDUMP_FIELD_BOOT_ID, + COREDUMP_FIELD_MACHINE_ID, + COREDUMP_FIELD_MESSAGE, + _COREDUMP_FIELD_MAX, +} CoredumpField; + +static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = { + [COREDUMP_FIELD_MID] = "MESSAGE_ID", + [COREDUMP_FIELD_PID] = "COREDUMP_PID", + [COREDUMP_FIELD_UID] = "COREDUMP_UID", + [COREDUMP_FIELD_GID] = "COREDUMP_GID", + [COREDUMP_FIELD_SGNL] = "COREDUMP_SIGNAL", + [COREDUMP_FIELD_EXE] = "COREDUMP_EXE", + [COREDUMP_FIELD_COMM] = "COREDUMP_COMM", + [COREDUMP_FIELD_CMDLINE] = "COREDUMP_CMDLINE", + [COREDUMP_FIELD_HOSTNAME] = "COREDUMP_HOSTNAME", + [COREDUMP_FIELD_UNIT] = "COREDUMP_UNIT", + [COREDUMP_FIELD_USER_UNIT] = "COREDUMP_USER_UNIT", + [COREDUMP_FIELD_SESSION] = "COREDUMP_SESSION", + [COREDUMP_FIELD_OWNER_UID] = "COREDUMP_OWNER_UID", + [COREDUMP_FIELD_SLICE] = "COREDUMP_SLICE", + [COREDUMP_FIELD_CGROUP] = "COREDUMP_CGROUP", + [COREDUMP_FIELD_TIMESTAMP] = "COREDUMP_TIMESTAMP", + [COREDUMP_FIELD_FILENAME] = "COREDUMP_FILENAME", + [COREDUMP_FIELD_TRUNCATED] = "COREDUMP_TRUNCATED", + [COREDUMP_FIELD_PKGMETA_NAME] = "COREDUMP_PACKAGE_NAME", + [COREDUMP_FIELD_PKGMETA_VERSION] = "COREDUMP_PACKAGE_VERSION", + [COREDUMP_FIELD_PKGMETA_JSON] = "COREDUMP_PACKAGE_JSON", + [COREDUMP_FIELD_TID] = "COREDUMP_TID", + [COREDUMP_FIELD_THREAD_NAME] = "COREDUMP_THREAD_NAME", + [COREDUMP_FIELD_BOOT_ID] = "_BOOT_ID", + [COREDUMP_FIELD_MACHINE_ID] = "_MACHINE_ID", + [COREDUMP_FIELD_MESSAGE] = "MESSAGE", +}; + +typedef struct CoredumpFields { + char *fields[_COREDUMP_FIELD_MAX]; + + bool normal_coredump; + const char *storage_state; /* points to a static string, not owned */ + const char *storage_color; /* points to a static string, not owned */ + uint64_t disk_size; + sd_json_variant *package_json; +} CoredumpFields; + +static void coredump_fields_done(CoredumpFields *f) { + assert(f); + + free_many_charp(f->fields, _COREDUMP_FIELD_MAX); + sd_json_variant_unref(f->package_json); +} + +static int coredump_fields_load(sd_journal *j, CoredumpFields *ret) { const void *d; size_t l; - bool normal_coredump, has_inline_coredump; int r; - assert(file); assert(j); + assert(ret); (void) sd_journal_set_data_threshold(j, 0); SD_JOURNAL_FOREACH_DATA(j, d, l) { - RETRIEVE(d, l, "MESSAGE_ID", mid); - RETRIEVE(d, l, "COREDUMP_PID", pid); - RETRIEVE(d, l, "COREDUMP_UID", uid); - RETRIEVE(d, l, "COREDUMP_GID", gid); - RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl); - RETRIEVE(d, l, "COREDUMP_EXE", exe); - RETRIEVE(d, l, "COREDUMP_COMM", comm); - RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline); - RETRIEVE(d, l, "COREDUMP_HOSTNAME", hostname); - RETRIEVE(d, l, "COREDUMP_UNIT", unit); - RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit); - RETRIEVE(d, l, "COREDUMP_SESSION", session); - RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid); - RETRIEVE(d, l, "COREDUMP_SLICE", slice); - RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup); - RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp); - RETRIEVE(d, l, "COREDUMP_FILENAME", filename); - RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); - 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); + for (CoredumpField i = 0; i < _COREDUMP_FIELD_MAX; i++) { + int k = retrieve(d, l, coredump_field_table[i], &ret->fields[i]); + if (k < 0) + return k; + if (k > 0) + break; + } } - /* 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; + ret->normal_coredump = streq_ptr(ret->fields[COREDUMP_FIELD_MID], SD_MESSAGE_COREDUMP_STR); + + if (ret->fields[COREDUMP_FIELD_FILENAME]) { + r = resolve_filename(arg_root, &ret->fields[COREDUMP_FIELD_FILENAME]); + if (r < 0) + return r; + + analyze_coredump_file(ret->fields[COREDUMP_FIELD_FILENAME], &ret->storage_state, &ret->storage_color, &ret->disk_size); + + if (STRPTR_IN_SET(ret->storage_state, "present", "journal") && ret->fields[COREDUMP_FIELD_TRUNCATED] && parse_boolean(ret->fields[COREDUMP_FIELD_TRUNCATED]) > 0) + ret->storage_state = "truncated"; + } else if (sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0) + ret->storage_state = "journal"; + else + ret->storage_state = "none"; + + if (ret->fields[COREDUMP_FIELD_PKGMETA_JSON]) { + r = sd_json_parse(ret->fields[COREDUMP_FIELD_PKGMETA_JSON], SD_JSON_PARSE_MUST_BE_OBJECT, &ret->package_json, NULL, NULL); + if (r < 0) { + _cleanup_free_ char *esc = cescape(ret->fields[COREDUMP_FIELD_PKGMETA_JSON]); + log_warning_errno(r, "Failed to parse COREDUMP_PACKAGE_JSON \"%s\", ignoring: %m", strnull(esc)); + } + } + + return 0; +} + +static int print_info(FILE *file, sd_journal *j, bool need_space) { + _cleanup_(coredump_fields_done) CoredumpFields f = { + .disk_size = UINT64_MAX, + }; + int r; + + assert(file); + assert(j); + + r = coredump_fields_load(j, &f); + if (r < 0) + return r; if (need_space) fputs("\n", file); - normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR); - - if (comm) + if (f.fields[COREDUMP_FIELD_COMM]) fprintf(file, " PID: %s%s%s (%s)\n", - ansi_highlight(), strna(pid), ansi_normal(), comm); + ansi_highlight(), strna(f.fields[COREDUMP_FIELD_PID]), ansi_normal(), f.fields[COREDUMP_FIELD_COMM]); else fprintf(file, " PID: %s%s%s\n", - ansi_highlight(), strna(pid), ansi_normal()); + ansi_highlight(), strna(f.fields[COREDUMP_FIELD_PID]), ansi_normal()); - if (tid) { - if (thread_name) - fprintf(file, " TID: %s (%s)\n", tid, thread_name); + if (f.fields[COREDUMP_FIELD_TID]) { + if (f.fields[COREDUMP_FIELD_THREAD_NAME]) + fprintf(file, " TID: %s (%s)\n", f.fields[COREDUMP_FIELD_TID], f.fields[COREDUMP_FIELD_THREAD_NAME]); else - fprintf(file, " TID: %s\n", tid); + fprintf(file, " TID: %s\n", f.fields[COREDUMP_FIELD_TID]); } - if (uid) { + if (f.fields[COREDUMP_FIELD_UID]) { uid_t n; - if (parse_uid(uid, &n) >= 0) { + if (parse_uid(f.fields[COREDUMP_FIELD_UID], &n) >= 0) { _cleanup_free_ char *u = NULL; u = uid_to_name(n); fprintf(file, " UID: %s (%s)\n", - uid, u); - } else { + f.fields[COREDUMP_FIELD_UID], u); + } else fprintf(file, " UID: %s\n", - uid); - } + f.fields[COREDUMP_FIELD_UID]); } - if (gid) { + if (f.fields[COREDUMP_FIELD_GID]) { gid_t n; - if (parse_gid(gid, &n) >= 0) { + if (parse_gid(f.fields[COREDUMP_FIELD_GID], &n) >= 0) { _cleanup_free_ char *g = NULL; g = gid_to_name(n); fprintf(file, " GID: %s (%s)\n", - gid, g); - } else { + f.fields[COREDUMP_FIELD_GID], g); + } else fprintf(file, " GID: %s\n", - gid); - } + f.fields[COREDUMP_FIELD_GID]); } - if (sgnl) { + if (f.fields[COREDUMP_FIELD_SGNL]) { int sig; - const char *name = normal_coredump ? "Signal" : "Reason"; + const char *name = f.normal_coredump ? "Signal" : "Reason"; - if (normal_coredump && safe_atoi(sgnl, &sig) >= 0) - fprintf(file, " %s: %s (%s)\n", name, sgnl, signal_to_string(sig)); + if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0) + fprintf(file, " %s: %s (%s)\n", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig)); else - fprintf(file, " %s: %s\n", name, sgnl); + fprintf(file, " %s: %s\n", name, f.fields[COREDUMP_FIELD_SGNL]); } - if (timestamp) { + if (f.fields[COREDUMP_FIELD_TIMESTAMP]) { usec_t u; - r = safe_atou64(timestamp, &u); + r = safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &u); if (r >= 0) fprintf(file, " Timestamp: %s (%s)\n", FORMAT_TIMESTAMP(u), FORMAT_TIMESTAMP_RELATIVE(u)); - else - fprintf(file, " Timestamp: %s\n", timestamp); + fprintf(file, " Timestamp: %s\n", f.fields[COREDUMP_FIELD_TIMESTAMP]); } - if (cmdline) - fprintf(file, " Command Line: %s\n", cmdline); - if (exe) - fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal()); - if (cgroup) - fprintf(file, " Control Group: %s\n", cgroup); - if (unit) - fprintf(file, " Unit: %s\n", unit); - if (user_unit) - fprintf(file, " User Unit: %s\n", user_unit); - if (slice) - fprintf(file, " Slice: %s\n", slice); - if (session) - fprintf(file, " Session: %s\n", session); - if (owner_uid) { + if (f.fields[COREDUMP_FIELD_CMDLINE]) + fprintf(file, " Command Line: %s\n", f.fields[COREDUMP_FIELD_CMDLINE]); + if (f.fields[COREDUMP_FIELD_EXE]) + fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), f.fields[COREDUMP_FIELD_EXE], ansi_normal()); + if (f.fields[COREDUMP_FIELD_CGROUP]) + fprintf(file, " Control Group: %s\n", f.fields[COREDUMP_FIELD_CGROUP]); + if (f.fields[COREDUMP_FIELD_UNIT]) + fprintf(file, " Unit: %s\n", f.fields[COREDUMP_FIELD_UNIT]); + if (f.fields[COREDUMP_FIELD_USER_UNIT]) + fprintf(file, " User Unit: %s\n", f.fields[COREDUMP_FIELD_USER_UNIT]); + if (f.fields[COREDUMP_FIELD_SLICE]) + fprintf(file, " Slice: %s\n", f.fields[COREDUMP_FIELD_SLICE]); + if (f.fields[COREDUMP_FIELD_SESSION]) + fprintf(file, " Session: %s\n", f.fields[COREDUMP_FIELD_SESSION]); + if (f.fields[COREDUMP_FIELD_OWNER_UID]) { uid_t n; - if (parse_uid(owner_uid, &n) >= 0) { + if (parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &n) >= 0) { _cleanup_free_ char *u = NULL; u = uid_to_name(n); fprintf(file, " Owner UID: %s (%s)\n", - owner_uid, u); - } else { + f.fields[COREDUMP_FIELD_OWNER_UID], u); + } else fprintf(file, " Owner UID: %s\n", - owner_uid); - } + f.fields[COREDUMP_FIELD_OWNER_UID]); } - if (boot_id) - fprintf(file, " Boot ID: %s\n", boot_id); - if (machine_id) - fprintf(file, " Machine ID: %s\n", machine_id); - if (hostname) - fprintf(file, " Hostname: %s\n", hostname); - - if (filename) { - r = resolve_filename(arg_root, &filename); - if (r < 0) - return r; - - const char *state = NULL, *color = NULL; - uint64_t size = UINT64_MAX; - - analyze_coredump_file(filename, &state, &color, &size); - - if (STRPTR_IN_SET(state, "present", "journal") && truncated && parse_boolean(truncated) > 0) - state = "truncated"; - + if (f.fields[COREDUMP_FIELD_BOOT_ID]) + fprintf(file, " Boot ID: %s\n", f.fields[COREDUMP_FIELD_BOOT_ID]); + if (f.fields[COREDUMP_FIELD_MACHINE_ID]) + fprintf(file, " Machine ID: %s\n", f.fields[COREDUMP_FIELD_MACHINE_ID]); + if (f.fields[COREDUMP_FIELD_HOSTNAME]) + fprintf(file, " Hostname: %s\n", f.fields[COREDUMP_FIELD_HOSTNAME]); + + if (f.fields[COREDUMP_FIELD_FILENAME]) { fprintf(file, " Storage: %s%s (%s)%s\n", - strempty(color), - filename, - state, + strempty(f.storage_color), + f.fields[COREDUMP_FIELD_FILENAME], + f.storage_state, ansi_normal()); - if (size != UINT64_MAX) - fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(size)); - - } else if (has_inline_coredump) - fprintf(file, " Storage: journal\n"); - else - fprintf(file, " Storage: none\n"); + if (f.disk_size != UINT64_MAX) + fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(f.disk_size)); + } else + fprintf(file, " Storage: %s\n", f.storage_state); - if (pkgmeta_name && pkgmeta_version) - fprintf(file, " Package: %s/%s\n", pkgmeta_name, pkgmeta_version); + if (f.fields[COREDUMP_FIELD_PKGMETA_NAME] && f.fields[COREDUMP_FIELD_PKGMETA_VERSION]) + fprintf(file, " Package: %s/%s\n", f.fields[COREDUMP_FIELD_PKGMETA_NAME], f.fields[COREDUMP_FIELD_PKGMETA_VERSION]); /* Print out the build-id of the 'main' ELF module, by matching the JSON key * with the 'exe' field. */ - if (exe && pkgmeta_json) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (f.fields[COREDUMP_FIELD_EXE] && f.package_json) { + const char *module_name; + sd_json_variant *module_json; - 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)); - } else { - const char *module_name; - sd_json_variant *module_json; + JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, f.package_json) { + sd_json_variant *build_id; - JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, v) { - sd_json_variant *build_id; - - /* We only print the build-id for the 'main' ELF module */ - if (!path_equal_filename(module_name, exe)) - continue; + /* We only print the build-id for the 'main' ELF module */ + if (!path_equal_filename(module_name, f.fields[COREDUMP_FIELD_EXE])) + continue; - build_id = sd_json_variant_by_key(module_json, "buildId"); - if (build_id) - fprintf(file, " build-id: %s\n", sd_json_variant_string(build_id)); + build_id = sd_json_variant_by_key(module_json, "buildId"); + if (build_id) + fprintf(file, " build-id: %s\n", sd_json_variant_string(build_id)); - break; - } + break; } } - if (message) { + if (f.fields[COREDUMP_FIELD_MESSAGE]) { _cleanup_free_ char *m = NULL; - m = strreplace(message, "\n", "\n "); + m = strreplace(f.fields[COREDUMP_FIELD_MESSAGE], "\n", "\n "); - fprintf(file, " Message: %s\n", strstrip(m ?: message)); + fprintf(file, " Message: %s\n", strstrip(m ?: f.fields[COREDUMP_FIELD_MESSAGE])); } return 0; @@ -831,6 +885,86 @@ static int focus(sd_journal *j) { return r; } +static int print_info_json(FILE *file, sd_journal *j) { + _cleanup_(coredump_fields_done) CoredumpFields f = { + .disk_size = UINT64_MAX, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + pid_t pid_as_int = 0, tid_as_int = 0; + uid_t uid_as_int = UID_INVALID, owner_uid_as_int = UID_INVALID; + gid_t gid_as_int = GID_INVALID; + int sig_as_int = 0; + usec_t ts = USEC_INFINITY; + int r; + + assert(file); + assert(j); + + r = coredump_fields_load(j, &f); + if (r < 0) + return r; + + if (f.fields[COREDUMP_FIELD_PID]) + (void) parse_pid(f.fields[COREDUMP_FIELD_PID], &pid_as_int); + if (f.fields[COREDUMP_FIELD_TID]) + (void) parse_pid(f.fields[COREDUMP_FIELD_TID], &tid_as_int); + if (f.fields[COREDUMP_FIELD_UID]) + (void) parse_uid(f.fields[COREDUMP_FIELD_UID], &uid_as_int); + if (f.fields[COREDUMP_FIELD_GID]) + (void) parse_gid(f.fields[COREDUMP_FIELD_GID], &gid_as_int); + if (f.fields[COREDUMP_FIELD_OWNER_UID]) + (void) parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &owner_uid_as_int); + if (f.normal_coredump && f.fields[COREDUMP_FIELD_SGNL]) + (void) safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig_as_int); + if (f.fields[COREDUMP_FIELD_TIMESTAMP]) + (void) safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &ts); + + r = sd_json_build(&v, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(pid_as_int), "PID", SD_JSON_BUILD_UNSIGNED(pid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!pid_is_valid(pid_as_int) && !!f.fields[COREDUMP_FIELD_PID], "PID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PID])), + SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(tid_as_int), "TID", SD_JSON_BUILD_UNSIGNED(tid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!pid_is_valid(tid_as_int) && !!f.fields[COREDUMP_FIELD_TID], "TID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_THREAD_NAME], "ThreadName", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_THREAD_NAME])), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid_as_int), "UID", SD_JSON_BUILD_UNSIGNED(uid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!uid_is_valid(uid_as_int) && !!f.fields[COREDUMP_FIELD_UID], "UID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_UID])), + SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(gid_as_int), "GID", SD_JSON_BUILD_UNSIGNED(gid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!gid_is_valid(gid_as_int) && !!f.fields[COREDUMP_FIELD_GID], "GID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_GID])), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0, "Signal", SD_JSON_BUILD_INTEGER(sig_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0 && !!signal_to_string(sig_as_int), "SignalName", SD_JSON_BUILD_STRING(signal_to_string(sig_as_int))), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int <= 0 && !!f.fields[COREDUMP_FIELD_SGNL], "Signal", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), + SD_JSON_BUILD_PAIR_CONDITION(!f.normal_coredump && !!f.fields[COREDUMP_FIELD_SGNL], "Reason", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), + SD_JSON_BUILD_PAIR_CONDITION(ts != USEC_INFINITY, "Timestamp", SD_JSON_BUILD_UNSIGNED(ts)), + SD_JSON_BUILD_PAIR_CONDITION(ts == USEC_INFINITY && !!f.fields[COREDUMP_FIELD_TIMESTAMP], "Timestamp", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TIMESTAMP])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_EXE], "Executable", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_EXE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_COMM], "Command", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_COMM])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_CMDLINE], "CommandLine", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CMDLINE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_CGROUP], "ControlGroup", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CGROUP])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_UNIT], "Unit", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_UNIT])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_USER_UNIT], "UserUnit", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_USER_UNIT])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_SLICE], "Slice", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SLICE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_SESSION], "Session", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SESSION])), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(owner_uid_as_int), "OwnerUID", SD_JSON_BUILD_UNSIGNED(owner_uid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!uid_is_valid(owner_uid_as_int) && !!f.fields[COREDUMP_FIELD_OWNER_UID], "OwnerUID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_OWNER_UID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_BOOT_ID], "BootID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_BOOT_ID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_MACHINE_ID], "MachineID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_MACHINE_ID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_HOSTNAME], "Hostname", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_HOSTNAME])), + SD_JSON_BUILD_PAIR("Storage", SD_JSON_BUILD_STRING(f.storage_state)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_FILENAME], "Filename", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_FILENAME])), + SD_JSON_BUILD_PAIR_CONDITION(f.disk_size != UINT64_MAX, "DiskSize", SD_JSON_BUILD_UNSIGNED(f.disk_size)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_PKGMETA_NAME], "PackageName", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PKGMETA_NAME])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_PKGMETA_VERSION], "PackageVersion", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PKGMETA_VERSION])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.package_json, "Package", SD_JSON_BUILD_VARIANT(f.package_json)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_MESSAGE], "Message", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_MESSAGE])))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + r = sd_json_variant_dump(v, arg_json_format_flags, file, NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump JSON object: %m"); + + return 0; +} + static int print_entry( sd_journal *j, size_t n_found, @@ -842,6 +976,8 @@ static int print_entry( return print_list(stdout, j, t); else if (arg_field) return print_field(stdout, j); + else if (sd_json_format_enabled(arg_json_format_flags)) + return print_info_json(stdout, j); else return print_info(stdout, j, n_found > 0); } @@ -878,7 +1014,7 @@ static int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdat (void) table_set_align_percent(t, TABLE_HEADER_CELL(7), 100); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); - } else + } else if (!sd_json_format_enabled(arg_json_format_flags)) pager_open(arg_pager_flags); /* "info" without pattern implies "-1" */ diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index b3a3de76960a3..30252132ff85f 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -149,6 +149,17 @@ coredumpctl info "${CORE_TEST_BIN##*/}" 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" +# JSON output for info subcommand (issue #38844) +coredumpctl info --json=short "$CORE_TEST_BIN" | jq +coredumpctl info --json=pretty "$CORE_TEST_BIN" | jq +coredumpctl info --json=off "$CORE_TEST_BIN" +# Verify that mandatory fields are present and have valid values across all matching entries +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'length > 0' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; .PID > 0)' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; .Signal > 0)' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Executable"))' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Command"))' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Storage"))' # Check that COREDUMP_TID= is present and displayed by coredumpctl info coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null @@ -192,6 +203,8 @@ coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" "${UNPRIV_CMD[@]}" coredumpctl "${UNPRIV_CMD[@]}" coredumpctl info "$CORE_TEST_UNPRIV_BIN" "${UNPRIV_CMD[@]}" coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" +# Verify JSON output for unprivileged coredumps +"${UNPRIV_CMD[@]}" coredumpctl info --json=short "$CORE_TEST_UNPRIV_BIN" | jq (! "${UNPRIV_CMD[@]}" coredumpctl info --all "$CORE_TEST_BIN") (! "${UNPRIV_CMD[@]}" coredumpctl info --all "${CORE_TEST_BIN##*/}") # We should have a couple of externally stored coredumps From 59203dca3af581be32f383263e2a3e4bc406c2e4 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 14 May 2026 09:41:09 -0700 Subject: [PATCH 1714/2155] core: move service_context_build_json() to varlink-service.c Move the existing (partial) service context builder from varlink-unit.c into its own varlink-service.c file, following the pattern established by other unit type context builders (varlink-path.c, varlink-scope.c, etc.). No functional change. --- src/core/meson.build | 1 + src/core/varlink-service.c | 22 ++++++++++++++++++++++ src/core/varlink-service.h | 6 ++++++ src/core/varlink-unit.c | 15 +-------------- 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 src/core/varlink-service.c create mode 100644 src/core/varlink-service.h diff --git a/src/core/meson.build b/src/core/meson.build index 3af15b948fe17..98cf02aef8879 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -76,6 +76,7 @@ libcore_sources = files( 'varlink-mount.c', 'varlink-path.c', 'varlink-scope.c', + 'varlink-service.c', 'varlink-socket.c', 'varlink-swap.c', 'varlink-timer.c', diff --git a/src/core/varlink-service.c b/src/core/varlink-service.c new file mode 100644 index 0000000000000..8d9ed6e91db7d --- /dev/null +++ b/src/core/varlink-service.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "service.h" +#include "varlink-common.h" +#include "varlink-service.h" + +/* TODO: This covers only a small subset of a service object's properties. Extend to make more available to + * consumers like Unit.StartTransient */ +int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Service *s = ASSERT_PTR(SERVICE(u)); + assert(ret); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit)); +} diff --git a/src/core/varlink-service.h b/src/core/varlink-service.h new file mode 100644 index 0000000000000..07ed1b9d1b0f9 --- /dev/null +++ b/src/core/varlink-service.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int service_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 f7ef7506493c2..368e39a63edcb 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -31,6 +31,7 @@ #include "varlink-mount.h" #include "varlink-path.h" #include "varlink-scope.h" +#include "varlink-service.h" #include "varlink-socket.h" #include "varlink-swap.h" #include "varlink-timer.h" @@ -119,20 +120,6 @@ static int unit_conditions_build_json(sd_json_variant **ret, const char *name, v return 0; } -/* TODO: This covers only a small subset of a service object's properties. Extend to make more available to - * consumers like Unit.StartTransient */ -static int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { - Unit *u = ASSERT_PTR(userdata); - Service *s = ASSERT_PTR(SERVICE(u)); - assert(ret); - - return sd_json_buildo( - ret, - JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), - SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit)); -} - static int unit_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { Unit *u = ASSERT_PTR(userdata); From a03f3af3e3816da18b677cf2de585ec0cac452ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=91=E8=B6=85=E5=8E=89=E5=AE=B3?= <524413304@qq.com> Date: Fri, 15 May 2026 01:51:29 +0800 Subject: [PATCH 1715/2155] sd-device: use ERRNO_IS_NEG_DEVICE_ABSENT() for device-id load failures (#41764) Device enumeration may encounter transient errors such as ENXIO when devices appear or disappear concurrently. These conditions represent expected "device absent" races and should be treated uniformly across the enumeration logic. This change replaces the ENODEV-specific check with ERRNO_IS_NEG_DEVICE_ABSENT(), ensuring that all expected disappearance conditions are handled consistently. Unexpected errors are still propagated, while expected races are ignored without aborting the enumeration. --- src/libsystemd/sd-device/device-enumerator.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c index d1a48defe906c..6c1e79edd588c 100644 --- a/src/libsystemd/sd-device/device-enumerator.c +++ b/src/libsystemd/sd-device/device-enumerator.c @@ -9,6 +9,7 @@ #include "device-filter.h" #include "device-util.h" #include "dirent-util.h" +#include "errno-util.h" #include "fd-util.h" #include "log.h" #include "path-util.h" @@ -741,7 +742,7 @@ static int enumerator_scan_dir_and_add_devices( k = sd_device_new_from_syspath(&device, syspath); if (k < 0) { - if (k != -ENODEV) + if (!ERRNO_IS_NEG_DEVICE_ABSENT(k)) /* this is necessarily racey, so ignore missing devices */ r = k; @@ -836,7 +837,7 @@ static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const c k = sd_device_new_from_device_id(&device, de->d_name); if (k < 0) { - if (k != -ENODEV) + if (!ERRNO_IS_NEG_DEVICE_ABSENT(k)) /* this is necessarily racy, so ignore missing devices */ r = k; @@ -882,7 +883,7 @@ static int parent_add_child(sd_device_enumerator *enumerator, const char *path, int r; r = sd_device_new_from_syspath(&device, path); - if (r == -ENODEV) + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* this is necessarily racy, so ignore missing devices */ return 0; else if (r < 0) From 953ff3c83e95ae3943e4307b64fe429d56cc8ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 10:35:30 +0200 Subject: [PATCH 1716/2155] core: reorder cases in parse_argv() to match order in --help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hidden-from-help options (--crash-reboot, --service-watchdogs, --deserialize, --switched-root, --machine-id, -D, -b/-s/-z, ?) move to the bottom. The 'b'/'s'/'z' → '?' fall-through is preserved. Co-developed-by: Claude Opus 4.7 --- src/core/main.c | 178 ++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 88 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index c10df7d87a4fc..dc55ea62e91c8 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -990,13 +990,92 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + case 'h': + arg_action = ACTION_HELP; + break; + + case ARG_VERSION: + arg_action = ACTION_VERSION; + break; + + case ARG_TEST: + arg_action = ACTION_TEST; + break; + + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + user_arg_seen = true; + break; + + case ARG_DUMP_CONFIGURATION_ITEMS: + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + + case ARG_DUMP_BUS_PROPERTIES: + arg_action = ACTION_DUMP_BUS_PROPERTIES; + break; + + case ARG_BUS_INTROSPECT: + arg_bus_introspect = optarg; + arg_action = ACTION_BUS_INTROSPECT; + break; + + case ARG_UNIT: + r = free_and_strdup(&arg_default_unit, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse log level \"%s\": %m", optarg); + return log_error_errno(r, "Failed to set default unit \"%s\": %m", optarg); break; + case ARG_DUMP_CORE: + r = parse_boolean_argument("--dump-core", optarg, &arg_dump_core); + if (r < 0) + return r; + break; + + case ARG_CRASH_CHVT: + r = parse_crash_chvt(optarg, &arg_crash_chvt); + if (r < 0) + return log_error_errno(r, "Failed to parse crash virtual terminal index: \"%s\": %m", + optarg); + break; + + case ARG_CRASH_ACTION: + r = crash_action_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash action \"%s\": %m", optarg); + arg_crash_action = r; + break; + + case ARG_CRASH_SHELL: + r = parse_boolean_argument("--crash-shell", optarg, &arg_crash_shell); + if (r < 0) + return r; + break; + + case ARG_CONFIRM_SPAWN: + arg_confirm_spawn = mfree(arg_confirm_spawn); + + r = parse_confirm_spawn(optarg, &arg_confirm_spawn); + if (r < 0) + return log_error_errno(r, "Failed to parse confirm spawn option: \"%s\": %m", + optarg); + break; + + case ARG_SHOW_STATUS: + if (optarg) { + r = parse_show_status(optarg, &arg_show_status); + if (r < 0) + return log_error_errno(r, "Failed to parse show status boolean: \"%s\": %m", + optarg); + } else + arg_show_status = SHOW_STATUS_YES; + break; + case ARG_LOG_TARGET: r = log_set_target_from_string(optarg); if (r < 0) @@ -1004,6 +1083,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse log level \"%s\": %m", optarg); + + break; + case ARG_LOG_COLOR: if (optarg) { @@ -1055,65 +1141,11 @@ static int parse_argv(int argc, char *argv[]) { arg_defaults.std_error = r; break; - case ARG_UNIT: - r = free_and_strdup(&arg_default_unit, optarg); - if (r < 0) - return log_error_errno(r, "Failed to set default unit \"%s\": %m", optarg); - - break; - - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - user_arg_seen = true; - break; - - case ARG_TEST: - arg_action = ACTION_TEST; - break; - case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_VERSION: - arg_action = ACTION_VERSION; - break; - - case ARG_DUMP_CONFIGURATION_ITEMS: - arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; - break; - - case ARG_DUMP_BUS_PROPERTIES: - arg_action = ACTION_DUMP_BUS_PROPERTIES; - break; - - case ARG_BUS_INTROSPECT: - arg_bus_introspect = optarg; - arg_action = ACTION_BUS_INTROSPECT; - break; - - case ARG_DUMP_CORE: - r = parse_boolean_argument("--dump-core", optarg, &arg_dump_core); - if (r < 0) - return r; - break; - - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg, &arg_crash_chvt); - if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: \"%s\": %m", - optarg); - break; - - case ARG_CRASH_SHELL: - r = parse_boolean_argument("--crash-shell", optarg, &arg_crash_shell); - if (r < 0) - return r; - break; + /* Options not shown in --help. */ case ARG_CRASH_REBOOT: r = parse_boolean_argument("--crash-reboot", optarg, NULL); @@ -1122,38 +1154,12 @@ static int parse_argv(int argc, char *argv[]) { arg_crash_action = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; break; - case ARG_CRASH_ACTION: - r = crash_action_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash action \"%s\": %m", optarg); - arg_crash_action = r; - break; - - case ARG_CONFIRM_SPAWN: - arg_confirm_spawn = mfree(arg_confirm_spawn); - - r = parse_confirm_spawn(optarg, &arg_confirm_spawn); - if (r < 0) - return log_error_errno(r, "Failed to parse confirm spawn option: \"%s\": %m", - optarg); - break; - case ARG_SERVICE_WATCHDOGS: r = parse_boolean_argument("--service-watchdogs=", optarg, &arg_service_watchdogs); if (r < 0) return r; break; - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); - if (r < 0) - return log_error_errno(r, "Failed to parse show status boolean: \"%s\": %m", - optarg); - } else - arg_show_status = SHOW_STATUS_YES; - break; - case ARG_DESERIALIZE: { int fd; FILE *f; @@ -1184,10 +1190,6 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "MachineID '%s' is not valid: %m", optarg); break; - case 'h': - arg_action = ACTION_HELP; - break; - case 'D': log_set_max_level(LOG_DEBUG); break; From 2289ac82d4150174212e716b696b7bddb4f240d7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 14 May 2026 17:56:56 +0200 Subject: [PATCH 1717/2155] shared/options: introduce OPTION_ERROR [zjs: this was originally proposed in https://github.com/systemd/systemd/commit/930fc9d6980f27b278527b0d6117f97296fcaf6a. I'm rescuing one chunk from that patch.] --- src/shared/options.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/options.h b/src/shared/options.h index d60c9afe81bfe..a6c55e0bdc51a 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -80,6 +80,10 @@ typedef struct Option { #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) +/* This can be used when custom error handling is needed. */ +#define OPTION_ERROR \ + case INT_MIN ... -1 + #define OPTION_COMMON_HELP \ OPTION('h', "help", NULL, "Show this help") From 39f4185c698fb2c41502ee16700a625144678292 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 13 Apr 2026 19:03:27 +0200 Subject: [PATCH 1718/2155] network: honour static IPv6LL addresses in network_adjust_*() link_radv_enabled() and link_ndisc_enabled() use link_ipv6ll_enabled_harder(), which considers a static fe80:: address in [Address] sufficient to run radv/ndisc even when LinkLocalAddressing= (or IPv6LinkLocalAddressGenerationMode=none, which network_verify() folds into the same flag) disables the kernel-generated link-local. network_adjust_radv()/ndisc()/dhcp() however only check the raw link_local flag and zero router_prefix_delegation / ndisc / dhcp&IPV6 at parse time, so the runtime gate never gets a chance to fire. Factor the static-LL lookup out of link_ipv6ll_enabled_harder() into a Network-level helper and use it in the three network_adjust_*() functions, bringing parse-time and runtime behaviour in line. --- src/network/networkd-dhcp-common.c | 2 ++ src/network/networkd-ipv6ll.c | 28 +++++++++++++++++----------- src/network/networkd-ipv6ll.h | 1 + src/network/networkd-ndisc.c | 4 +++- src/network/networkd-radv.c | 4 +++- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 5d83835580971..d2e51e7e91bc3 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -19,6 +19,7 @@ #include "iovec-util.h" #include "networkd-dhcp-common.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" @@ -100,6 +101,7 @@ void network_adjust_dhcp(Network *network) { } if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network) && FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV6)) { log_warning("%s: DHCPv6 client is enabled but IPv6 link-local addressing is disabled. " "Disabling DHCPv6 client.", network->filename); diff --git a/src/network/networkd-ipv6ll.c b/src/network/networkd-ipv6ll.c index 546229a2e2929..654fbdb6a8ca4 100644 --- a/src/network/networkd-ipv6ll.c +++ b/src/network/networkd-ipv6ll.c @@ -42,6 +42,22 @@ bool link_ipv6ll_enabled(Link *link) { return link->network->link_local & ADDRESS_FAMILY_IPV6; } +bool network_has_static_ipv6ll_address(const Network *network) { + assert(network); + + Address *a; + ORDERED_HASHMAP_FOREACH(a, network->addresses_by_section) { + if (a->family != AF_INET6) + continue; + if (in6_addr_is_set(&a->in_addr_peer.in6)) + continue; + if (in6_addr_is_link_local(&a->in_addr.in6)) + return true; + } + + return false; +} + bool link_ipv6ll_enabled_harder(Link *link) { assert(link); @@ -54,17 +70,7 @@ bool link_ipv6ll_enabled_harder(Link *link) { if (!link->network) return false; - Address *a; - ORDERED_HASHMAP_FOREACH(a, link->network->addresses_by_section) { - if (a->family != AF_INET6) - continue; - if (in6_addr_is_set(&a->in_addr_peer.in6)) - continue; - if (in6_addr_is_link_local(&a->in_addr.in6)) - return true; - } - - return false; + return network_has_static_ipv6ll_address(link->network); } IPv6LinkLocalAddressGenMode link_get_ipv6ll_addrgen_mode(Link *link) { diff --git a/src/network/networkd-ipv6ll.h b/src/network/networkd-ipv6ll.h index 62ce9d957ebd0..73fa51fbef1ce 100644 --- a/src/network/networkd-ipv6ll.h +++ b/src/network/networkd-ipv6ll.h @@ -16,6 +16,7 @@ typedef enum IPv6LinkLocalAddressGenMode { bool link_ipv6ll_enabled(Link *link); bool link_ipv6ll_enabled_harder(Link *link); +bool network_has_static_ipv6ll_address(const Network *network); IPv6LinkLocalAddressGenMode link_get_ipv6ll_addrgen_mode(Link *link); int ipv6ll_addrgen_mode_fill_message(sd_netlink_message *message, IPv6LinkLocalAddressGenMode mode); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index e6b03c0826cad..4e15da958881a 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -17,6 +17,7 @@ #include "networkd-address.h" #include "networkd-address-generation.h" #include "networkd-dhcp6.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-ndisc.h" @@ -91,7 +92,8 @@ bool link_ndisc_enabled(Link *link) { void network_adjust_ndisc(Network *network) { assert(network); - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network)) { if (network->ndisc > 0) log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link-local addressing is disabled or not supported. " "Disabling IPv6AcceptRA=.", network->filename); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index a71e01dfbf474..02322d4dabad5 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -14,6 +14,7 @@ #include "networkd-address.h" #include "networkd-address-generation.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" @@ -815,7 +816,8 @@ void network_adjust_radv(Network *network) { /* For backward compatibility. */ network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network)) { if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " "Disabling IPv6PrefixDelegation=.", network->filename); From 62feba20aaf6848ff76d3be6da743c5638e2471b Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 13 Apr 2026 14:41:11 +0200 Subject: [PATCH 1719/2155] sd-radv: do not stop on transient send errors When the periodic RA timer fires, any error returned by sendmsg() currently propagates up through sd_radv_send() into radv_timeout(), which then calls sd_radv_stop(). The RA engine is never started again until the next carrier transition. On an 802.3ad bond there is a window right after carrier-up where the link is administratively up but no aggregator has been selected yet, so sendmsg() returns ENOBUFS. If the very first RA after a flap lands in that window, radv stops permanently and all clients lose their SLAAC addresses, on-link/PD prefixes, and default router once the previously advertised lifetimes expire, while IPv4 keeps working, leading to a very confusing situation with v4 up and v6 down. Handle this the same way solicited RAs already do (see radv_process_packet()): log the failure and reschedule the timer instead of giving up. ra_sent is left untouched on failure so we stay in the fast initial-advertisement regime until a send actually succeeds. --- src/libsystemd-network/sd-radv.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index b9c59d01643af..21a80da38ee90 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -254,9 +254,13 @@ int sd_radv_send(sd_radv *ra) { r = radv_send_router(ra, NULL); if (r < 0) - return log_radv_errno(ra, r, "Unable to send Router Advertisement: %m"); - - ra->ra_sent++; + /* Do not treat transient send failures (e.g. ENOBUFS while a bond is still selecting an + * aggregator after a carrier flap, or ENETDOWN/EADDRNOTAVAIL during a short link bounce) as + * fatal: log and reschedule so we try again instead of stopping the RA engine for good. + * Solicited RAs already behave this way, see radv_process_packet(). */ + log_radv_errno(ra, r, "Unable to send Router Advertisement, will retry later: %m"); + else + ra->ra_sent++; /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */ if (ra->ra_sent <= RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) @@ -283,8 +287,12 @@ int sd_radv_send(sd_radv *ra) { assert(min_timeout <= max_timeout * 3 / 4); timeout = min_timeout + random_u64_range(max_timeout - min_timeout); - log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.", - FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + if (r >= 0) + log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.", + FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + else + log_radv(ra, "Next Router Advertisement attempt in %s.", + FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); return event_reset_time( ra->event, &ra->timeout_event_source, From b47ed6246d14489bded767a1bcb25dd47bfa771d Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 3 May 2026 22:36:32 +0100 Subject: [PATCH 1720/2155] test: Add a sysupdate test for files which are a prefix match of each other MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tests whether the pattern matching code checks it’s matched the whole string and not just a prefix (see commit 4ffb60319b). In particular, this tests a setup which KDE currently use in their sysupdate images, where two regular file transfers are done, one of a `foo.erofs` file, and the other `foo.erofs.caibx`. As one is a prefix of the other, they were hitting this bug. See: - https://files.kde.org/kde-linux/sysupdate/v2/ - https://github.com/KDE/kde-linux/tree/master/mkosi.extra/usr/lib/sysupdate.d Signed-off-by: Philip Withnall Fixes: https://github.com/systemd/systemd/issues/38605 Fixes: https://github.com/systemd/systemd/issues/41288 --- test/units/TEST-72-SYSUPDATE.sh | 48 ++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 0e9bf78646e46..0e1c9afa837fb 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -56,11 +56,11 @@ at_exit() { trap at_exit EXIT update_checksums() { - (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && sha256sum uki* part* dir-*.tar.gz >SHA256SUMS) + (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && sha256sum uki* part* dir-*.tar.gz linux* >SHA256SUMS) } update_checksums_with_best_before() { - (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && touch "BEST-BEFORE-$1" && sha256sum uki* part* dir-*.tar.gz "BEST-BEFORE-$1" >SHA256SUMS) + (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && touch "BEST-BEFORE-$1" && sha256sum uki* part* dir-*.tar.gz linux* "BEST-BEFORE-$1" >SHA256SUMS) } new_version() { @@ -76,6 +76,10 @@ new_version() { dd if=/dev/urandom of="$WORKDIR/source/part2-$version.raw" bs="$sector_size" count=2048 gzip -k -f "$WORKDIR/source/part2-$version.raw" + # Create a file payload and a suffixed version + echo $RANDOM >"$WORKDIR/source/linux-$version.erofs" + echo $RANDOM >"$WORKDIR/source/linux-$version.erofs.caibx" + # Create a random "UKI" payload echo $RANDOM >"$WORKDIR/source/uki-$version.efi" @@ -99,6 +103,8 @@ new_version() { echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part1-$version.raw" echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw" echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw.gz" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea linux-$version.erofs" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea linux-$version.erofs.caibx" echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-$version.efi" echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-extra-$version.efi" echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea dir-$version.tar.gz" @@ -172,6 +178,10 @@ verify_version() { # Check the extra efi cmp "$WORKDIR/source/uki-extra-$version.efi" "$WORKDIR/xbootldr/EFI/Linux/uki_$version.efi.extra.d/extra.addon.efi" + + # Check the regular file and its suffixed version + cmp "$WORKDIR/source/linux-$version.erofs" "$WORKDIR/system/linux-$version.erofs" + cmp "$WORKDIR/source/linux-$version.erofs.caibx" "$WORKDIR/system/linux-$version.erofs.caibx" } verify_version_current() { @@ -294,6 +304,36 @@ PathRelativeTo=boot MatchPattern=uki_@v.efi.extra.d/extra.addon.efi Mode=0444 InstancesMax=2 +EOF + + # Test with a transfer which contains one of the other transfers as a prefix + # of its files, to check pattern matching can distinguish the two. + cat >"$CONFIGDIR/06-sixth.transfer" <"$CONFIGDIR/07-seventh.transfer" <"$CONFIGDIR/optional.feature" < Date: Thu, 14 May 2026 17:15:06 +0700 Subject: [PATCH 1721/2155] sysupdate: mkdir_parents CurrentSymlink= path This was missing in the CurrentSymlink= creation path, and leads to partially-broken update installs. Signed-off-by: Aleksa Sarai --- src/sysupdate/sysupdate-transfer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 8db1c81962f70..27756298fa284 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -1750,6 +1750,10 @@ int transfer_install_instance( if (r < 0) return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent); + r = mkdir_parents(link_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path); + r = symlink_atomic(relative, link_path); if (r < 0) return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m", From 1006535bb9cce96a0765ab0fa161c428453b94cc Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 14 May 2026 21:06:15 +0100 Subject: [PATCH 1722/2155] ci: switch SUSE mkosi mirror to cdn.o.o The cdn mirror is preferred by SUSE for clouds/CIs. There have been issues with some mirrors, which fail to download from GHA quite often lately, so hopefully this will make it reliable again. --- .github/workflows/mkosi.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 6c734cbdae877..55be6ab86b850 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -226,6 +226,15 @@ jobs: RAM=4G EOF + # Preferred for cloud/CIs + if [ ${{ matrix.distro }} = opensuse ]; then + tee -a mkosi/mkosi.local.conf < Date: Wed, 13 May 2026 14:12:02 +0200 Subject: [PATCH 1723/2155] include: import linux/nsfs.h from kernel v7.0.0~rc1 --- src/include/uapi/linux/nsfs.h | 128 ++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/include/uapi/linux/nsfs.h diff --git a/src/include/uapi/linux/nsfs.h b/src/include/uapi/linux/nsfs.h new file mode 100644 index 0000000000000..a25e38d1c8747 --- /dev/null +++ b/src/include/uapi/linux/nsfs.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NSFS_H +#define __LINUX_NSFS_H + +#include +#include + +#define NSIO 0xb7 + +/* Returns a file descriptor that refers to an owning user namespace */ +#define NS_GET_USERNS _IO(NSIO, 0x1) +/* Returns a file descriptor that refers to a parent namespace */ +#define NS_GET_PARENT _IO(NSIO, 0x2) +/* Returns the type of namespace (CLONE_NEW* value) referred to by + file descriptor */ +#define NS_GET_NSTYPE _IO(NSIO, 0x3) +/* Get owner UID (in the caller's user namespace) for a user namespace */ +#define NS_GET_OWNER_UID _IO(NSIO, 0x4) +/* Translate pid from target pid namespace into the caller's pid namespace. */ +#define NS_GET_PID_FROM_PIDNS _IOR(NSIO, 0x6, int) +/* Return thread-group leader id of pid in the callers pid namespace. */ +#define NS_GET_TGID_FROM_PIDNS _IOR(NSIO, 0x7, int) +/* Translate pid from caller's pid namespace into a target pid namespace. */ +#define NS_GET_PID_IN_PIDNS _IOR(NSIO, 0x8, int) +/* Return thread-group leader id of pid in the target pid namespace. */ +#define NS_GET_TGID_IN_PIDNS _IOR(NSIO, 0x9, int) + +struct mnt_ns_info { + __u32 size; + __u32 nr_mounts; + __u64 mnt_ns_id; +}; + +#define MNT_NS_INFO_SIZE_VER0 16 /* size of first published struct */ + +/* Get information about namespace. */ +#define NS_MNT_GET_INFO _IOR(NSIO, 10, struct mnt_ns_info) +/* Get next namespace. */ +#define NS_MNT_GET_NEXT _IOR(NSIO, 11, struct mnt_ns_info) +/* Get previous namespace. */ +#define NS_MNT_GET_PREV _IOR(NSIO, 12, struct mnt_ns_info) + +/* Retrieve namespace identifiers. */ +#define NS_GET_MNTNS_ID _IOR(NSIO, 5, __u64) +#define NS_GET_ID _IOR(NSIO, 13, __u64) + +enum init_ns_ino { + IPC_NS_INIT_INO = 0xEFFFFFFFU, + UTS_NS_INIT_INO = 0xEFFFFFFEU, + USER_NS_INIT_INO = 0xEFFFFFFDU, + PID_NS_INIT_INO = 0xEFFFFFFCU, + CGROUP_NS_INIT_INO = 0xEFFFFFFBU, + TIME_NS_INIT_INO = 0xEFFFFFFAU, + NET_NS_INIT_INO = 0xEFFFFFF9U, + MNT_NS_INIT_INO = 0xEFFFFFF8U, +#ifdef __KERNEL__ + MNT_NS_ANON_INO = 0xEFFFFFF7U, +#endif +}; + +struct nsfs_file_handle { + __u64 ns_id; + __u32 ns_type; + __u32 ns_inum; +}; + +#define NSFS_FILE_HANDLE_SIZE_VER0 16 /* sizeof first published struct */ +#define NSFS_FILE_HANDLE_SIZE_LATEST sizeof(struct nsfs_file_handle) /* sizeof latest published struct */ + +enum init_ns_id { + IPC_NS_INIT_ID = 1ULL, + UTS_NS_INIT_ID = 2ULL, + USER_NS_INIT_ID = 3ULL, + PID_NS_INIT_ID = 4ULL, + CGROUP_NS_INIT_ID = 5ULL, + TIME_NS_INIT_ID = 6ULL, + NET_NS_INIT_ID = 7ULL, + MNT_NS_INIT_ID = 8ULL, +#ifdef __KERNEL__ + NS_LAST_INIT_ID = MNT_NS_INIT_ID, +#endif +}; + +enum ns_type { + TIME_NS = (1ULL << 7), /* CLONE_NEWTIME */ + MNT_NS = (1ULL << 17), /* CLONE_NEWNS */ + CGROUP_NS = (1ULL << 25), /* CLONE_NEWCGROUP */ + UTS_NS = (1ULL << 26), /* CLONE_NEWUTS */ + IPC_NS = (1ULL << 27), /* CLONE_NEWIPC */ + USER_NS = (1ULL << 28), /* CLONE_NEWUSER */ + PID_NS = (1ULL << 29), /* CLONE_NEWPID */ + NET_NS = (1ULL << 30), /* CLONE_NEWNET */ +}; + +/** + * struct ns_id_req - namespace ID request structure + * @size: size of this structure + * @spare: reserved for future use + * @filter: filter mask + * @ns_id: last namespace id + * @user_ns_id: owning user namespace ID + * + * Structure for passing namespace ID and miscellaneous parameters to + * statns(2) and listns(2). + * + * For statns(2) @param represents the request mask. + * For listns(2) @param represents the last listed mount id (or zero). + */ +struct ns_id_req { + __u32 size; + __u32 spare; + __u64 ns_id; + struct /* listns */ { + __u32 ns_type; + __u32 spare2; + __u64 user_ns_id; + }; +}; + +/* + * Special @user_ns_id value that can be passed to listns() + */ +#define LISTNS_CURRENT_USER 0xffffffffffffffff /* Caller's userns */ + +/* List of all ns_id_req versions. */ +#define NS_ID_REQ_SIZE_VER0 32 /* sizeof first published struct */ + +#endif /* __LINUX_NSFS_H */ From e1c63fd4c0657a7fc7b749c64283ab4dbc6fb39e Mon Sep 17 00:00:00 2001 From: Michal Sekletar Date: Wed, 13 May 2026 16:20:55 +0200 Subject: [PATCH 1724/2155] core: make manager event loop rate limit configurable Co-developed-by: Claude Opus 4.6 --- man/org.freedesktop.systemd1.xml | 18 ++++++++++++++++-- man/systemd-system.conf.xml | 16 ++++++++++++++++ src/core/dbus-manager.c | 2 ++ src/core/main.c | 31 +++++++++++++++++++++++++++++++ src/core/manager-serialize.c | 3 +++ src/core/manager.c | 3 +-- src/core/manager.h | 3 +++ src/core/system.conf.in | 2 ++ src/core/user.conf.in | 4 +++- 9 files changed, 77 insertions(+), 5 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 5474dd788ed6d..7efc899dba251 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -475,6 +475,10 @@ node /org/freedesktop/systemd1 { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u DefaultStartLimitBurst = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly t EventLoopRateLimitIntervalUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u EventLoopRateLimitBurst = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultIOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultIPAccounting = ...; @@ -725,6 +729,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -1183,6 +1191,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -12691,8 +12703,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ DefaultCPUPressureThresholdUSec, DefaultCPUPressureWatch, DefaultIOPressureThresholdUSec, - DefaultIOPressureWatch, and - ReloadCount were added in version 261. + DefaultIOPressureWatch, + ReloadCount, + EventLoopRateLimitIntervalUSec, and + EventLoopRateLimitBurst were added in version 261. Unit Objects diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index fb565b03506c7..e33267409bf2f 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -705,6 +705,22 @@ + + EventLoopRateLimitIntervalSec= + EventLoopRateLimitBurst= + + Configures the rate limiting applied to the manager's main event loop. If the event + loop iterates more than EventLoopRateLimitBurst= times within + EventLoopRateLimitIntervalSec=, event processing is briefly paused to prevent + excessive CPU usage. EventLoopRateLimitIntervalSec= defaults to 1s. + EventLoopRateLimitBurst= defaults to 50000. These settings can also be set on the + kernel command line via + systemd.event_loop_ratelimit_interval_sec= and + systemd.event_loop_ratelimit_burst=. + + + + MinimumUptimeSec= diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5b41e1befe7cc..595136f22c994 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2959,6 +2959,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, defaults.start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, defaults.start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, defaults.start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EventLoopRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Manager, event_loop_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EventLoopRateLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, event_loop_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultIOAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.io_accounting), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultIPAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.ip_accounting), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/main.c b/src/core/main.c index c10df7d87a4fc..2ce73a8624d92 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -165,6 +165,8 @@ 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_event_loop_ratelimit_interval_sec; +static unsigned arg_event_loop_ratelimit_burst; static usec_t arg_minimum_uptime_usec; /* A copy of the original environment block */ @@ -557,6 +559,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } + } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_interval_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_event_loop_ratelimit_interval_sec); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_interval_sec= argument '%s', ignoring: %m", value); + return 0; + } + + } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_burst")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = safe_atou(value, &arg_event_loop_ratelimit_burst); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_burst= argument '%s', ignoring: %m", value); + return 0; + } + } else if (proc_cmdline_key_streq(key, "systemd.minimum_uptime_sec")) { if (proc_cmdline_value_missing(key, value)) @@ -865,6 +889,8 @@ static int parse_config_file(void) { { "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", "EventLoopRateLimitIntervalSec", config_parse_sec, 0, &arg_event_loop_ratelimit_interval_sec }, + { "Manager", "EventLoopRateLimitBurst", config_parse_unsigned, 0, &arg_event_loop_ratelimit_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 @@ -951,6 +977,8 @@ static void set_manager_settings(Manager *m) { * counter on every daemon-reload. */ m->reload_reexec_ratelimit.interval = arg_reload_limit_interval_sec; m->reload_reexec_ratelimit.burst = arg_reload_limit_burst; + m->event_loop_ratelimit.interval = arg_event_loop_ratelimit_interval_sec; + m->event_loop_ratelimit.burst = arg_event_loop_ratelimit_burst; manager_set_watchdog(m, WATCHDOG_RUNTIME, arg_runtime_watchdog); manager_set_watchdog(m, WATCHDOG_REBOOT, arg_reboot_watchdog); @@ -2902,6 +2930,9 @@ static void reset_arguments(void) { arg_reload_limit_interval_sec = 0; arg_reload_limit_burst = 0; + arg_event_loop_ratelimit_interval_sec = 1 * USEC_PER_SEC; + arg_event_loop_ratelimit_burst = 50000; + arg_minimum_uptime_usec = USEC_INFINITY; } diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index d3549d81294ea..6bc41f15f93bf 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -163,6 +163,7 @@ int manager_serialize( (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit); (void) serialize_ratelimit(f, "reload-reexec-ratelimit", &m->reload_reexec_ratelimit); + (void) serialize_ratelimit(f, "event-loop-ratelimit", &m->event_loop_ratelimit); (void) serialize_id128(f, "bus-id", m->bus_id); bus_track_serialize(m->subscribed, f, "subscribed"); @@ -747,6 +748,8 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val); else if ((val = startswith(l, "reload-reexec-ratelimit="))) deserialize_ratelimit(&m->reload_reexec_ratelimit, "reload-reexec-ratelimit", val); + else if ((val = startswith(l, "event-loop-ratelimit="))) + deserialize_ratelimit(&m->event_loop_ratelimit, "event-loop-ratelimit", val); else if ((val = startswith(l, "soft-reboots-count="))) { unsigned n; diff --git a/src/core/manager.c b/src/core/manager.c index 0e33273c3dc24..038690a808a00 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3310,7 +3310,6 @@ static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t use } int manager_loop(Manager *m) { - RateLimit rl = { .interval = 1*USEC_PER_SEC, .burst = 50000 }; int r; assert(m); @@ -3325,7 +3324,7 @@ int manager_loop(Manager *m) { while (m->objective == MANAGER_OK) { - if (!ratelimit_below(&rl)) { + if (!ratelimit_below(&m->event_loop_ratelimit)) { /* Yay, something is going seriously wrong, pause a little */ log_warning("Looping too fast. Throttling execution a little."); sleep(1); diff --git a/src/core/manager.h b/src/core/manager.h index fb65705d321df..d17693369ab5a 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -495,6 +495,9 @@ typedef struct Manager { /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ RateLimit dump_ratelimit; + /* Rate limit for the manager event loop */ + RateLimit event_loop_ratelimit; + sd_event_source *pressure_event_source[_PRESSURE_RESOURCE_MAX]; /* For NFTSet= */ diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 35c7cec6efcbb..aa8208ca1d504 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -88,4 +88,6 @@ #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= #ReloadLimitBurst= +#EventLoopRateLimitIntervalSec=1s +#EventLoopRateLimitBurst=50000 #DefaultMemoryZSwapWriteback=yes diff --git a/src/core/user.conf.in b/src/core/user.conf.in index 33c6733268c08..f9c40458da451 100644 --- a/src/core/user.conf.in +++ b/src/core/user.conf.in @@ -61,4 +61,6 @@ #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= -#ReloadLimitBurst +#ReloadLimitBurst= +#EventLoopRateLimitIntervalSec=1s +#EventLoopRateLimitBurst=50000 From c33a9eb858ef7b69385eb4ead36bf31905b0a7e5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 15 May 2026 15:04:02 +0900 Subject: [PATCH 1725/2155] sd-journal: update comments --- src/libsystemd/sd-journal/sd-journal.c | 2 +- src/libsystemd/sd-journal/test-journal-interleaving.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 52b3e616a172e..01bca7e82a19e 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -3162,7 +3162,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { if (r < 0) return r; - /* Server might have done some vacuuming while we weren't watching. Get rid of the deleted + /* journald might have done some vacuuming while we weren't watching. Get rid of the deleted * files now so they don't stay around indefinitely. */ ORDERED_HASHMAP_FOREACH(f, j->files) { r = journal_file_fstat(f); diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index 5cf65d89ed95d..df5605f1bcbd9 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -566,7 +566,7 @@ static void test_sequence_numbers_one(void) { if (sd_id128_get_machine(NULL) >= 0) { two = journal_file_offline_close(two); - /* restart server */ + /* emulate a system restart */ seqnum = 0; ASSERT_OK(journal_file_open(-EBADF, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, From bbf69974a8fdf57d449668d6b3093501c8526e51 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 15 May 2026 15:33:11 +0900 Subject: [PATCH 1726/2155] TODO: fix typo --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 024e2bcfb0687..3c5ec2884dbca 100644 --- a/TODO.md +++ b/TODO.md @@ -142,7 +142,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later some JSON fragment rather than transfer files, so that we can update it independently from any DDI, and it needs no activation cycle. Why? so that making additional transfers/components/features available can be done without - reloading confext/sysext, and out-band with other configuratoin changes. + reloading confext/sysext, and out-band with other configuration changes. - sysupdate: go through all components, and update them all, one by one. From 5305da009aa9e79ca309b16bffaf3898a3fe57e8 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 14 May 2026 09:41:24 -0700 Subject: [PATCH 1727/2155] shared: add exec_command_status_build_json() and ExecCommandStatus varlink type to common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add exec_command_status_build_json() and exec_command_status_list_build_json() to varlink-common, alongside exec_command_build_json() and exec_command_list_build_json(). The status list function is the runtime counterpart of the command list function — the two arrays are positionally aligned so index N in the status array corresponds to index N in the command array. Commands that have not yet run produce null entries to preserve alignment. Add the ExecCommandStatus varlink struct type to varlink-idl-common next to ExecCommand. It contains PID, timestamps, and mutually exclusive ExitStatus (int, for normal exit) / ExitSignal (string, for signal kill). --- src/core/varlink-common.c | 59 +++++++++++++++++++++++++++++++++ src/core/varlink-common.h | 2 ++ src/shared/varlink-idl-common.c | 15 +++++++++ src/shared/varlink-idl-common.h | 1 + 4 files changed, 77 insertions(+) diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index f5745e12e8e49..bdbecd4551bd6 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -7,6 +7,8 @@ #include "cpu-set-util.h" #include "execute.h" #include "json-util.h" +#include "pidref.h" +#include "process-util.h" #include "rlimit-util.h" #include "varlink-common.h" #include "varlink-unit.h" @@ -159,3 +161,60 @@ int exec_command_list_build_json(sd_json_variant **ret, const char *name, void * *ret = TAKE_PTR(v); return 0; } + +int exec_command_status_build_json(sd_json_variant **ret, const char *name, void *userdata) { + ExecStatus *status = ASSERT_PTR(userdata); + + assert(ret); + + if (!pid_is_valid(status->pid)) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + /* TODO: replace with a real PidRef once ExecStatus carries one */ + SD_JSON_BUILD_PAIR("PID", JSON_BUILD_PIDREF(&PIDREF_MAKE_FROM_PID(status->pid))), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("StartTimestamp", &status->start_timestamp), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ExitTimestamp", &status->exit_timestamp), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("HandoffTimestamp", &status->handoff_timestamp), + SD_JSON_BUILD_PAIR_CONDITION(status->code > 0, "Code", SD_JSON_BUILD_INTEGER(status->code)), + SD_JSON_BUILD_PAIR_CONDITION(status->code > 0, "Status", SD_JSON_BUILD_INTEGER(status->status))); +} + +/* exec_command_status_list_build_json() is the runtime counterpart of exec_command_list_build_json(). + * The two arrays are positionally aligned: index N in the status array corresponds to index N in the + * command array. Commands that have not yet run produce null entries to preserve alignment. */ +int exec_command_status_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + bool any_ran = false; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_status_build_json(&entry, /* name= */ NULL, &c->exec_status); + if (r < 0) + return r; + + if (entry) { + any_ran = true; + r = sd_json_variant_append_array(&v, entry); + } else + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_NULL); + if (r < 0) + return r; + } + + if (!any_ran) { + *ret = NULL; + return 0; + } + + *ret = TAKE_PTR(v); + return 0; +} diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index 8a238c34a5d73..98fe47b48ccaa 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -9,4 +9,6 @@ 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); int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata); +int exec_command_status_build_json(sd_json_variant **ret, const char *name, void *userdata); +int exec_command_status_list_build_json(sd_json_variant **ret, const char *name, void *userdata); int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e); diff --git a/src/shared/varlink-idl-common.c b/src/shared/varlink-idl-common.c index a543c92a3b8a3..d9b148daac66a 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -71,6 +71,21 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Run via shell"), SD_VARLINK_DEFINE_FIELD(viaShell, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +SD_VARLINK_DEFINE_STRUCT_TYPE( + ExecCommandStatus, + SD_VARLINK_FIELD_COMMENT("Process ID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process started"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process exited"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExitTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process was handed off to the executor"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(HandoffTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Termination reason (si_code): CLD_EXITED, CLD_KILLED, CLD_DUMPED"), + SD_VARLINK_DEFINE_FIELD(Code, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Exit code or signal number (si_status), meaning depends on Code"), + SD_VARLINK_DEFINE_FIELD(Status, SD_VARLINK_INT, 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 a42df7118bdc1..5bd351734a9b9 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -9,6 +9,7 @@ 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_ExecCommandStatus; 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 69b5c5db10d078644c30d40ea38ff1c0e48a42ab Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 14 May 2026 09:41:39 -0700 Subject: [PATCH 1728/2155] core: expand ServiceContext and add ServiceRuntime for io.systemd.Unit.List Co-developed-by: Claude Opus 4.6 --- src/core/varlink-service.c | 178 +++++++++++++++++++++- src/core/varlink-service.h | 1 + src/core/varlink-unit.c | 1 + src/shared/varlink-io.systemd.Unit.c | 216 ++++++++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 6 + 5 files changed, 388 insertions(+), 14 deletions(-) diff --git a/src/core/varlink-service.c b/src/core/varlink-service.c index 8d9ed6e91db7d..dde75a30ff802 100644 --- a/src/core/varlink-service.c +++ b/src/core/varlink-service.c @@ -2,21 +2,185 @@ #include "sd-json.h" +#include "exit-status.h" #include "json-util.h" +#include "open-file.h" #include "service.h" +#include "signal-util.h" +#include "strv.h" +#include "user-util.h" #include "varlink-common.h" #include "varlink-service.h" -/* TODO: This covers only a small subset of a service object's properties. Extend to make more available to - * consumers like Unit.StartTransient */ -int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { - Unit *u = ASSERT_PTR(userdata); - Service *s = ASSERT_PTR(SERVICE(u)); +static int exit_status_set_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *statuses = NULL, *signals = NULL; + ExitStatusSet *set = ASSERT_PTR(userdata); + unsigned n; + int r; + + assert(ret); + + if (exit_status_set_is_empty(set)) { + *ret = NULL; + return 0; + } + + BITMAP_FOREACH(n, &set->status) { + assert(n < 256); + + r = sd_json_variant_append_arrayb(&statuses, SD_JSON_BUILD_UNSIGNED(n)); + if (r < 0) + return r; + } + + BITMAP_FOREACH(n, &set->signal) { + const char *str = signal_to_string(n); + if (!str) + continue; + + r = sd_json_variant_append_arrayb(&signals, SD_JSON_BUILD_STRING(str)); + if (r < 0) + return r; + } + + return sd_json_buildo(ret, + JSON_BUILD_PAIR_VARIANT_NON_NULL("statuses", statuses), + JSON_BUILD_PAIR_VARIANT_NON_NULL("signals", signals)); +} + +static int open_files_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + OpenFile *open_files = userdata; + int r; + + assert(ret); + + LIST_FOREACH(open_files, of, open_files) { + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_STRING("path", of->path), + SD_JSON_BUILD_PAIR_STRING("fileDescriptorName", of->fdname), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", of->flags)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int extra_fd_names_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Service *s = ASSERT_PTR(userdata); + int r; + + assert(ret); + + FOREACH_ARRAY(i, s->extra_fds, s->n_extra_fds) { + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(i->fdname)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int refresh_on_reload_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Service *s = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **l = NULL; + int r; + assert(ret); + r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l); + if (r < 0) + return r; + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + return sd_json_variant_new_array_strv(ret, l); +} + +int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Service *s = ASSERT_PTR(SERVICE(userdata)); + return sd_json_buildo( - ret, + ASSERT_PTR(ret), JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), + JSON_BUILD_PAIR_ENUM("ExitType", service_exit_type_to_string(s->exit_type)), + JSON_BUILD_PAIR_ENUM("Restart", service_restart_to_string(s->restart)), + JSON_BUILD_PAIR_ENUM("RestartMode", service_restart_mode_to_string(s->restart_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PIDFile", s->pid_file), + JSON_BUILD_PAIR_FINITE_USEC("RestartUSec", s->restart_usec), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("RestartSteps", s->restart_steps), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RestartMaxDelayUSec", s->restart_max_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStartUSec", s->timeout_start_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStopUSec", s->timeout_stop_usec), + JSON_BUILD_PAIR_ENUM("TimeoutStartFailureMode", service_timeout_failure_mode_to_string(s->timeout_start_failure_mode)), + JSON_BUILD_PAIR_ENUM("TimeoutStopFailureMode", service_timeout_failure_mode_to_string(s->timeout_stop_failure_mode)), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeMaxUSec", s->runtime_max_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("WatchdogUSec", s->watchdog_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit), + SD_JSON_BUILD_PAIR_BOOLEAN("RootDirectoryStartOnly", s->root_directory_start_only), + SD_JSON_BUILD_PAIR_BOOLEAN("GuessMainPID", s->guess_main_pid), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("SuccessExitStatus", exit_status_set_build_json, &s->success_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestartPreventExitStatus", exit_status_set_build_json, &s->restart_prevent_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestartForceExitStatus", exit_status_set_build_json, &s->restart_force_status), + JSON_BUILD_PAIR_STRING_NON_EMPTY("BusName", s->bus_name), + JSON_BUILD_PAIR_ENUM("NotifyAccess", notify_access_to_string(service_get_notify_access(s))), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("FileDescriptorStoreMax", s->n_fd_store_max), + JSON_BUILD_PAIR_ENUM("FileDescriptorStorePreserve", exec_preserve_mode_to_string(s->fd_store_preserve_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("USBFunctionDescriptors", s->usb_function_descriptors), + JSON_BUILD_PAIR_STRING_NON_EMPTY("USBFunctionStrings", s->usb_function_strings), + JSON_BUILD_PAIR_ENUM("OOMPolicy", oom_policy_to_string(s->oom_policy)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("OpenFile", open_files_build_json, s->open_files), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExtraFileDescriptorNames", extra_fd_names_build_json, s), + SD_JSON_BUILD_PAIR_STRING("ReloadSignal", signal_to_string(s->reload_signal)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RefreshOnReload", refresh_on_reload_build_json, s), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecCondition", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_CONDITION]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), - SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit)); + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPre", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReload", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStop", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_STOP]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_STOP_POST])); +} + +int service_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Service *s = ASSERT_PTR(SERVICE(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->main_pid), "MainPID", JSON_BUILD_PIDREF(&s->main_pid)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusText", s->status_text), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("StatusErrno", s->status_errno), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusBusError", s->status_bus_error), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusVarlinkError", s->status_varlink_error), + JSON_BUILD_PAIR_ENUM("Result", service_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("ReloadResult", service_result_to_string(s->reload_result)), + JSON_BUILD_PAIR_ENUM("CleanResult", service_result_to_string(s->clean_result)), + JSON_BUILD_PAIR_ENUM("LiveMountResult", service_result_to_string(s->live_mount_result)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NFileDescriptorStore", s->n_fd_store), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NRestarts", s->n_restarts), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RestartUSecNext", service_restart_usec_next(s)), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutAbortUSec", service_timeout_abort_usec(s)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecMain", exec_command_status_build_json, &s->main_exec_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecConditionStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_CONDITION]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPreStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_STOP]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_STOP_POST]), + 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-service.h b/src/core/varlink-service.h index 07ed1b9d1b0f9..a64ce45803371 100644 --- a/src/core/varlink-service.h +++ b/src/core/varlink-service.h @@ -4,3 +4,4 @@ #include "core-forward.h" int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int service_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 368e39a63edcb..31f7898a8cf04 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -304,6 +304,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void [UNIT_MOUNT] = mount_runtime_build_json, [UNIT_PATH] = path_runtime_build_json, [UNIT_SCOPE] = scope_runtime_build_json, + [UNIT_SERVICE] = service_runtime_build_json, [UNIT_SOCKET] = socket_runtime_build_json, [UNIT_SWAP] = swap_runtime_build_json, [UNIT_TIMER] = timer_runtime_build_json, diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 0758792e1393c..3efa90ed0c256 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1328,9 +1328,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Last time the timer triggered"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(LastTriggerUSec, Timestamp, SD_VARLINK_NULLABLE)); -/* Service-specific types */ - -/* Keep in sync with service_type_table[] in src/core/service.c */ +/* ServiceContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html */ SD_VARLINK_DEFINE_ENUM_TYPE( ServiceType, SD_VARLINK_DEFINE_ENUM_VALUE(simple), @@ -1343,14 +1342,205 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(notify_reload), SD_VARLINK_DEFINE_ENUM_VALUE(idle)); +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceExitType, + SD_VARLINK_DEFINE_ENUM_VALUE(main), + SD_VARLINK_DEFINE_ENUM_VALUE(cgroup)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceRestart, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(on_success), + SD_VARLINK_DEFINE_ENUM_VALUE(on_failure), + SD_VARLINK_DEFINE_ENUM_VALUE(on_abnormal), + SD_VARLINK_DEFINE_ENUM_VALUE(on_watchdog), + SD_VARLINK_DEFINE_ENUM_VALUE(on_abort), + SD_VARLINK_DEFINE_ENUM_VALUE(always)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceRestartMode, + SD_VARLINK_DEFINE_ENUM_VALUE(normal), + SD_VARLINK_DEFINE_ENUM_VALUE(direct), + SD_VARLINK_DEFINE_ENUM_VALUE(debug)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceTimeoutFailureMode, + SD_VARLINK_DEFINE_ENUM_VALUE(terminate), + SD_VARLINK_DEFINE_ENUM_VALUE(abort), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + NotifyAccess, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(all), + SD_VARLINK_DEFINE_ENUM_VALUE(exec), + SD_VARLINK_DEFINE_ENUM_VALUE(main)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(protocol), + 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(watchdog), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(oom_kill), + SD_VARLINK_DEFINE_ENUM_VALUE(exec_condition)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ExitStatusSet, + SD_VARLINK_FIELD_COMMENT("Exit status codes"), + SD_VARLINK_DEFINE_FIELD(statuses, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Signal names"), + SD_VARLINK_DEFINE_FIELD(signals, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + OpenFile, + SD_VARLINK_FIELD_COMMENT("Path to the file to open"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Name for the file descriptor"), + SD_VARLINK_DEFINE_FIELD(fileDescriptorName, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Open file flags"), + SD_VARLINK_DEFINE_FIELD(flags, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( ServiceContext, SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Type="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, ServiceType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExitType="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExitType, ServiceExitType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Restart="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Restart, ServiceRestart, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartMode, ServiceRestartMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#PIDFile="), + SD_VARLINK_DEFINE_FIELD(PIDFile, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartSec="), + SD_VARLINK_DEFINE_FIELD(RestartUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartSteps="), + SD_VARLINK_DEFINE_FIELD(RestartSteps, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartMaxDelaySec="), + SD_VARLINK_DEFINE_FIELD(RestartMaxDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStartSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStartUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStopSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStopUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStartFailureMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TimeoutStartFailureMode, ServiceTimeoutFailureMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStopFailureMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TimeoutStopFailureMode, ServiceTimeoutFailureMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RuntimeMaxSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RuntimeRandomizedExtraSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeRandomizedExtraUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#WatchdogSec="), + SD_VARLINK_DEFINE_FIELD(WatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RemainAfterExit="), + SD_VARLINK_DEFINE_FIELD(RemainAfterExit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RootDirectoryStartOnly="), + SD_VARLINK_DEFINE_FIELD(RootDirectoryStartOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#GuessMainPID="), + SD_VARLINK_DEFINE_FIELD(GuessMainPID, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#SuccessExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(SuccessExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartPreventExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartPreventExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartForceExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartForceExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#BusName="), + SD_VARLINK_DEFINE_FIELD(BusName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#NotifyAccess="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(NotifyAccess, NotifyAccess, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#FileDescriptorStoreMax="), + SD_VARLINK_DEFINE_FIELD(FileDescriptorStoreMax, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#FileDescriptorStorePreserve="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(FileDescriptorStorePreserve, ExecPreserveMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#USBFunctionDescriptors="), + SD_VARLINK_DEFINE_FIELD(USBFunctionDescriptors, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#USBFunctionStrings="), + SD_VARLINK_DEFINE_FIELD(USBFunctionStrings, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#OOMPolicy="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OOMPolicy, OOMPolicy, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#OpenFile="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OpenFile, OpenFile, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExtraFileDescriptorNames="), + SD_VARLINK_DEFINE_FIELD(ExtraFileDescriptorNames, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ReloadSignal="), + SD_VARLINK_DEFINE_FIELD(ReloadSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RefreshOnReload="), + SD_VARLINK_DEFINE_FIELD(RefreshOnReload, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecCondition="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecCondition, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStart="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStart, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RemainAfterExit="), - SD_VARLINK_DEFINE_FIELD(RemainAfterExit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStartPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStartPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecReload="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReload, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecReloadPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStop="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStop, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStopPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceRuntime, + SD_VARLINK_FIELD_COMMENT("Main process PID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MainPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Control process PID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status text set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusText, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status errno set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusErrno, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status D-Bus error set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusBusError, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status Varlink error set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusVarlinkError, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of service operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of reload operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ReloadResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of live mount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LiveMountResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Number of file descriptors in the store"), + SD_VARLINK_DEFINE_FIELD(NFileDescriptorStore, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of restarts"), + SD_VARLINK_DEFINE_FIELD(NRestarts, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Next restart delay"), + SD_VARLINK_DEFINE_FIELD(RestartUSecNext, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timeout for abort"), + SD_VARLINK_DEFINE_FIELD(TimeoutAbortUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Main process execution status"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecMain, ExecCommandStatus, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecCondition commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecConditionStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStartPre commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPreStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStart commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStartPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecReload commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecReloadPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStop commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStopPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + 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)); /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -1709,6 +1899,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The service runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Service, ServiceRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The socket runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Socket, SocketRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"), @@ -1874,6 +2066,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_AutomountResult, &vl_type_AutomountRuntime, &vl_type_ExecCommand, + &vl_type_ExecCommandStatus, &vl_type_MountContext, &vl_type_MountResult, &vl_type_MountRuntime, @@ -1907,6 +2100,17 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_EmergencyAction, /* Shared types (used by both StartTransient and Unit.List) */ + &vl_type_ExitStatusSet, + &vl_type_NotifyAccess, + &vl_type_OpenFile, + SD_VARLINK_SYMBOL_COMMENT("Service-specific context"), + &vl_type_ServiceContext, + &vl_type_ServiceExitType, + &vl_type_ServiceRestart, + &vl_type_ServiceRestartMode, + &vl_type_ServiceResult, + &vl_type_ServiceRuntime, + &vl_type_ServiceTimeoutFailureMode, SD_VARLINK_SYMBOL_COMMENT("Service type"), &vl_type_ServiceType, SD_VARLINK_SYMBOL_COMMENT("Job mode"), @@ -1919,8 +2123,6 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_JobResult, SD_VARLINK_SYMBOL_COMMENT("A job object (defined in io.systemd.Job)"), &vl_type_Job, - SD_VARLINK_SYMBOL_COMMENT("Service-specific context"), - &vl_type_ServiceContext, /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index a5e2e33df22b3..000ed1961105c 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -42,4 +42,10 @@ extern const sd_varlink_symbol vl_type_TimerBase; extern const sd_varlink_symbol vl_type_TimerResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; +extern const sd_varlink_symbol vl_type_NotifyAccess; +extern const sd_varlink_symbol vl_type_ServiceExitType; +extern const sd_varlink_symbol vl_type_ServiceRestart; +extern const sd_varlink_symbol vl_type_ServiceRestartMode; +extern const sd_varlink_symbol vl_type_ServiceResult; +extern const sd_varlink_symbol vl_type_ServiceTimeoutFailureMode; extern const sd_varlink_symbol vl_type_ServiceType; From 3fcb483bca38df416e0f4f49754a85738c7373ae Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 14 May 2026 09:41:50 -0700 Subject: [PATCH 1729/2155] test: add ServiceContext/Runtime enum and integration tests Co-developed-by: Claude Opus 4.6 --- src/test/test-varlink-idl-unit.c | 8 ++++++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 7859f2d735965..51d5b9396f3f5 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -67,6 +67,14 @@ TEST(unit_enums_idl) { /* ServiceContext enums */ TEST_IDL_ENUM(ServiceType, service_type, vl_type_ServiceType); + TEST_IDL_ENUM(ServiceExitType, service_exit_type, vl_type_ServiceExitType); + TEST_IDL_ENUM(ServiceRestart, service_restart, vl_type_ServiceRestart); + TEST_IDL_ENUM(ServiceRestartMode, service_restart_mode, vl_type_ServiceRestartMode); + TEST_IDL_ENUM(ServiceTimeoutFailureMode, service_timeout_failure_mode, vl_type_ServiceTimeoutFailureMode); + + /* ServiceRuntime enums */ + TEST_IDL_ENUM(NotifyAccess, notify_access, vl_type_NotifyAccess); + TEST_IDL_ENUM(ServiceResult, service_result, vl_type_ServiceResult); /* PathContext enums */ TEST_IDL_ENUM(PathType, path_type, vl_type_PathType); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index de56b37c3153e..bbc219decbfad 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -252,6 +252,12 @@ test -n "$path_id" path_params=$(jq -cn --arg name "$path_id" '{name: $name}') varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' +# test for ServiceContext/Runtime +service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$service_id" +service_params=$(jq -cn --arg name "$service_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' # test for ScopeContext/Runtime scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) test -n "$scope_id" From 5597a78df2618e64ccdda4dcd2e7e4942903f2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 10:38:35 +0200 Subject: [PATCH 1730/2155] core: convert PID 1 parse_argv to OPTION macros parse_argv() uses FOREACH_OPTION (not _OR_RETURN) so we can preserve the existing PID 1 tolerance: an unknown option, or one of the sysvinit-style -b/-s/-z catch-alls, returns 0 instead of -EINVAL when getpid_cached() == 1. The docs documented --crash-vt=, but the code implemented "crash-chvt", as introduced in b9e74c399458a1146894ce371e7d85c60658110c. The output from --help is now modified to match code. getopt-defs.h is intentionally left in place since proc_cmdline_filter_pid1_args() in src/basic/proc-cmdline.c still uses its COMMON_/SYSTEMD_/SHUTDOWN_GETOPT_* macros to walk the kernel command line. Previously, opterr=0 was used to suppress error messages about option parsing in PID1. They are now logged at debug level (if debug logging is enabled.) Co-developed-by: Claude Opus 4.7 --- src/core/main.c | 258 ++++++++++++++++++++---------------------------- 1 file changed, 105 insertions(+), 153 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index dc55ea62e91c8..d990017dedffe 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -19,6 +18,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "apparmor-setup.h" #include "architecture.h" #include "argv-util.h" @@ -47,8 +47,10 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" -#include "getopt-defs.h" +#include "glyph-util.h" +#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "id128-util.h" @@ -73,13 +75,13 @@ #include "mkdir-label.h" #include "mount-setup.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "random-util.h" @@ -966,251 +968,227 @@ static void set_manager_settings(Manager *m) { } static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - SYSTEMD_GETOPT_ARGS, - }; - - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SYSTEMD_GETOPT_OPTIONS, - {} - }; - - int c, r; bool user_arg_seen = false; + int r; assert(argc >= 1); assert(argv); - if (getpid_cached() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL)) >= 0) + int log_level_shift = getpid_cached() == 1 ? LOG_DEBUG - LOG_ERR : 0; + OptionParser opts = { argc, argv, .log_level_shift = log_level_shift }; + FOREACH_OPTION(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: arg_action = ACTION_HELP; break; - case ARG_VERSION: + OPTION_COMMON_VERSION: arg_action = ACTION_VERSION; break; - case ARG_TEST: + OPTION_LONG("test", NULL, "Determine initial transaction, dump it and exit"): arg_action = ACTION_TEST; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Combined with --test: operate in system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Combined with --test: operate in user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; user_arg_seen = true; break; - case ARG_DUMP_CONFIGURATION_ITEMS: + OPTION_LONG("dump-configuration-items", NULL, "Dump understood unit configuration items"): arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; break; - case ARG_DUMP_BUS_PROPERTIES: + OPTION_LONG("dump-bus-properties", NULL, "Dump exposed bus properties"): arg_action = ACTION_DUMP_BUS_PROPERTIES; break; - case ARG_BUS_INTROSPECT: - arg_bus_introspect = optarg; + OPTION_LONG("bus-introspect", "PATH", "Write XML introspection data"): + arg_bus_introspect = opts.arg; arg_action = ACTION_BUS_INTROSPECT; break; - case ARG_UNIT: - r = free_and_strdup(&arg_default_unit, optarg); + OPTION_LONG("unit", "UNIT", "Set default unit"): + r = free_and_strdup(&arg_default_unit, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to set default unit \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to set default unit \"%s\": %m", opts.arg); break; - case ARG_DUMP_CORE: - r = parse_boolean_argument("--dump-core", optarg, &arg_dump_core); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "dump-core", "BOOL", "Dump core on crash"): + r = parse_boolean_argument("--dump-core", opts.arg, &arg_dump_core); if (r < 0) return r; break; - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg, &arg_crash_chvt); + OPTION_LONG("crash-chvt", "NR", "Change to specified VT on crash"): + r = parse_crash_chvt(opts.arg, &arg_crash_chvt); if (r < 0) return log_error_errno(r, "Failed to parse crash virtual terminal index: \"%s\": %m", - optarg); + opts.arg); break; - case ARG_CRASH_ACTION: - r = crash_action_from_string(optarg); + OPTION_LONG("crash-action", "ACTION", "Specify what to do on crash"): + r = crash_action_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse crash action \"%s\": %m", optarg); + return log_error_errno(r, "Failed to parse crash action \"%s\": %m", opts.arg); arg_crash_action = r; break; - case ARG_CRASH_SHELL: - r = parse_boolean_argument("--crash-shell", optarg, &arg_crash_shell); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "crash-shell", "BOOL", "Run shell on crash"): + r = parse_boolean_argument("--crash-shell", opts.arg, &arg_crash_shell); if (r < 0) return r; break; - case ARG_CONFIRM_SPAWN: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "confirm-spawn", "BOOL", + "Ask for confirmation when spawning processes"): arg_confirm_spawn = mfree(arg_confirm_spawn); - r = parse_confirm_spawn(optarg, &arg_confirm_spawn); + r = parse_confirm_spawn(opts.arg, &arg_confirm_spawn); if (r < 0) return log_error_errno(r, "Failed to parse confirm spawn option: \"%s\": %m", - optarg); + opts.arg); break; - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "show-status", "BOOL", + "Show status updates on the console during boot"): + if (opts.arg) { + r = parse_show_status(opts.arg, &arg_show_status); if (r < 0) return log_error_errno(r, "Failed to parse show status boolean: \"%s\": %m", - optarg); + opts.arg); } else arg_show_status = SHOW_STATUS_YES; break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_LONG("log-target", "TARGET", + "Set log target (console, journal, kmsg, journal-or-kmsg, null)"): + r = log_set_target_from_string(opts.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", opts.arg); break; - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_LONG("log-level", "LEVEL", + "Set log level (debug, info, notice, warning, err, crit, alert, emerg)"): + r = log_set_max_level_from_string(opts.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", opts.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 log messages"): + if (opts.arg) { + r = log_show_color_from_string(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", - optarg); + opts.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 log messages"): + if (opts.arg) { + r = log_show_location_from_string(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", - optarg); + opts.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 log messages with current time"): + if (opts.arg) { + r = log_show_time_from_string(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", - optarg); + opts.arg); } else log_show_time(true); - break; - case ARG_DEFAULT_STD_OUTPUT: - r = exec_output_from_string(optarg); + OPTION_LONG("default-standard-output", "…", "Set default standard output for services"): + r = exec_output_from_string(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse default standard output setting \"%s\": %m", - optarg); + opts.arg); arg_defaults.std_output = r; break; - case ARG_DEFAULT_STD_ERROR: - r = exec_output_from_string(optarg); + OPTION_LONG("default-standard-error", "…", "Set default standard error output for services"): + r = exec_output_from_string(opts.arg); if (r < 0) return log_error_errno(r, "Failed to parse default standard error output setting \"%s\": %m", - optarg); + opts.arg); arg_defaults.std_error = r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; /* Options not shown in --help. */ - case ARG_CRASH_REBOOT: - r = parse_boolean_argument("--crash-reboot", optarg, NULL); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "crash-reboot", "BOOL", /* help= */ NULL): + r = parse_boolean_argument("--crash-reboot", opts.arg, NULL); if (r < 0) return r; arg_crash_action = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; break; - case ARG_SERVICE_WATCHDOGS: - r = parse_boolean_argument("--service-watchdogs=", optarg, &arg_service_watchdogs); + OPTION_LONG("service-watchdogs", "BOOL", /* help= */ NULL): + r = parse_boolean_argument("--service-watchdogs=", opts.arg, &arg_service_watchdogs); if (r < 0) return r; break; - case ARG_DESERIALIZE: { - int fd; - FILE *f; - - fd = parse_fd(optarg); + OPTION_LONG("deserialize", "FD", /* help= */ NULL): { + int fd = parse_fd(opts.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", opts.arg); (void) fd_cloexec(fd, true); - f = fdopen(fd, "r"); + FILE *f = fdopen(fd, "r"); if (!f) return log_error_errno(errno, "Failed to open serialization fd %d: %m", fd); safe_fclose(arg_serialization); arg_serialization = f; - break; } - case ARG_SWITCHED_ROOT: + OPTION_LONG("switched-root", NULL, /* help= */ NULL): arg_switched_root = true; break; - case ARG_MACHINE_ID: - r = id128_from_string_nonzero(optarg, &arg_machine_id); + OPTION_LONG("machine-id", "ID", /* help= */ NULL): + r = id128_from_string_nonzero(opts.arg, &arg_machine_id); if (r < 0) - return log_error_errno(r, "MachineID '%s' is not valid: %m", optarg); + return log_error_errno(r, "MachineID '%s' is not valid: %m", opts.arg); break; - case 'D': + OPTION_SHORT('D', NULL, /* help= */ NULL): log_set_max_level(LOG_DEBUG); break; - case 'b': - case 's': - case 'z': - /* Just to eat away the sysvinit kernel cmdline args that we'll parse in - * parse_proc_cmdline_item() or ignore, without any getopt() error messages. - */ - case '?': - if (getpid_cached() != 1) - return -EINVAL; - else + /* Just to eat away the sysvinit kernel cmdline args that we'll parse in + * parse_proc_cmdline_item() or ignore. */ + OPTION_SHORT('b', NULL, /* help= */ NULL): {} + OPTION_SHORT('s', NULL, /* help= */ NULL): {} + OPTION_SHORT('z', "ARG", /* help= */ NULL): + OPTION_ERROR: + if (getpid_cached() == 1) return 0; - - default: - assert_not_reached(); + return -EINVAL; } - if (optind < argc && getpid_cached() != 1) + if (option_parser_get_n_args(&opts) > 0 && getpid_cached() != 1) /* Hmm, when we aren't run as init system let's complain about excess arguments */ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Excess arguments."); @@ -1222,50 +1200,24 @@ static int parse_argv(int argc, char *argv[]) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd", "1", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Starts and monitors system and user services."); - printf("%s [OPTIONS...]\n\n" - "%sStarts and monitors system and user services.%s\n\n" - "This program takes no positional arguments.\n\n" - "%sOptions%s:\n" - " -h --help Show this help\n" - " --version Show version\n" - " --test Determine initial transaction, dump it and exit\n" - " --system Combined with --test: operate in system mode\n" - " --user Combined with --test: operate in user mode\n" - " --dump-configuration-items Dump understood unit configuration items\n" - " --dump-bus-properties Dump exposed bus properties\n" - " --bus-introspect=PATH Write XML introspection data\n" - " --unit=UNIT Set default unit\n" - " --dump-core[=BOOL] Dump core on crash\n" - " --crash-vt=NR Change to specified VT on crash\n" - " --crash-action=ACTION Specify what to do on crash\n" - " --crash-shell[=BOOL] Run shell on crash\n" - " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" - " --show-status[=BOOL] Show status updates on the console during boot\n" - " --log-target=TARGET Set log target (console, journal, kmsg,\n" - " journal-or-kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice, warning,\n" - " err, crit, alert, emerg)\n" - " --log-color[=BOOL] Highlight important log messages\n" - " --log-location[=BOOL] Include code location in log messages\n" - " --log-time[=BOOL] Prefix log messages with current time\n" - " --default-standard-output= Set default standard output for services\n" - " --default-standard-error= Set default standard error output for services\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + printf("\nThis program takes no positional arguments.\n"); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd", "1"); return 0; } @@ -2202,8 +2154,8 @@ static int do_reexecute( } /* Try the fallback, if there is any, without any serialization. We pass the original argv[] and - * envp[]. (Well, modulo the ordering changes due to getopt() in argv[], and some cleanups in envp[], - * but let's hope that doesn't matter.) */ + * envp[]. (Well, modulo the ordering changes due to option parsing in argv[], and some cleanups in + * envp[], but let's hope that doesn't matter.) */ arg_serialization = safe_fclose(arg_serialization); fds = fdset_free(fds); From f51393716f2c4aa59b7741bfb5d7d8b1fc5fd601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 18:18:24 +0200 Subject: [PATCH 1731/2155] core: add --crash-vt= for compat with --help Fixes b9e74c399458a1146894ce371e7d85c60658110c. --- src/core/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index d990017dedffe..9fc2ace1c56f2 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1026,10 +1026,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_LONG("crash-chvt", "NR", "Change to specified VT on crash"): + OPTION_LONG("crash-chvt", "NR", "Change to specified VT on crash"): {} + OPTION_LONG("crash-vt", "NR", /* help= */ NULL): /* compat option that was previously documented in --help */ r = parse_crash_chvt(opts.arg, &arg_crash_chvt); if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: \"%s\": %m", + return log_error_errno(r, "Failed to parse crash virtual terminal index '%s': %m", opts.arg); break; From 63fc9f1696a7d2032280f0bfd8729ecf2fe425e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 18:44:29 +0200 Subject: [PATCH 1732/2155] core: tweak handling of unknown options 099663ff8c117303af369a4d412dafed0c5614c2 added "support" for -b/-s/-z ARG with a comment of > /* Just to eat away the sysvinit kernel cmdline args without getopt() > * error messages that we'll parse in parse_proc_cmdline_word() or > * ignore. */ And for PID1 those was valid. But when not running as PID1, those options would be parsed as valid but then help() would immediately return -EINVAL: $ build-old/systemd -b; echo $? 1 At the same time, when running as PID1, if we encounter an error, we shouldn't opine about the rest of the command line. So continuing with the loop and the checks after the loop were iffy. Later, cd57038a30aa9447bde3af7111ac8dc517b38bbf made a big refactoring, and the 'break' (i.e. continuation of the loop) was changed to 'return 0', making things even more confusing, since now we'd just silently stop in the middle of the command line if -b/-s/-z were encountered. So be more careful: when running as PID1, stop parsing on error and return from the function. We didn't parse the full command line, so the later checks are not useful. Silently ignore -b/-s/-z. When not running as PID1, explicitly say that -b/-s/-z are not supported, and propogate the error if parsing fails, e.g. with an unknown option. --- src/core/main.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 9fc2ace1c56f2..3e67186f33b4c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1178,15 +1178,21 @@ static int parse_argv(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); break; - /* Just to eat away the sysvinit kernel cmdline args that we'll parse in + /* Eat the sysvinit kernel cmdline args that we'll parse in * parse_proc_cmdline_item() or ignore. */ OPTION_SHORT('b', NULL, /* help= */ NULL): {} OPTION_SHORT('s', NULL, /* help= */ NULL): {} OPTION_SHORT('z', "ARG", /* help= */ NULL): + if (getpid_cached() != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Switch -%c is only supported when running as PID 1.", opts.opt->short_code); + /* In PID1, do nothing and continue with option parsing. */ + break; OPTION_ERROR: - if (getpid_cached() == 1) - return 0; - return -EINVAL; + if (getpid_cached() != 1) + return -EINVAL; + /* In PID1, silently terminate option parsing. */ + return 0; } if (option_parser_get_n_args(&opts) > 0 && getpid_cached() != 1) From 4bfdb3c62f78a34736f6debeade4a849317f5dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 19:44:22 +0200 Subject: [PATCH 1733/2155] basic: fold getopt-defs.h into proc-cmdline.c It'd be nice if we could not repeat the information about the option list a second time. But I don't see a nice way to do this, since (by design) with the macro approach, the macros must be intertwined with the parse_argv() code. But that code in turn refers to a bunch of variables, so lifting out the function is not immediately possible. So I think it's best to keep the existing approach where we provide a list of options, without additional context, and skip them using a custom routine. --- TODO.md | 3 ++ src/basic/getopt-defs.h | 77 ---------------------------------------- src/basic/proc-cmdline.c | 60 ++++++++++++++++++++++--------- src/core/main.c | 2 ++ src/shutdown/shutdown.c | 4 ++- 5 files changed, 52 insertions(+), 94 deletions(-) delete mode 100644 src/basic/getopt-defs.h diff --git a/TODO.md b/TODO.md index fe13b0b24dff4..d9d9e7dd9fc93 100644 --- a/TODO.md +++ b/TODO.md @@ -3021,6 +3021,9 @@ SPDX-License-Identifier: LGPL-2.1-or-later - whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the reception limit the kernel silently enforces. +- after option+verb introspection is added, add a test to verify that the + list in proc-cmdline.c matches the actual option list in systemd and shutdown. + - 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 diff --git a/src/basic/getopt-defs.h b/src/basic/getopt-defs.h deleted file mode 100644 index 9abef6f156699..0000000000000 --- a/src/basic/getopt-defs.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:" - -#define COMMON_GETOPT_ARGS \ - ARG_LOG_LEVEL = 0x100, \ - ARG_LOG_TARGET, \ - ARG_LOG_COLOR, \ - ARG_LOG_LOCATION, \ - ARG_LOG_TIME - -#define SYSTEMD_GETOPT_ARGS \ - ARG_UNIT, \ - ARG_SYSTEM, \ - ARG_USER, \ - ARG_TEST, \ - ARG_NO_PAGER, \ - ARG_VERSION, \ - ARG_DUMP_CONFIGURATION_ITEMS, \ - ARG_DUMP_BUS_PROPERTIES, \ - ARG_BUS_INTROSPECT, \ - ARG_DUMP_CORE, \ - ARG_CRASH_CHVT, \ - ARG_CRASH_SHELL, \ - ARG_CRASH_REBOOT, \ - ARG_CRASH_ACTION, \ - ARG_CONFIRM_SPAWN, \ - ARG_SHOW_STATUS, \ - ARG_DESERIALIZE, \ - ARG_SWITCHED_ROOT, \ - ARG_DEFAULT_STD_OUTPUT, \ - ARG_DEFAULT_STD_ERROR, \ - ARG_MACHINE_ID, \ - ARG_SERVICE_WATCHDOGS - -#define SHUTDOWN_GETOPT_ARGS \ - ARG_EXIT_CODE, \ - ARG_TIMEOUT - -#define COMMON_GETOPT_OPTIONS \ - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \ - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, \ - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \ - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \ - { "log-time", optional_argument, NULL, ARG_LOG_TIME } - -#define SYSTEMD_GETOPT_OPTIONS \ - { "unit", required_argument, NULL, ARG_UNIT }, \ - { "system", no_argument, NULL, ARG_SYSTEM }, \ - { "user", no_argument, NULL, ARG_USER }, \ - { "test", no_argument, NULL, ARG_TEST }, \ - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, \ - { "help", no_argument, NULL, 'h' }, \ - { "version", no_argument, NULL, ARG_VERSION }, \ - { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \ - { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \ - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \ - { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \ - { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \ - { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \ - { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \ - { "crash-action", required_argument, NULL, ARG_CRASH_ACTION }, \ - { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \ - { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \ - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \ - { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \ - { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \ - { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \ - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \ - { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS } - -#define SHUTDOWN_GETOPT_OPTIONS \ - { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \ - { "timeout", required_argument, NULL, ARG_TIMEOUT } diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index eb61b0bc554e2..4de3480ff0255 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -5,7 +5,6 @@ #include "alloc-util.h" #include "extract-word.h" #include "fileio.h" -#include "getopt-defs.h" #include "initrd-util.h" #include "log.h" #include "parse-util.h" @@ -15,18 +14,50 @@ #include "strv.h" #include "virt.h" +typedef enum ArgType { + no_argument, + required_argument, + optional_argument, +} ArgType; + int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { - enum { - COMMON_GETOPT_ARGS, - SYSTEMD_GETOPT_ARGS, - SHUTDOWN_GETOPT_ARGS, - }; - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SYSTEMD_GETOPT_OPTIONS, - SHUTDOWN_GETOPT_OPTIONS, + const struct { + const char *name; + ArgType has_arg; + } options[] = { + { "log-level", required_argument }, + { "log-target", required_argument }, + { "log-color", optional_argument }, + { "log-location", optional_argument }, + { "log-time", optional_argument }, + { "unit", required_argument }, + { "system", no_argument, }, + { "user", no_argument, }, + { "test", no_argument, }, + { "no-pager", no_argument, }, + { "help", no_argument, }, + { "version", no_argument, }, + { "dump-configuration-items", no_argument, }, + { "dump-bus-properties", no_argument, }, + { "bus-introspect", required_argument }, + { "dump-core", optional_argument }, + { "crash-chvt", required_argument }, + { "crash-vt", required_argument }, + { "crash-shell", optional_argument }, + { "crash-reboot", optional_argument }, + { "crash-action", required_argument }, + { "confirm-spawn", optional_argument }, + { "show-status", optional_argument }, + { "deserialize", required_argument }, + { "switched-root", no_argument, }, + { "default-standard-output", required_argument }, + { "default-standard-error", required_argument }, + { "machine-id", required_argument }, + { "service-watchdogs", required_argument }, + { "exit-code", required_argument }, + { "timeout", required_argument }, }; - static const char *short_options = SYSTEMD_GETOPT_SHORT_OPTIONS; + const char *short_options = "hDbsz:"; _cleanup_strv_free_ char **filtered = NULL; int state, r; @@ -108,12 +139,10 @@ int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { } int proc_cmdline(char **ret) { - const char *e; - assert(ret); /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ - e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + const char *e = secure_getenv("SYSTEMD_PROC_CMDLINE"); if (e) return strdup_to(ret, e); @@ -124,13 +153,12 @@ int proc_cmdline(char **ret) { } static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) { - const char *e; int r; assert(ret); /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ - e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + const char *e = secure_getenv("SYSTEMD_PROC_CMDLINE"); if (e) return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); diff --git a/src/core/main.c b/src/core/main.c index 3e67186f33b4c..ce72d4431f906 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -977,6 +977,8 @@ static int parse_argv(int argc, char *argv[]) { int log_level_shift = getpid_cached() == 1 ? LOG_DEBUG - LOG_ERR : 0; OptionParser opts = { argc, argv, .log_level_shift = log_level_shift }; + /* Note: when new options are added here, also add them to the exclusion list in proc-cmdline.c! */ + FOREACH_OPTION(c, &opts) switch (c) { diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 131550c46b20d..c3c114a6674c4 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -61,7 +61,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); /* The interface is: the verb must stay in argv[1]. Any extra positional arguments - * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. */ + * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. + * + * Note: when new options are added here, also add them to the exclusion list in proc-cmdline.c! */ OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; int r; From 601684ba950424888bde568153addabade8d4627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 10:12:00 +0200 Subject: [PATCH 1734/2155] basic: cache the result of invoked_by_systemd() This function is used in a quite a few places and it calls getenv and does a bit of parsing, so let's try to speed this up. Also, mark it _const_. With the caching, it now always returns the same result, so we can tell the compiler to eliminate subsequent invocations. --- src/basic/argv-util.c | 13 +++++++++---- src/basic/argv-util.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/basic/argv-util.c b/src/basic/argv-util.c index c9d610b3a037d..0ff2fd9bcbaaf 100644 --- a/src/basic/argv-util.c +++ b/src/basic/argv-util.c @@ -39,27 +39,32 @@ bool invoked_as(char *argv[], const char *token) { } bool invoked_by_systemd(void) { + static int cached = -1; int r; + if (cached >= 0) + return cached; + /* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd, * or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */ const char *e = getenv("SYSTEMD_EXEC_PID"); if (!e) - return false; + return (cached = false); if (streq(e, "*")) /* For testing. */ - return true; + return (cached = true); pid_t p; r = parse_pid(e, &p); if (r < 0) { /* We know that systemd sets the variable correctly. Something else must have set it. */ log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e); - return false; + return (cached = false); } - return getpid_cached() == p; + cached = getpid_cached() == p; + return cached; } bool argv_looks_like_help(int argc, char **argv) { diff --git a/src/basic/argv-util.h b/src/basic/argv-util.h index 650b00353dc08..02901ebc49f4b 100644 --- a/src/basic/argv-util.h +++ b/src/basic/argv-util.h @@ -9,7 +9,7 @@ extern char **saved_argv; void save_argc_argv(int argc, char **argv); bool invoked_as(char *argv[], const char *token); -bool invoked_by_systemd(void); +bool invoked_by_systemd(void) _const_; bool argv_looks_like_help(int argc, char **argv); int rename_process_full(const char *comm, const char *invocation); From 09f107c811a591efa90bd2cfb4982f82c955a349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 10:18:28 +0200 Subject: [PATCH 1735/2155] manager: skip reopening of console and signal reset when running as normal program We want to reopen the console used for logging when running as PID1, but also when running a user manager (c.f. 48a601fe5de8aa0d89ba6dadde168769fa7ce992 and 2a646b1d624e510a79785e1268b55a9c3a441db5). But this can cause problems when the binary is invoked directly, e.g. to print --help. E.g. if we ignore SIGPIPE, we'd remain running briefly after '/usr/lib/systemd/systemd --help | head -n1'. Previusly, the getopt machinery would print to stderr unconditionally. But after the rework of option parsing, which means that we use the log_* functions to repor errors, the test that checks if we print errors to stderr started failing. So let's skip some more of the setup if !invoked_by_systemd(). --- src/core/main.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index ce72d4431f906..54f510614cf63 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -3273,15 +3273,19 @@ int main(int argc, char *argv[]) { /* The efivarfs is now mounted, let's lock down the system token. */ lock_down_efi_variables(); + } else { /* Running as user instance */ arg_runtime_scope = RUNTIME_SCOPE_USER; - log_set_always_reopen_console(true); - log_set_target_and_open(LOG_TARGET_AUTO); /* clear the kernel timestamp, because we are not PID 1 */ kernel_timestamp = DUAL_TIMESTAMP_NULL; + if (invoked_by_systemd()) { + log_set_always_reopen_console(true); + log_set_target_and_open(LOG_TARGET_AUTO); + } + r = mac_init(); if (r < 0) { error_message = "Failed to initialize MAC support"; @@ -3293,9 +3297,11 @@ int main(int argc, char *argv[]) { * transitioning from the initrd to the main systemd or suchlike. */ save_rlimits(&saved_rlimit_nofile, &saved_rlimit_memlock); - /* Reset all signal handlers. */ + /* Reset all signal handlers to clean up after reexec. */ (void) reset_all_signal_handlers(); - (void) ignore_signals(SIGNALS_IGNORE); + + if (getpid_cached() == 1 || invoked_by_systemd()) + (void) ignore_signals(SIGNALS_IGNORE); (void) parse_configuration(&saved_rlimit_nofile, &saved_rlimit_memlock); From 1fdcf3ee40946ddb0ab0f60f56390faca9d11eda Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 23 Feb 2026 04:32:05 +0900 Subject: [PATCH 1736/2155] sd-device: drop support of udev database version 0 The udev database versioning has been introduced in v247, which is released on 2020-11-26. Let's drop the support of old udev database. --- src/libsystemd/sd-device/device-internal.h | 3 ++- src/libsystemd/sd-device/device-private.c | 2 +- src/libsystemd/sd-device/sd-device.c | 22 +--------------------- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h index 16a5792350283..f207a1618f7dc 100644 --- a/src/libsystemd/sd-device/device-internal.h +++ b/src/libsystemd/sd-device/device-internal.h @@ -6,6 +6,7 @@ #include "sd-forward.h" #include "iterator.h" +#define OLDEST_UDEV_DATABASE_VERSION 1 #define LATEST_UDEV_DATABASE_VERSION 1 struct sd_device { @@ -51,7 +52,7 @@ struct sd_device { /* The database version indicates the supported features by the udev database. * This is saved and parsed in V field. * - * 0: None of the following features are supported (systemd version <= 246). + * 0: None of the following features are supported (systemd version <= 246), unsupported since v261. * 1: The current tags (Q) and the database version (V) features are implemented (>= 247). */ unsigned database_version; diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c index 054c3545d006d..48dce7768949e 100644 --- a/src/libsystemd/sd-device/device-private.c +++ b/src/libsystemd/sd-device/device-private.c @@ -912,7 +912,7 @@ int device_update_db(sd_device *device) { fprintf(f, "Q:%s\n", ct); /* Current tag */ /* Always write the latest database version here, instead of the value stored in - * device->database_version, as which may be 0. */ + * device->database_version. */ fputs("V:" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\n", f); } diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index ef67c649d1ebd..ae44d0c86fd96 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -47,6 +47,7 @@ int device_new_aux(sd_device **ret) { .devuid = UID_INVALID, .devgid = GID_INVALID, .action = _SD_DEVICE_ACTION_INVALID, + .database_version = OLDEST_UDEV_DATABASE_VERSION, }; *ret = device; @@ -1966,26 +1967,11 @@ _public_ const char* sd_device_get_tag_next(sd_device *device) { return v; } -static bool device_database_supports_current_tags(sd_device *device) { - assert(device); - - (void) device_read_db(device); - - /* The current tags (saved in Q field) feature is implemented in database version 1. - * If the database version is 0, then the tags (NOT current tags, saved in G field) are not - * sticky. Thus, we can safely bypass the operations for the current tags (Q) to tags (G). */ - - return device->database_version >= 1; -} - _public_ const char* sd_device_get_current_tag_first(sd_device *device) { void *v; assert_return(device, NULL); - if (!device_database_supports_current_tags(device)) - return sd_device_get_tag_first(device); - (void) device_read_db(device); device->current_tags_iterator_generation = device->tags_generation; @@ -2000,9 +1986,6 @@ _public_ const char* sd_device_get_current_tag_next(sd_device *device) { assert_return(device, NULL); - if (!device_database_supports_current_tags(device)) - return sd_device_get_tag_next(device); - (void) device_read_db(device); if (device->current_tags_iterator_generation != device->tags_generation) @@ -2273,9 +2256,6 @@ _public_ int sd_device_has_current_tag(sd_device *device, const char *tag) { assert_return(device, -EINVAL); assert_return(tag, -EINVAL); - if (!device_database_supports_current_tags(device)) - return sd_device_has_tag(device, tag); - (void) device_read_db(device); return set_contains(device->current_tags, tag); From dfe0e81de68c13219f1c8a7ac9390c21204fb496 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 23 Feb 2026 01:28:41 +0900 Subject: [PATCH 1737/2155] udev/watch: use mapping from device ID -> watch on restart The mapping from device ID to watch handle has been introduced by e7f781e473f5119bf9246208a6de9f6b76a39c5d (v249, released on 2021-07-07). Let's drop the runtime upgradability of udevd from an ancient version. --- src/udev/udev-watch.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 08bc3492bb0b0..0636c325e6bc6 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -337,21 +337,20 @@ static int udev_watch_restore(Manager *manager) { return log_warning_errno(errno, "Failed to open old watches directory '%s': %m", old); FOREACH_DIRENT(de, dir, break) { - - /* For backward compatibility, read symlink from watch handle to device ID. This is necessary - * when udevd is restarted after upgrading from v248 or older. The new format (ID -> wd) was - * introduced by e7f781e473f5119bf9246208a6de9f6b76a39c5d (v249). */ - - int wd; - if (safe_atoi(de->d_name, &wd) < 0) - continue; /* This should be ID -> wd symlink. Skipping. */ + if (in_charset(de->d_name, DIGITS)) + continue; /* This should be wd -> ID symlink. Skipping. */ _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - r = device_new_from_watch_handle_at(&dev, dirfd(dir), wd); + r = sd_device_new_from_device_id(&dev, de->d_name); if (r < 0) { + _cleanup_free_ char *wd_str = NULL; + + if (ERRNO_IS_NEG_DEVICE_ABSENT(r) || DEBUG_LOGGING) + (void) readlinkat_malloc(dirfd(dir), de->d_name, &wd_str); + log_full_errno(ERRNO_IS_NEG_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to create sd_device object from saved watch handle '%i', ignoring: %m", - wd); + "Failed to create sd_device object from device ID '%s' for watch handle '%s', ignoring: %m", + de->d_name, strna(wd_str)); continue; } From 3b6402446412ace7d99723398b0adac59230c881 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 23 Feb 2026 01:51:26 +0900 Subject: [PATCH 1738/2155] udev/node: drop support of old file format in /run/udev/links/ The new file format in /run/udev/links/ has been introduced in 377a83f0d80376456d9be203796f66f543a8b943 (v250, released on 2021-12-23). Let's drop the old format support, to simplify the logic. --- src/udev/udev-node.c | 80 ++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 58 deletions(-) diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index d30ffd9a56e42..4d1ecf5d116a1 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -79,84 +79,48 @@ static int node_create_symlink(sd_device *dev, const char *devnode, const char * } static int stack_directory_read_one(int dirfd, const char *id, char **devnode, int *priority) { - _cleanup_free_ char *buf = NULL; - int tmp_prio, r; + int r; assert(dirfd >= 0); assert(id); assert(priority); - /* This reads priority and device node from the symlink under /run/udev/links (or udev database). + /* This reads priority and device node from the symlink under /run/udev/links/ directory. * If 'devnode' is NULL, obtained priority is always set to '*priority'. If 'devnode' is non-NULL, - * this updates '*devnode' and '*priority'. */ + * this updates '*devnode' and '*priority' if the obtained one has a higher priority. */ - /* First, let's try to read the entry with the new format, which should replace the old format pretty - * quickly. */ + _cleanup_free_ char *buf = NULL; r = readlinkat_malloc(dirfd, id, &buf); - if (r >= 0) { - char *colon; - - /* With the new format, the devnode and priority can be obtained from symlink itself. */ - - colon = strchr(buf, ':'); - if (!colon || colon == buf) - return -EINVAL; + if (r < 0) + return r == -ENOENT ? -ENODEV : r; - *colon = '\0'; + char *colon = strchr(buf, ':'); + if (!colon || colon == buf) + return -EINVAL; - /* Of course, this check is racy, but it is not necessary to be perfect. Even if the device - * node will be removed after this check, we will receive 'remove' uevent, and the invalid - * symlink will be removed during processing the event. The check is just for shortening the - * timespan that the symlink points to a non-existing device node. */ - if (access(colon + 1, F_OK) < 0) - return -ENODEV; + *colon = '\0'; - r = safe_atoi(buf, &tmp_prio); - if (r < 0) - return r; + /* Of course, this check is racy, but it is not necessary to be perfect. Even if the device + * node will be removed after this check, we will receive 'remove' uevent, and the invalid + * symlink will be removed during processing the event. The check is just for shortening the + * timespan that the symlink points to a non-existing device node. */ + if (access(colon + 1, F_OK) < 0) + return -ENODEV; - if (!devnode) - goto finalize; + int tmp_prio; + r = safe_atoi(buf, &tmp_prio); + if (r < 0) + return r; + if (devnode) { if (*devnode && tmp_prio <= *priority) return 0; /* Unchanged */ r = free_and_strdup(devnode, colon + 1); if (r < 0) return r; + } - } else if (r == -EINVAL) { /* Not a symlink ? try the old format */ - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - const char *val; - - /* Old format. The devnode and priority must be obtained from uevent and udev database. */ - - r = sd_device_new_from_device_id(&dev, id); - if (r < 0) - return r; - - r = device_get_devlink_priority(dev, &tmp_prio); - if (r < 0) - return r; - - if (!devnode) - goto finalize; - - if (*devnode && tmp_prio <= *priority) - return 0; /* Unchanged */ - - r = sd_device_get_devname(dev, &val); - if (r < 0) - return r; - - r = free_and_strdup(devnode, val); - if (r < 0) - return r; - - } else - return r == -ENOENT ? -ENODEV : r; - -finalize: *priority = tmp_prio; return 1; /* Updated */ } From c5997b2bbfd1efee997a7fe297c060d26b6b9757 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 25 Mar 2026 05:25:04 +0900 Subject: [PATCH 1739/2155] dhcp-message: allow to serialize/deserialize sd_dhcp_message object By using this, we can expose received DHCP message in Describe() DBus/Varlink methods. Preparation for later change. --- src/libsystemd-network/dhcp-message.c | 186 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 3 + src/libsystemd-network/test-dhcp-message.c | 13 ++ src/libsystemd-network/tlv-util.c | 69 ++++++++ src/libsystemd-network/tlv-util.h | 3 + 5 files changed, 274 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index e0ef4f06fb481..07664588301de 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -17,6 +17,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "json-util.h" #include "network-common.h" #include "set.h" #include "sort-util.h" @@ -1459,3 +1460,188 @@ int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret) { *ret = TAKE_STRUCT(iovw); return 0; } + +int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret) { + int r; + + assert(message); + assert(message->header.hlen <= sizeof(message->header.chaddr)); + assert(ret); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_UNSIGNED("op", message->header.op), + SD_JSON_BUILD_PAIR_UNSIGNED("htype", message->header.htype), + SD_JSON_BUILD_PAIR_UNSIGNED("hops", message->header.hops), + SD_JSON_BUILD_PAIR_UNSIGNED("xid", be32toh(message->header.xid)), + SD_JSON_BUILD_PAIR_UNSIGNED("secs", be16toh(message->header.secs)), + SD_JSON_BUILD_PAIR_UNSIGNED("flags", be16toh(message->header.flags)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("ciaddr", &message->header.ciaddr, sizeof(message->header.ciaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("yiaddr", &message->header.yiaddr, sizeof(message->header.yiaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("siaddr", &message->header.siaddr, sizeof(message->header.siaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("giaddr", &message->header.giaddr, sizeof(message->header.giaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("chaddr", message->header.chaddr, message->header.hlen)); + if (r < 0) + return r; + + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME) && !eqzero(message->header.sname)) { + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_HEX_NON_EMPTY("sname", message->header.sname, sizeof(message->header.sname))); + if (r < 0) + return r; + } + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE) && !eqzero(message->header.file)) { + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_HEX_NON_EMPTY("file", message->header.file, sizeof(message->header.file))); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; + r = tlv_build_json(&message->options, &w); + if (r < 0) + return r; + + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("options", w)); + if (r < 0) + return r; + + *ret = TAKE_PTR(v); + return 0; +} + +typedef struct MessageParam { + uint8_t op; + uint8_t htype; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + struct iovec ciaddr; + struct iovec yiaddr; + struct iovec siaddr; + struct iovec giaddr; + struct iovec chaddr; + struct iovec sname; + struct iovec file; + TLV *options; +} MessageParam; + +static void message_param_done(MessageParam *p) { + assert(p); + + iovec_done(&p->ciaddr); + iovec_done(&p->yiaddr); + iovec_done(&p->siaddr); + iovec_done(&p->giaddr); + iovec_done(&p->chaddr); + iovec_done(&p->sname); + iovec_done(&p->file); + tlv_unref(p->options); +} + +static int dispatch_options(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + TLV **options = ASSERT_PTR(userdata); + int r; + + if (*options) + return -EINVAL; /* multiple options field? */ + + _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(TLV_DHCP4); + if (!tlv) + return -ENOMEM; + + r = tlv_parse_json(tlv, v); + if (r < 0) + return r; + + *options = TAKE_PTR(tlv); + return 0; +} + +int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret) { + static const sd_json_dispatch_field dispatch_table[] = { + { "op", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, op), SD_JSON_MANDATORY }, + { "htype", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, htype), 0 }, + { "hops", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, hops), 0 }, + { "xid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(MessageParam, xid), 0 }, + { "secs", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(MessageParam, secs), 0 }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(MessageParam, flags), 0 }, + { "ciaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, ciaddr), 0 }, + { "yiaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, yiaddr), 0 }, + { "siaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, siaddr), 0 }, + { "giaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, giaddr), 0 }, + { "chaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, chaddr), 0 }, + { "sname", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, sname), 0 }, + { "file", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, file), 0 }, + { "options", SD_JSON_VARIANT_ARRAY, dispatch_options, offsetof(MessageParam, options), 0 }, + {}, + }; + + int r; + + assert(v); + assert(ret); + + _cleanup_(message_param_done) MessageParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (!IN_SET(p.op, BOOTREQUEST, BOOTREPLY)) + return -EINVAL; + if (!iovec_is_valid(&p.ciaddr) || !IN_SET(p.ciaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.ciaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.yiaddr) || !IN_SET(p.yiaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.yiaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.siaddr) || !IN_SET(p.siaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.siaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.giaddr) || !IN_SET(p.giaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.giaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.chaddr) || p.chaddr.iov_len > sizeof_field(sd_dhcp_message, header.chaddr)) + return -EINVAL; + if (!iovec_is_valid(&p.sname) || p.sname.iov_len > sizeof_field(sd_dhcp_message, header.sname)) + return -EINVAL; + if (!iovec_is_valid(&p.file) || p.file.iov_len > sizeof_field(sd_dhcp_message, header.file)) + return -EINVAL; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + message->header = (DHCPMessageHeader) { + .op = p.op, + .htype = p.htype, + .hlen = p.chaddr.iov_len, + .hops = p.hops, + .xid = htobe32(p.xid), + .secs = htobe16(p.secs), + .flags = htobe16(p.flags), + .magic = htobe32(DHCP_MAGIC_COOKIE), + }; + + memcpy_safe(&message->header.ciaddr, p.ciaddr.iov_base, p.ciaddr.iov_len); + memcpy_safe(&message->header.yiaddr, p.yiaddr.iov_base, p.yiaddr.iov_len); + memcpy_safe(&message->header.siaddr, p.siaddr.iov_base, p.siaddr.iov_len); + memcpy_safe(&message->header.giaddr, p.giaddr.iov_base, p.giaddr.iov_len); + memcpy_safe(message->header.chaddr, p.chaddr.iov_base, p.chaddr.iov_len); + memcpy_safe(message->header.sname, p.sname.iov_base, p.sname.iov_len); + memcpy_safe(message->header.file, p.file.iov_base, p.file.iov_len); + if (p.options) { + message->options = TAKE_STRUCT(*p.options); + p.options = mfree(p.options); + } + + *ret = TAKE_PTR(message); + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e003d9cc5bf20..41b22d2488092 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -90,3 +90,6 @@ int dhcp_message_parse( sd_dhcp_message **ret); int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); + +int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret); +int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 7f5d6669a9495..5194cffcc24ec 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -2,6 +2,8 @@ #include +#include "sd-json.h" + #include "alloc-util.h" #include "dhcp-client-id-internal.h" #include "dhcp-message.h" @@ -439,6 +441,17 @@ TEST(dhcp_message) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; ASSERT_OK(dhcp_message_build(m2, &iovw2)); ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + /* json */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ASSERT_OK(dhcp_message_build_json(m, &v)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m3 = NULL; + ASSERT_OK(dhcp_message_parse_json(v, &m3)); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw3 = {}; + ASSERT_OK(dhcp_message_build(m3, &iovw3)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw3)); } static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { diff --git a/src/libsystemd-network/tlv-util.c b/src/libsystemd-network/tlv-util.c index 68574e718c741..488b6c5bd1e42 100644 --- a/src/libsystemd-network/tlv-util.c +++ b/src/libsystemd-network/tlv-util.c @@ -4,6 +4,7 @@ #include "hashmap.h" #include "iovec-util.h" #include "iovec-wrapper.h" +#include "json-util.h" #include "tlv-util.h" #include "unaligned.h" @@ -503,3 +504,71 @@ int tlv_build(const TLV *tlv, struct iovec *ret) { *ret = IOVEC_MAKE(TAKE_PTR(buf), sz); return 0; } + +int tlv_build_json(const TLV *tlv, sd_json_variant **ret) { + int r; + + assert(tlv); + assert(ret); + + /* Sort by tags, for reproducibility. */ + _cleanup_free_ void **sorted = NULL; + size_t n; + r = hashmap_dump_keys_sorted(tlv->entries, &sorted, &n); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + FOREACH_ARRAY(tagp, sorted, n) { + uint32_t tag = PTR_TO_UINT32(*tagp); + struct iovec_wrapper *iovw = ASSERT_PTR(tlv_get_all(tlv, tag)); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_UNSIGNED("tag", tag), + JSON_BUILD_PAIR_IOVEC_HEX("data", iov)); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(v); + return 0; +} + +typedef struct TLVParam { + uint32_t tag; + struct iovec data; +} TLVParam; + +static void tlv_param_done(TLVParam *p) { + iovec_done(&p->data); +} + +int tlv_parse_json(TLV *tlv, sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "tag", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(TLVParam, tag), SD_JSON_MANDATORY }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(TLVParam, data), SD_JSON_MANDATORY }, + {}, + }; + + int r; + + assert(tlv); + assert(v); + + sd_json_variant *e; + JSON_VARIANT_ARRAY_FOREACH(e, v) { + _cleanup_(tlv_param_done) TLVParam p = {}; + r = sd_json_dispatch(e, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + r = tlv_append(tlv, p.tag, p.data.iov_len, p.data.iov_base); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/libsystemd-network/tlv-util.h b/src/libsystemd-network/tlv-util.h index 5344c28703244..1bdcadccb805a 100644 --- a/src/libsystemd-network/tlv-util.h +++ b/src/libsystemd-network/tlv-util.h @@ -80,3 +80,6 @@ int tlv_append_tlv(TLV *tlv, const TLV *source); int tlv_parse(TLV *tlv, const struct iovec *iov); size_t tlv_size(const TLV *tlv); int tlv_build(const TLV *tlv, struct iovec *ret); + +int tlv_build_json(const TLV *tlv, sd_json_variant **ret); +int tlv_parse_json(TLV *tlv, sd_json_variant *v); From 5608575e01613eea7ce85c15c5406c88c0d4d1e0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 1 May 2026 14:19:33 +0100 Subject: [PATCH 1740/2155] core: propagate FDs from store from user to system manager In order to allow FD Stores of user units to survive a user session restart, propagate FDs received via the protocol up one level from user to system manager via sd_notify. And the other way around, propagate them down via LISTEN_FDS tagging them with the unit name so that the child manager can inject them in the appropriate unit. Ensure units that are dead or not loaded can get FDs added to their stores, and that they are correctly propagated once the unit is started or loaded. When the unit is not loaded we don't know what the FD max limit is, so simply increase it for each FD injected, and then when the unit is realised prune it down to match the unit's now available config in case the limit is lower than the number of FDs in the store. Each FD sent up or down is assigned a monotonic index, and the manager also sends a JSON map that associates the index with the original unit and FDNAME: { "unit-name.service": [ { "name": "fdname1", "index": 1 }, { "name": "fdname2", "index": 2 } ], ... } This allows the manager to assign back the FDs to the appropriate unit using the appropriate name, given the FDNAMEs are not unique. --- docs/FILE_DESCRIPTOR_STORE.md | 21 +++ man/systemd.service.xml | 11 ++ src/analyze/analyze-condition.c | 2 +- src/analyze/analyze-security.c | 2 +- src/analyze/analyze-verify-util.c | 2 +- src/core/main.c | 260 ++++++++++++++++++++++++- src/core/manager-serialize.c | 4 + src/core/manager.c | 99 +++++++++- src/core/manager.h | 21 ++- src/core/service.c | 272 +++++++++++++++++++++++++-- src/core/service.h | 22 +++ src/core/unit.h | 7 + src/shared/daemon-util.c | 22 +++ src/shared/daemon-util.h | 2 + src/test/test-bpf-firewall.c | 2 +- src/test/test-bpf-foreign-programs.c | 2 +- src/test/test-bpf-restrict-fs.c | 2 +- src/test/test-cgroup-mask.c | 2 +- src/test/test-engine.c | 2 +- src/test/test-execute.c | 2 +- src/test/test-load-fragment.c | 12 +- src/test/test-path.c | 2 +- src/test/test-sched-prio.c | 2 +- src/test/test-socket-bind.c | 2 +- src/test/test-watch-pid.c | 2 +- 25 files changed, 741 insertions(+), 38 deletions(-) diff --git a/docs/FILE_DESCRIPTOR_STORE.md b/docs/FILE_DESCRIPTOR_STORE.md index 231af87c912d4..8fa2ae0127c96 100644 --- a/docs/FILE_DESCRIPTOR_STORE.md +++ b/docs/FILE_DESCRIPTOR_STORE.md @@ -198,6 +198,27 @@ The soft reboot cycle transition and the initrd→host transition are semantically very similar, hence similar rules apply, and in both cases it is recommended to use the fdstore if pinned resources shall be passed over. +## Propagation Across Manager Boundaries + +When a service that has `FileDescriptorStorePreserve=yes` set is itself running +under another service manager, for example a service of the per-user manager +(`user@.service`), or a payload running inside a +[`systemd-nspawn`](https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html) +container, fds pushed into its fdstore are automatically forwarded one level up +the supervisor chain via the enveloping manager's `$NOTIFY_SOCKET`. This allows +the fdstore contents of inner services to be preserved across restarts, re-execs, +soft-reboots, etc. of the *outer* manager, even when the inner manager (or the +container payload) is itself restarted along the way. On the way up, each fd is +tagged with its originating unit id and the original `FDNAME=…` value, so that +when the fds are eventually handed back down (via the regular +`$LISTEN_FDS`/`$LISTEN_FDNAMES` protocol), each manager along the chain can +route them back to the correct unit's fdstore. `FDSTOREREMOVE=1` notifications +are forwarded the same way, so that explicit removals propagate all the way up too. + +For this to work the enveloping unit must itself enable the fdstore (i.e. set +`FileDescriptorStoreMax=` to a sufficiently large value and +`FileDescriptorStorePreserve=yes`). + ## Debugging The diff --git a/man/systemd.service.xml b/man/systemd.service.xml index d4a1978523011..b25f1a90aabe0 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1255,6 +1255,17 @@ RestartMaxDelaySec=160s is removed, the service manager exits, or the file descriptors get EPOLLHUP or EPOLLERR. + When set to yes, and the service is itself running under another service + manager (e.g. a service of user@.service, or a payload inside + systemd-nspawn1), + file descriptors pushed into the store are also forwarded one level up via the enveloping manager's + $NOTIFY_SOCKET, tagged with the originating unit id, so that they are preserved + across restarts of the inner manager and handed back to the originating unit when it is started + again. For this to take effect, the enveloping unit must itself enable + FileDescriptorStoreMax= and FileDescriptorStorePreserve=yes. + See the File Descriptor Store + overview for details. + Use systemctl clean --what=fdstore … to release the file descriptor store explicitly. diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c index a928f84ef4e95..5c5177c3bea6a 100644 --- a/src/analyze/analyze-condition.c +++ b/src/analyze/analyze-condition.c @@ -98,7 +98,7 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit, return log_error_errno(r, "Failed to initialize manager: %m"); log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 08bff2cf9e9d2..fdeaf69e17446 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -2713,7 +2713,7 @@ static int offline_security_checks( log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; diff --git a/src/analyze/analyze-verify-util.c b/src/analyze/analyze-verify-util.c index e7ffae5a28787..ad553078d4833 100644 --- a/src/analyze/analyze-verify-util.c +++ b/src/analyze/analyze-verify-util.c @@ -315,7 +315,7 @@ int verify_units( log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; diff --git a/src/core/main.c b/src/core/main.c index c6d66c9ec783b..8d5ab57e3df54 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -15,6 +15,7 @@ #include "sd-bus.h" #include "sd-daemon.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" @@ -44,12 +45,15 @@ #include "emergency-action.h" #include "env-util.h" #include "escape.h" +#include "extract-word.h" #include "fd-util.h" #include "fdset.h" #include "fileio.h" #include "format-table.h" #include "format-util.h" #include "glyph-util.h" +#include "hash-funcs.h" +#include "hashmap.h" #include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" @@ -59,6 +63,7 @@ #include "initrd-util.h" #include "io-util.h" #include "ipe-setup.h" +#include "json-util.h" #include "killall.h" #include "kmod-setup.h" #include "label-util.h" @@ -82,6 +87,7 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "proc-cmdline.h" #include "process-util.h" #include "random-util.h" @@ -91,6 +97,7 @@ #include "selinux-setup.h" #include "selinux-util.h" #include "serialize.h" +#include "service.h" #include "set.h" #include "signal-util.h" #include "smack-setup.h" @@ -3069,10 +3076,254 @@ static int initialize_security( return 0; } -static int collect_fds(FDSet **ret_fds, const char **ret_error_message) { +static int parse_listen_fds_env(unsigned *ret_n_fds, char ***ret_names) { + _cleanup_strv_free_ char **names = NULL; + const char *e; + unsigned n_fds; + int r; + + assert(ret_n_fds); + assert(ret_names); + + /* Parse and validate the LISTEN_PID=/LISTEN_PIDFDID=/LISTEN_FDS=/LISTEN_FDNAMES= environment + * variables. */ + + e = secure_getenv("LISTEN_PID"); + if (!e) + return -ENXIO; + + pid_t pid; + r = parse_pid(e, &pid); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_PID=%s: %m", e); + if (pid != getpid_cached()) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "LISTEN_PID=%s does not match our own PID " PID_FMT ", ignoring.", + e, + getpid_cached()); + + e = secure_getenv("LISTEN_PIDFDID"); + if (e) { + uint64_t own_pidfdid, pidfdid; + + r = safe_atou64(e, &pidfdid); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_PIDFDID=%s: %m", e); + + if (pidfd_get_inode_id_self_cached(&own_pidfdid) >= 0 && pidfdid != own_pidfdid) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "LISTEN_PIDFDID=%s does not match our own pidfdid %" PRIu64 ", ignoring.", + e, + own_pidfdid); + } + + e = secure_getenv("LISTEN_FDS"); + if (!e) + return -ENXIO; + + r = safe_atou(e, &n_fds); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_FDS= value '%s': %m", e); + if (n_fds == 0) + return -ENXIO; + if (n_fds > INT_MAX - SD_LISTEN_FDS_START) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid number of fds in LISTEN_FDS= value '%s'", e); + + e = secure_getenv("LISTEN_FDNAMES"); + if (!e) + return -ENXIO; + + r = strv_split_full(&names, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_FDNAMES=%s: %m", e); + if (strv_length(names) != (size_t) n_fds) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Mismatch between number of LISTEN_FDS= and LISTEN_FDNAMES= entries: %u vs %zu", + n_fds, strv_length(names)); + + *ret_n_fds = n_fds; + *ret_names = TAKE_PTR(names); + return 0; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + index_to_tag_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func, + ListenFDsTag, listen_fds_tag_free); + +static int parse_listen_fds_mapping(int mapping_fd, Hashmap **ret_index_to_tag) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_hashmap_free_ Hashmap *index_to_tag = NULL; + const char *unit_id; + sd_json_variant *fds_json; + int r; + + assert(mapping_fd >= 0); + assert(ret_index_to_tag); + + /* Parse the JSON mapping memfd that the downstream manager pushed alongside the indexed fds: + * { "unit-name.service": [ { "name": "fdname1", "index": 1 }, ... ], ... } + * Returns a hashmap keyed by stringified index ("1", "2", ...) with ListenFDsTag* values + * carrying the resolved (unit_id, original fdname, upstream index). */ + + _cleanup_fclose_ FILE *f = NULL; + r = fdopen_independent(mapping_fd, "r", &f); + if (r < 0) + return log_warning_errno(r, "Failed to open fdstore-mapping memfd: %m"); + + r = sd_json_parse_file(f, "fdstore-mapping", SD_JSON_PARSE_MUST_BE_OBJECT, &root, + /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse fdstore-mapping JSON: %m"); + + JSON_VARIANT_OBJECT_FOREACH(unit_id, fds_json, root) { + sd_json_variant *entry; + + if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) { + log_warning("fdstore-mapping has invalid unit name '%s', skipping.", unit_id); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { + struct { + const char *name; + uint64_t index; + } p = { }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "index", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, index), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (p.index == 0) { + log_warning("fdstore-mapping entry for unit '%s' name '%s' has zero index, skipping.", unit_id, p.name); + continue; + } + + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t = new(ListenFDsTag, 1); + if (!t) + return log_oom(); + + *t = (ListenFDsTag) { + .index = p.index, + }; + + t->unit_id = strdup(unit_id); + t->fdname = strdup(p.name); + if (!t->unit_id || !t->fdname) + return log_oom(); + + /* Key points into the value struct, so freeing the value frees the key. */ + r = hashmap_ensure_put(&index_to_tag, &index_to_tag_hash_ops, &t->index, t); + if (r < 0) + return log_warning_errno(r, "Failed to insert fdstore-mapping entry into hashmap: %m"); + if (r > 0) + TAKE_PTR(t); + } + } + + *ret_index_to_tag = TAKE_PTR(index_to_tag); + return 0; +} + +static int collect_listen_fds_named(FDSet *fds, Hashmap **ret_named_fds) { + _cleanup_hashmap_free_ Hashmap *named_fds = NULL, *index_to_tag = NULL; + _cleanup_strv_free_ char **names = NULL; + unsigned n_fds; + int r; + + assert(fds); + assert(ret_named_fds); + + /* Pull entries from the LISTEN_FDS=/LISTEN_FDNAMES= protocol out of 'fds' into a hashmap + * keyed by fd. Two flavours of named entries are recognized: + * + * - A single mapping memfd whose fdname matches SERVICE_FDSTORE_MAPPING_FDNAME, which + * contains a JSON map pairing numeric indices to (unit-id, original-fdname). + * - Numeric indices (matching entries in the mapping document) for the actual fds. + * + * The hashmap owns the fds (closed via destructor on cleanup) so any entries the dispatcher + * does not consume are correctly cleaned up. */ + + r = parse_listen_fds_env(&n_fds, &names); + if (r < 0) { + /* Fail gracefully here, just warn and ignore but otherwise proceed on parsing failure */ + if (r != -ENXIO) + log_warning_errno(r, "Failed to parse LISTEN_FDS environment, ignoring: %m"); + *ret_named_fds = NULL; + return 0; + } + + /* First pass: locate and parse the mapping memfd, if any. */ + for (unsigned i = 0; i < n_fds; i++) { + int fd = SD_LISTEN_FDS_START + i; + + if (!streq(names[i], SERVICE_FDSTORE_MAPPING_FDNAME)) + continue; + + if (!fdset_contains(fds, fd)) + continue; + + (void) parse_listen_fds_mapping(fd, &index_to_tag); + + /* The mapping memfd itself is not routed to any unit; close it and remove from fds + * so it doesn't get redistributed */ + assert_se(fdset_remove(fds, fd) == fd); + safe_close(fd); + break; + } + + /* Second pass: route fds whose name matches an entry in the mapping. */ + for (unsigned i = 0; i < n_fds; i++) { + int fd = SD_LISTEN_FDS_START + i; + const char *name = names[i], *suffix; + ListenFDsTag *t; + uint64_t idx; + + if (!fdset_contains(fds, fd)) + continue; + + if (!index_to_tag) + continue; + + suffix = startswith(name, SERVICE_FDSTORE_SUB_FDNAME_PREFIX); + if (!suffix || safe_atou64(suffix, &idx) < 0) + continue; + + /* Steal the matching mapping entry — we transfer ownership of the parsed + * (unit_id, fdname, index) struct into the per-fd hashmap that the manager + * will consume. */ + t = hashmap_remove(index_to_tag, &idx); + if (!t) + continue; + + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t_owned = t; + + r = hashmap_ensure_put(&named_fds, &fd_to_listen_fds_tag_hash_ops, FD_TO_PTR(fd), t_owned); + if (r < 0) + return log_debug_errno(r, "Failed to insert named fd into hashmap: %m"); + if (r == 0) + continue; /* fd already inserted, cannot really happen */ + + TAKE_PTR(t_owned); + + assert_se(fdset_remove(fds, fd) == fd); + } + + *ret_named_fds = TAKE_PTR(named_fds); + return 1; +} + +static int collect_fds(FDSet **ret_fds, Hashmap **ret_named_fds, const char **ret_error_message) { int r; assert(ret_fds); + assert(ret_named_fds); assert(ret_error_message); /* Pick up all fds passed to us. We apply a filter here: we only take the fds that have O_CLOEXEC @@ -3094,6 +3345,8 @@ static int collect_fds(FDSet **ret_fds, const char **ret_error_message) { /* The serialization fd should have O_CLOEXEC turned on already, let's verify that we didn't pick it up here */ assert_se(!arg_serialization || !fdset_contains(*ret_fds, fileno(arg_serialization))); + (void) collect_listen_fds_named(*ret_fds, ret_named_fds); + return 0; } @@ -3151,6 +3404,7 @@ int main(int argc, char *argv[]) { * for the two that indicate whether * these fields are initialized! */ bool skip_setup, loaded_policy = false, queue_default_job = false, first_boot = false; + _cleanup_hashmap_free_ Hashmap *named_listen_fds = NULL; char *switch_root_dir = NULL, *switch_root_init = NULL; usec_t before_startup, after_startup; static char systemd[] = "systemd"; @@ -3396,7 +3650,7 @@ int main(int argc, char *argv[]) { log_close(); /* Remember open file descriptors for later deserialization */ - r = collect_fds(&fds, &error_message); + r = collect_fds(&fds, &named_listen_fds, &error_message); if (r < 0) goto finish; @@ -3471,7 +3725,7 @@ int main(int argc, char *argv[]) { before_startup = now(CLOCK_MONOTONIC); - r = manager_startup(m, arg_serialization, fds, /* root= */ NULL); + r = manager_startup(m, arg_serialization, fds, named_listen_fds, /* root= */ NULL); if (r < 0) { error_message = "Failed to start up manager"; goto finish; diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 6bc41f15f93bf..bef4021771e3f 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -124,6 +124,7 @@ int manager_serialize( (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective)); (void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count); + (void) serialize_item_format(f, "fd-store-upstream-next-index", "%" PRIu64, m->fd_store_upstream_next_index); for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { _cleanup_free_ char *joined = NULL; @@ -757,6 +758,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { log_notice("Failed to parse soft reboots counter '%s', ignoring.", val); else m->soft_reboots_count = n; + } else if ((val = startswith(l, "fd-store-upstream-next-index="))) { + if (safe_atou64(val, &m->fd_store_upstream_next_index) < 0) + log_notice("Failed to parse fd-store-upstream-next-index '%s', ignoring.", val); } else if ((val = startswith(l, "previous-objective="))) { ManagerObjective objective; diff --git a/src/core/manager.c b/src/core/manager.c index 038690a808a00..1bd35a7d21b53 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -76,6 +76,7 @@ #include "rm-rf.h" #include "selinux-util.h" #include "serialize.h" +#include "service.h" #include "set.h" #include "signal-util.h" #include "socket-util.h" @@ -1878,6 +1879,93 @@ static void manager_catchup(Manager *m) { } } +ListenFDsTag* listen_fds_tag_free(ListenFDsTag *t) { + if (!t) + return NULL; + + free(t->unit_id); + free(t->fdname); + return mfree(t); +} + +DEFINE_HASH_OPS_FULL( + fd_to_listen_fds_tag_hash_ops, + void, trivial_hash_func, trivial_compare_func, close_fd_ptr, + ListenFDsTag, listen_fds_tag_free); + +int manager_dispatch_external_fd_to_unit( + Manager *m, + const char *unit_id, + const char *fdname, + uint64_t index, + int fd_in, + const char *log_context) { + + _cleanup_close_ int fd = ASSERT_FD(fd_in); + Unit *u = NULL; + int r; + + assert(m); + assert(unit_id); + assert(fdname); + assert(log_context); + + /* Load the unit eagerly: if the unit file exists this brings it into UNIT_LOADED, otherwise it + * lands in UNIT_NOT_FOUND. In both cases we want to attach the fd so it's preserved until the + * unit is fully stopped (or its file appears via daemon-reload). */ + r = manager_load_unit(m, unit_id, /* path= */ NULL, /* e= */ NULL, &u); + if (r < 0) + return log_warning_errno(r, "%s: failed to load unit '%s', closing fd '%s': %m", + log_context, unit_id, fdname); + + if (!UNIT_VTABLE(u)->attach_external_fd_to_fdstore) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unit '%s' does not support fd restoration, closing fd '%s'.", + log_context, unit_id, fdname); + + r = UNIT_VTABLE(u)->attach_external_fd_to_fdstore(u, TAKE_FD(fd), fdname, index); + if (r < 0) + return log_unit_warning_errno(u, r, "%s: failed to attach fd '%s' to fd store: %m", + log_context, fdname); + + return 1; /* fd consumed */ +} + +static int manager_distribute_listen_fds_named(Manager *m, Hashmap *named_listen_fds) { + assert(m); + + /* Route fds whose LISTEN_FDNAMES name was a numeric index into the matching unit's fd store. + * The hashmap is built and owned by main.c's collect_fds(), keyed by fd, with ListenFDsTag* values + * that already carry the parsed unit-id, original fdname and index (resolved against the + * upstream-pushed fdstore-mapping memfd). We steal entries here so any leftover (skipped) entries + * are still cleaned up by the hashmap's destructor on the caller side. */ + + if (MANAGER_IS_TEST_RUN(m)) + return 0; + + for (;;) { + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t = NULL; + _cleanup_close_ int fd = -EBADF; + void *key; + + t = hashmap_steal_first_key_and_value(named_listen_fds, &key); + if (!t) + break; + + fd = PTR_TO_FD(key); + + if (!t->unit_id || !t->fdname) + continue; + + if (!unit_name_is_valid(t->unit_id, UNIT_NAME_ANY)) + continue; + + (void) manager_dispatch_external_fd_to_unit(m, t->unit_id, t->fdname, t->index, TAKE_FD(fd), "LISTEN_FDS"); + } + + return 0; +} + static void manager_distribute_fds(Manager *m, FDSet *fds) { Unit *u; @@ -2034,7 +2122,7 @@ static int manager_make_runtime_dir(Manager *m) { return 0; } -int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *root) { +int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_listen_fds, const char *root) { int r; assert(m); @@ -2103,6 +2191,11 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo if (m->previous_objective == MANAGER_SOFT_REBOOT) m->soft_reboots_count++; + /* Pick up fds passed via the LISTEN_FDS=/LISTEN_FDNAMES= protocol that are tagged with a + * unit id ("unit-id|fdname"), and route them into the matching unit's fd store. Untagged + * fds remain in 'fds' and are handed to socket units below as before. */ + (void) manager_distribute_listen_fds_named(m, named_listen_fds); + /* Any fds left? Find some unit which wants them. This is useful to allow container managers to pass * some file descriptors to us pre-initialized. This enables socket-based activation of entire * containers. */ @@ -2142,6 +2235,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo /* Clean up runtime objects */ manager_vacuum(m); + /* After deserialization, refresh the upstream JSON mapping memfd so the supervisor's + * view of our fd store stays consistent with the indices we just restored. */ + (void) service_propagate_fd_store_mapping_upstream(m); + if (serialization) /* Let's wait for the UnitNew/JobNew messages being sent, before we notify that the * reload is finished */ diff --git a/src/core/manager.h b/src/core/manager.h index d17693369ab5a..4695112c041ff 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -513,6 +513,11 @@ typedef struct Manager { /* The number of successfully completed configuration reloads. */ uint64_t reload_count; + /* Monotonic counter for fdstore entries propagated to a NOTIFY_SOCKET supervisor. Each propagated + * fd is sent upstream using this index as the FDNAME. The mapping (index -> unit_id + original fdname) + * is pushed alongside as a JSON memfd named "systemd-fdstore-mapping". */ + uint64_t fd_store_upstream_next_index; + /* Original ambient capabilities when we were initialized */ uint64_t saved_ambient_set; } Manager; @@ -542,7 +547,20 @@ int manager_new(RuntimeScope scope, ManagerTestRunFlags test_run_flags, Manager Manager* manager_free(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); -int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *root); +/* One entry parsed out of the upstream "systemd-fdstore-mapping" memfd. Pairs the numeric index from the + * JSON map to the (unit-id, original fdname) the fd was originally stored as. */ +typedef struct ListenFDsTag { + char *unit_id; + char *fdname; + uint64_t index; +} ListenFDsTag; + +ListenFDsTag* listen_fds_tag_free(ListenFDsTag *t); +DEFINE_TRIVIAL_CLEANUP_FUNC(ListenFDsTag*, listen_fds_tag_free); + +extern const struct hash_ops fd_to_listen_fds_tag_hash_ops; + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_listen_fds, const char *root); Job *manager_get_job(Manager *m, uint32_t id); Unit *manager_get_unit(Manager *m, const char *name); @@ -552,6 +570,7 @@ int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); bool manager_unit_cache_should_retry_load(Unit *u); int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); +int manager_dispatch_external_fd_to_unit(Manager *m, const char *unit_id, const char *fdname, uint64_t index, int fd, const char *log_context); int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, Unit **ret); int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); diff --git a/src/core/service.c b/src/core/service.c index 445812f9522d5..6a02ca8d6f983 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -6,6 +6,7 @@ #include #include "sd-bus.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" @@ -15,6 +16,7 @@ #include "bus-util.h" #include "cgroup.h" #include "chase.h" +#include "daemon-util.h" #include "dbus-service.h" #include "dbus-unit.h" #include "devnum-util.h" @@ -33,6 +35,7 @@ #include "image-policy.h" #include "log.h" #include "manager.h" +#include "memfd-util.h" #include "mount-util.h" #include "namespace.h" #include "open-file.h" @@ -462,12 +465,27 @@ static void service_override_watchdog_timeout(Service *s, usec_t watchdog_overri log_unit_debug(UNIT(s), "watchdog_override_usec="USEC_FMT, s->watchdog_override_usec); } -static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { +static ServiceFDStore* service_fd_store_unlink_full(ServiceFDStore *fs, bool propagate_upstream) { if (!fs) return NULL; if (fs->service) { assert(fs->service->n_fd_store > 0); + + /* If we previously propagated this fd to an enveloping service/container manager via + * the FDSTORE=1 protocol on its NOTIFY_SOCKET (only done when persistence is on), + * tell that supervisor to drop it now too, so the upstream fd store stays in sync. + * Only do this for explicit removals (EPOLLHUP/EPOLLERR or app FDSTOREREMOVE), not + * for local cleanup like service shutdown or fdstore-limit truncation: in those + * cases we want the upstream copy to survive so it can be handed back to us later. */ + if (propagate_upstream && fs->index > 0) { + (void) notify_remove_fd_warnf(SERVICE_FDSTORE_SUB_FDNAME_PREFIX "%" PRIu64, fs->index); + fs->index = 0; + /* Refresh the upstream JSON mapping so the supervisor's view stays in sync + * with what fds are actually still around. */ + (void) service_propagate_fd_store_mapping_upstream(UNIT(fs->service)->manager); + } + LIST_REMOVE(fd_store, fs->service->fd_store, fs); fs->service->n_fd_store--; } @@ -479,6 +497,10 @@ static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { return mfree(fs); } +static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { + return service_fd_store_unlink_full(fs, /* propagate_upstream= */ false); +} + DEFINE_TRIVIAL_CLEANUP_FUNC(ServiceFDStore*, service_fd_store_unlink); static void service_release_fd_store(Service *s) { @@ -495,6 +517,21 @@ static void service_release_fd_store(Service *s) { assert(s->n_fd_store == 0); } +static void service_truncate_fd_store(Service *s) { + assert(s); + + /* Drop fds that exceed the (possibly newly lowered) n_fd_store_max, e.g. after the fragment was + * parsed and FileDescriptorStoreMax= shrunk the configured limit. Newest entries are at the head + * of the list, so drop from the head (newest first). */ + + while (s->n_fd_store > s->n_fd_store_max) { + ServiceFDStore *fs = ASSERT_PTR(s->fd_store); + log_unit_debug(UNIT(s), "Dropping stored fd '%s' to honor FileDescriptorStoreMax=%u.", + strna(fs->fdname), s->n_fd_store_max); + service_fd_store_unlink(fs); + } +} + static void service_release_extra_fds(Service *s) { assert(s); @@ -512,6 +549,15 @@ static void service_release_extra_fds(Service *s) { s->n_extra_fds = 0; } +ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *fd) { + if (!fd) + return NULL; + + safe_close(fd->fd); + free(fd->fdname); + return mfree(fd); +} + static void service_release_stdio_fd(Service *s) { assert(s); @@ -585,7 +631,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us "Received %s on stored fd %d (%s), closing.", revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP", fs->fd, strna(fs->fdname)); - service_fd_store_unlink(fs); + service_fd_store_unlink_full(fs, /* propagate_upstream= */ true); if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); @@ -593,7 +639,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us return 0; } -static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll) { +int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, bool propagate_upstream) { _cleanup_(service_fd_store_unlinkp) ServiceFDStore *fs = NULL; _cleanup_(asynchronous_closep) int fd = ASSERT_FD(fd_in); struct stat st; @@ -647,14 +693,43 @@ static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fs->fd, fs->fdname); + /* If fd-store persistence is enabled and we have an enveloping service/container manager (i.e. + * NOTIFY_SOCKET is set), forward the fd to it via sd_notify(FDSTORE=1) tagged with a fresh + * incrementing index, and (re-)push the JSON mapping memfd that pairs the index back to this + * unit and the original fdname. This way fdstore persistence chains all the way up to whichever + * entity is ultimately responsible for surviving across kexec/restart, regardless of fdname + * length or charset constraints. */ + if (propagate_upstream && s->fd_store_preserve_mode == EXEC_PRESERVE_YES) { + Manager *m = ASSERT_PTR(UNIT(s)->manager); + char idx_str[STRLEN(SERVICE_FDSTORE_SUB_FDNAME_PREFIX) + DECIMAL_STR_MAX(uint64_t)]; + + assert(m->fd_store_upstream_next_index < UINT64_MAX); + uint64_t idx = ++m->fd_store_upstream_next_index; + + xsprintf(idx_str, SERVICE_FDSTORE_SUB_FDNAME_PREFIX "%" PRIu64, idx); + + r = notify_push_fd(fs->fd, idx_str); + if (r < 0) + log_unit_debug_errno(UNIT(s), r, + "Failed to propagate fd '%s' to upstream supervisor as index %" PRIu64 ", ignoring: %m", + fs->fdname, idx); + else + fs->index = idx; + } + fs->service = s; LIST_PREPEND(fd_store, s->fd_store, TAKE_PTR(fs)); s->n_fd_store++; + if (propagate_upstream && s->fd_store_preserve_mode == EXEC_PRESERVE_YES) + /* Refresh the JSON mapping memfd so the supervisor can resolve the new index. Do this + * after LIST_PREPEND so the new entry is visible to the helper. */ + (void) service_propagate_fd_store_mapping_upstream(UNIT(s)->manager); + return 1; /* fd newly stored */ } -static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bool do_poll) { +static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bool do_poll, bool propagate_upstream) { int r; assert(s); @@ -666,7 +741,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo if (fd < 0) break; - r = service_add_fd_store(s, fd, name, do_poll); + r = service_add_fd_store(s, fd, name, do_poll, propagate_upstream); if (r == -EXFULL) return log_unit_warning_errno(UNIT(s), r, "Cannot store more fds than FileDescriptorStoreMax=%u, closing remaining.", @@ -678,6 +753,139 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo return 0; } +int service_propagate_fd_store_mapping_upstream(Manager *m) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *text = NULL; + Unit *u; + int r; + + assert(m); + + /* Build a JSON object listing all fdstore entries that have been propagated upstream: + * + * { + * "unit-name.service": [ + * { "name": "fdname1", "index": 1 }, + * { "name": "fdname2", "index": 2 } + * ], + * ... + * } + * + * Push it as a sealed memfd to the upstream supervisor under a fixed FDNAME so it can resolve + * the per-fd numeric indices back to (unit_id, original fdname) at startup. The mapping is + * regenerated and re-pushed after every add/remove, so the supervisor's view stays in sync. */ + HASHMAP_FOREACH(u, m->units) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; + Service *s; + + if (u->type != UNIT_SERVICE) + continue; + + s = SERVICE(u); + if (!s->fd_store) + continue; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + if (fs->index == 0) + continue; + + r = sd_json_variant_append_arraybo( + &entries, + SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), + SD_JSON_BUILD_PAIR_UNSIGNED("index", fs->index)); + if (r < 0) + return log_warning_errno(r, "Failed to build fdstore-mapping JSON entry: %m"); + } + + if (!entries) + continue; + + r = sd_json_variant_set_field(&root, u->id, entries); + if (r < 0) + return log_warning_errno(r, "Failed to add unit to fdstore-mapping JSON: %m"); + } + + if (!root) { + /* Nothing to map: tell the supervisor to drop any previously-pushed mapping memfd + * so it doesn't keep stale entries around. Only do this if we have actually pushed + * one in the past (i.e. we ever assigned an upstream index, either in this + * incarnation or in a previous one whose counter we deserialized), otherwise we + * might inadvertently remove a mapping that was just handed back to us via + * LISTEN_FDS during a fresh manager startup. */ + if (m->fd_store_upstream_next_index > 0) + (void) notify_remove_fd_warn(SERVICE_FDSTORE_MAPPING_FDNAME); + return 0; + } + + r = sd_json_variant_format(root, /* flags= */ 0, &text); + if (r < 0) + return log_warning_errno(r, "Failed to format fdstore-mapping JSON: %m"); + + fd = memfd_new_and_seal_string(SERVICE_FDSTORE_MAPPING_FDNAME, text); + if (fd < 0) + return log_warning_errno(fd, "Failed to create fdstore-mapping memfd: %m"); + + r = notify_push_fd(fd, SERVICE_FDSTORE_MAPPING_FDNAME); + if (r < 0) + return log_warning_errno(r, "Failed to propagate fdstore-mapping to upstream supervisor: %m"); + + return 0; +} + +static int service_attach_external_fd_to_fdstore(Unit *u, int fd, const char *fdname, uint64_t index) { + Service *s = ASSERT_PTR(SERVICE(u)); + int r; + + assert(u->type == UNIT_SERVICE); + + /* If the unit file is absent, bump the limit by one and force preserve so the fd is + * accepted and pins the unit until a daemon-reload picks up the unit file or it is + * explicitly stopped. */ + if (u->load_state == UNIT_NOT_FOUND) { + s->fd_store_preserve_mode = EXEC_PRESERVE_YES; + s->n_fd_store_max++; + } + + /* Don't propagate upstream: the fd just came back from upstream, forwarding it would loop. */ + r = service_add_fd_store(s, fd, fdname, /* do_poll= */ true, /* propagate_upstream= */ false); + if (r <= 0 && u->load_state == UNIT_NOT_FOUND) + s->n_fd_store_max--; + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to add LUO fd '%s' to fd store: %m", fdname); + + /* If the fd was previously propagated to an upstream supervisor under a numeric index, + * preserve that index on the freshly-added entry so that future FDSTOREREMOVE messages + * (and the fdstore-mapping memfd we re-push to the supervisor) reference the same index + * the supervisor already knows about. service_add_fd_store() does LIST_PREPEND() on + * success, so the new entry is at the head. Also keep the manager's allocator counter + * past the highest restored index, to avoid collisions with newly allocated indices. */ + if (r > 0 && index > 0 && s->fd_store) { + Manager *m = ASSERT_PTR(u->manager); + + s->fd_store->index = index; + if (index > m->fd_store_upstream_next_index) + m->fd_store_upstream_next_index = index; + } + + /* If the unit is otherwise inactive (typical for LUO/upstream restore), pin its resources so it + * isn't garbage-collected before something explicitly stops it. Only flip the state when both + * runtime and deserialized state agree on DEAD, to avoid clobbering a just-deserialized live + * state (e.g. SERVICE_RUNNING after daemon-reload, where service_coldplug() will set the proper + * state later). */ + if (r > 0 && + s->state == SERVICE_DEAD && + s->deserialized_state == SERVICE_DEAD && + s->fd_store_preserve_mode == EXEC_PRESERVE_YES) { + service_set_state(s, SERVICE_DEAD_RESOURCES_PINNED); + s->deserialized_state = SERVICE_DEAD_RESOURCES_PINNED; + } + + if (r > 0) + log_unit_debug(u, "Restored fd '%s'.", fdname); + return r; +} + static void service_remove_fd_store(Service *s, const char *name) { assert(s); assert(name); @@ -687,7 +895,7 @@ static void service_remove_fd_store(Service *s, const char *name) { continue; log_unit_debug(UNIT(s), "Got explicit request to remove fd %i (%s), closing.", fs->fd, name); - service_fd_store_unlink(fs); + service_fd_store_unlink_full(fs, /* propagate_upstream= */ true); } } @@ -952,6 +1160,11 @@ static int service_load(Unit *u) { if (u->load_state != UNIT_LOADED) return 0; + /* The fragment may have lowered FileDescriptorStoreMax= below the number of fds currently in the + * store (e.g. fds restored from LUO into a synthesized UNIT_NOT_FOUND service that just got a real + * fragment via lazy reload, but which now disables the fd store). */ + service_truncate_fd_store(s); + /* This is a new unit? Then let's add in some extras */ r = service_add_extras(s); if (r < 0) @@ -1437,7 +1650,8 @@ static int service_coldplug(Unit *u) { int r; assert(s); - assert(s->state == SERVICE_DEAD); + /* Ensure we can insert FD store into units at boot */ + assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED)); if (s->deserialized_state == s->state) return 0; @@ -3470,7 +3684,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (!c) return log_oom(); - (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %s", copy, c, one_zero(fs->do_poll)); + (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %s %" PRIu64, + copy, c, one_zero(fs->do_poll), fs->index); } FOREACH_ARRAY(i, s->extra_fds, s->n_extra_fds) { @@ -3740,12 +3955,13 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->socket_fd = deserialize_fd(fds, value); } else if (streq(key, "fd-store-fd")) { - _cleanup_free_ char *fdv = NULL, *fdn = NULL, *fdp = NULL; + _cleanup_free_ char *fdv = NULL, *fdn = NULL, *fdp = NULL, *fdi = NULL; _cleanup_close_ int fd = -EBADF; int do_poll; + uint64_t index = 0; - r = extract_many_words(&value, " ", EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE, &fdv, &fdn, &fdp); - if (r < 2 || r > 3) { + r = extract_many_words(&value, " ", EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE, &fdv, &fdn, &fdp, &fdi); + if (r < 2 || r > 4) { log_unit_debug(u, "Failed to deserialize fd-store-fd, ignoring: %s", value); return 0; } @@ -3754,19 +3970,45 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, if (fd < 0) return 0; - do_poll = r == 3 ? parse_boolean(fdp) : true; + do_poll = r >= 3 ? parse_boolean(fdp) : true; if (do_poll < 0) { log_unit_debug_errno(u, do_poll, "Failed to deserialize fd-store-fd do_poll, ignoring: %s", fdp); return 0; } - r = service_add_fd_store(s, TAKE_FD(fd), fdn, do_poll); + if (r == 4 && safe_atou64(fdi, &index) < 0) { + log_unit_debug(u, "Failed to parse fd-store-fd index '%s', ignoring.", fdi); + index = 0; + } + + /* If the unit file is currently absent (e.g. after switch-root, before the unit file is + * available in the new root), the synthesized service has n_fd_store_max=0 and + * preserve_mode=NO, which would reject the fd. Grow the limit by one per fd so it matches + * exactly what was handed back, and force EXEC_PRESERVE_YES, so the fd survives until + * either a daemon-reload picks up the unit file or the service is explicitly stopped. + * Same logic as in luo_dispatch_fd(). */ + if (u->load_state == UNIT_NOT_FOUND) { + s->fd_store_preserve_mode = EXEC_PRESERVE_YES; + s->n_fd_store_max++; + } + + /* Don't propagate upstream during deserialization: the upstream supervisor (if any) + * already has these fds from when they were originally pushed. */ + r = service_add_fd_store(s, TAKE_FD(fd), fdn, do_poll, /* propagate_upstream= */ false); + if (r <= 0 && u->load_state == UNIT_NOT_FOUND) + /* The fd was not actually stored, roll back the limit bump. */ + s->n_fd_store_max--; if (r < 0) { log_unit_debug_errno(u, r, "Failed to store deserialized fd '%s', ignoring: %m", fdn); return 0; } + /* If preservation is enabled then this fd was previously propagated upstream when it + * was first pushed. Restore the index so future removals can be forwarded upstream + * and the JSON mapping memfd can be regenerated. */ + if (r > 0 && s->fd_store && index > 0) + s->fd_store->index = index; } else if (streq(key, "extra-fd")) { _cleanup_free_ char *fdv = NULL, *fdn = NULL; _cleanup_close_ int fd = -EBADF; @@ -5280,7 +5522,7 @@ static void service_notify_message( name = NULL; } - (void) service_add_fd_store_set(s, fds, name, !strv_contains(tags, "FDPOLL=0")); + (void) service_add_fd_store_set(s, fds, name, !strv_contains(tags, "FDPOLL=0"), /* propagate_upstream= */ fdstore_detected()); } /* Notify clients about changed status or main pid */ @@ -6178,6 +6420,8 @@ const UnitVTable service_vtable = { .serialize = service_serialize, .deserialize_item = service_deserialize_item, + .attach_external_fd_to_fdstore = service_attach_external_fd_to_fdstore, + .active_state = service_active_state, .sub_state_to_string = service_sub_state_to_string, diff --git a/src/core/service.h b/src/core/service.h index 9750b19ce285f..b57634cdb0f41 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -8,6 +8,18 @@ #include "pidref.h" #include "unit.h" +/* FDNAME used to push the JSON mapping memfd that pairs upstream-propagated fdstore indices with + * (unit-id, original fdname) tuples. The receiving manager looks for this fdname in LISTEN_FDNAMES + * to find the mapping document. */ +#define SERVICE_FDSTORE_MAPPING_FDNAME "systemd-fdstore-mapping" + +/* Prefix for the upstream FDNAME used when forwarding individual fd-store entries to a parent + * supervisor: the entries are exposed as "sub-fdstore-" so the supervisor's own fd-store + * namespace doesn't collide with names a downstream service manager assigns. The trailing index + * is matched up with an entry in the SERVICE_FDSTORE_MAPPING_FDNAME memfd to recover the original + * (unit, fdname) pair. */ +#define SERVICE_FDSTORE_SUB_FDNAME_PREFIX "sub-fdstore-" + typedef enum ServiceRestart { SERVICE_RESTART_NO, SERVICE_RESTART_ON_SUCCESS, @@ -112,6 +124,10 @@ typedef struct ServiceFDStore { char *fdname; sd_event_source *event_source; bool do_poll; + /* If non-zero, this fd was forwarded to the NOTIFY_SOCKET supervisor via FDSTORE=1, with the + * stringified value of this index as its FDNAME. The originating unit-id and original fdname + * are recorded in a JSON mapping memfd that is also pushed upstream. */ + uint64_t index; LIST_FIELDS(struct ServiceFDStore, fd_store); } ServiceFDStore; @@ -279,6 +295,12 @@ extern const UnitVTable service_vtable; int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net); void service_release_socket_fd(Service *s); +int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, bool propagate_upstream); + +int service_propagate_fd_store_mapping_upstream(Manager *m); + +ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *fd); + usec_t service_restart_usec_next(const Service *s) _pure_; int service_determine_exec_selinux_label(Service *s, char **ret); diff --git a/src/core/unit.h b/src/core/unit.h index d20e46ab57927..f96ae279362cc 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -618,6 +618,13 @@ typedef struct UnitVTable { /* Try to match up fds with what we need for this unit */ void (*distribute_fds)(Unit *u, FDSet *fds); + /* Restore one file descriptor that PID 1 retrieved from a Live Update Orchestrator session into the + * unit's per-instance state (e.g. fd store). Always consumes 'fd', even on failure. If the fd + * was previously propagated to an upstream NOTIFY_SOCKET supervisor under a numeric index, + * 'index' carries that index so it can be re-claimed (avoiding collisions with newly allocated + * indices and keeping FDSTOREREMOVE messages routable). Pass 0 to indicate no preserved index. */ + int (*attach_external_fd_to_fdstore)(Unit *u, int fd, const char *fdname, uint64_t index); + /* Boils down the more complex internal state of this unit to * a simpler one that the engine can understand */ UnitActiveState (*active_state)(Unit *u); diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c index 321a9e58dafc3..d85e52bcb68c3 100644 --- a/src/shared/daemon-util.c +++ b/src/shared/daemon-util.c @@ -7,6 +7,7 @@ #include "errno-util.h" #include "fd-util.h" #include "log.h" +#include "parse-util.h" #include "string-util.h" #include "time-util.h" @@ -84,6 +85,27 @@ int notify_push_fdf(int fd, const char *format, ...) { return notify_push_fd(fd, name); } +bool fdstore_detected(void) { + static int cached = -1; + int r; + + if (cached >= 0) + return cached; + + const char *e = getenv("FDSTORE"); + if (isempty(e)) + return (cached = 0); + + unsigned u; + r = safe_atou(e, &u); + if (r < 0) { + log_debug_errno(r, "Failed to parse 'FDSTORE=%s', ignoring: %m", e); + return (cached = 0); + } + + return (cached = u > 0); +} + int notify_reloading_full(const char *status) { int r; diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h index 708a32985c6c5..089e418f7b809 100644 --- a/src/shared/daemon-util.h +++ b/src/shared/daemon-util.h @@ -27,6 +27,8 @@ int close_and_notify_warn(int fd, const char *name); int notify_push_fd(int fd, const char *name); int notify_push_fdf(int fd, const char *format, ...) _printf_(2, 3); +bool fdstore_detected(void); + int notify_reloading_full(const char *status); static inline int notify_reloading(void) { return notify_reloading_full("Reloading configuration..."); diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c index c3d8e7d5d54b8..e591d0d5ea60e 100644 --- a/src/test/test-bpf-firewall.c +++ b/src/test/test-bpf-firewall.c @@ -75,7 +75,7 @@ int main(int argc, char *argv[]) { /* The simple tests succeeded. Now let's try full unit-based use-case. */ ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_EQ(unit_add_name(u, "foo.service"), 0); diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c index bf56451d2501c..9d9093bee7647 100644 --- a/src/test/test-bpf-foreign-programs.c +++ b/src/test/test-bpf-foreign-programs.c @@ -303,7 +303,7 @@ int main(int argc, char *argv[]) { assert_se(runtime_dir = setup_fake_runtime_dir()); ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_OK(test_bpf_cgroup_programs(m, "single_prog.service", single_prog, ELEMENTSOF(single_prog))); diff --git a/src/test/test-bpf-restrict-fs.c b/src/test/test-bpf-restrict-fs.c index 9d54ad033760e..8e41537f21ff7 100644 --- a/src/test/test-bpf-restrict-fs.c +++ b/src/test/test-bpf-restrict-fs.c @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) { ASSERT_NOT_NULL((runtime_dir = setup_fake_runtime_dir())); ASSERT_OK(manager_new(RUNTIME_SCOPE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); /* We need to enable access to the filesystem where the binary is so we * add @common-block and @application */ diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index 6b123f8761b47..967341508053e 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -51,7 +51,7 @@ TEST_RET(cgroup_mask, .sd_booted = true) { m->defaults.tasks_accounting = false; m->defaults.tasks_max = CGROUP_TASKS_MAX_UNSET; - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* Load units and verify hierarchy. */ ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent)); diff --git a/src/test/test-engine.c b/src/test/test-engine.c index e1a2f7ea04823..5a6a1a4204406 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) { if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); printf("Load1:\n"); assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, &a) >= 0); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index e14205bdf86a3..3a124f967431f 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1435,7 +1435,7 @@ static void run_tests(RuntimeScope scope, char **patterns) { ASSERT_OK(r); m->defaults.std_output = EXEC_OUTPUT_INHERIT; /* don't rely on host journald */ - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); /* Uncomment below if you want to make debugging logs stored to journal. */ //manager_override_log_target(m, LOG_TARGET_AUTO); diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 3e318a36c91d3..259bd142e4efb 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -108,7 +108,7 @@ TEST(config_parse_exec) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); @@ -430,7 +430,7 @@ TEST(config_parse_log_extra_fields) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); @@ -788,7 +788,7 @@ TEST(config_parse_unit_env_file) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); @@ -912,7 +912,7 @@ TEST(unit_is_recursive_template_dependency) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar@1.service")); @@ -1006,7 +1006,7 @@ TEST(config_parse_open_file) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); @@ -1065,7 +1065,7 @@ TEST(config_parse_service_refresh_on_reload) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); diff --git a/src/test/test-path.c b/src/test/test-path.c index 8b02f5d0fffa4..512eb96ead60f 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -34,7 +34,7 @@ static int setup_test(Manager **m) { if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); assert_se(r >= 0); - assert_se(manager_startup(tmp, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(tmp, NULL, NULL, NULL, NULL) >= 0); STRV_FOREACH(test_path, tests_path) { _cleanup_free_ char *p = NULL; diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c index c1305ac4abd21..a523c01e8f01b 100644 --- a/src/test/test-sched-prio.c +++ b/src/test/test-sched-prio.c @@ -33,7 +33,7 @@ int main(int argc, char *argv[]) { if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* load idle ok */ assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_ok.service", NULL, &idle_ok) >= 0); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c index 4e4fdbedd0fa9..63d249d6b4efe 100644 --- a/src/test/test-socket-bind.c +++ b/src/test/test-socket-bind.c @@ -133,7 +133,7 @@ int main(int argc, char *argv[]) { assert_se(runtime_dir = setup_fake_runtime_dir()); assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("2000"), STRV_MAKE("any")) >= 0); assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("ipv6:2001-2002"), STRV_MAKE("any")) >= 0); diff --git a/src/test/test-watch-pid.c b/src/test/test-watch-pid.c index 0a325dfb0880a..59f043f49ca33 100644 --- a/src/test/test-watch-pid.c +++ b/src/test/test-watch-pid.c @@ -21,7 +21,7 @@ TEST(watch_pid) { ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(a = unit_new(m, sizeof(Service))); ASSERT_OK(unit_add_name(a, "a.service")); From 9de91f0f4c715b278637af8b73cabac892d7e000 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 1 May 2026 14:06:11 +0100 Subject: [PATCH 1741/2155] nspawn: support forwarding FDs from payloads to managers When there is a NOTIFY_SOCKET, and FDs are received from the payload following the FD Store protocol, forward them up the chain to the service manager that is managing nspawn. This allows FD Store persistence across container restarts, and can chain up for user managers as well to survive restarting those, or reexecs, and in the future reboots too via LUO. Add a new test case to exercise the PID1 -> user session -> nspawn -> payload chain. --- man/systemd-nspawn.xml | 14 ++ .../systemd-nspawn@.service.d/fdstore.conf | 3 + .../system/user@.service.d/fdstore.conf | 5 + .../mkosi.images/minimal-base/mkosi.postinst | 5 + .../systemd-nspawn@.service.d/fdstore.conf | 3 + .../system/user@.service.d/fdstore.conf | 5 + src/nspawn/nspawn.c | 85 ++++++++- src/test/meson.build | 5 + src/test/test-fdstore.c | 166 ++++++++++++++++++ test/units/TEST-13-NSPAWN.unpriv.sh | 84 +++++++++ 10 files changed, 371 insertions(+), 4 deletions(-) create mode 100644 mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf create mode 100644 mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf create mode 100644 src/test/test-fdstore.c diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index c93fad377627e..f8b82a4a6ef52 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -681,6 +681,20 @@ systemd-vmspawn1 that defaults to true.) + If systemd-nspawn itself is invoked with a $NOTIFY_SOCKET + set in its environment (i.e. it is itself supervised by a service manager that uses the + sd_notify3 + protocol), FDSTORE=1 and FDSTOREREMOVE=1 messages received + from the container payload (along with any accompanying file descriptors and + FDNAME= tag) are forwarded one level up to the enveloping service manager. This + allows the file descriptor store of services running inside the container to be preserved across + container restarts (and, transitively, across restarts, re-execs, soft-reboots and LUO-based kexecs + of any outer service manager), provided + FileDescriptorStoreMax=/FileDescriptorStorePreserve=yes are + configured on the unit running systemd-nspawn. See the + File Descriptor Store documentation + for details. + diff --git a/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf b/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf new file mode 100644 index 0000000000000..3b023f7832175 --- /dev/null +++ b/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf @@ -0,0 +1,3 @@ +[Service] +FileDescriptorStoreMax=16 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf b/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf new file mode 100644 index 0000000000000..8a0b417e97660 --- /dev/null +++ b/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf @@ -0,0 +1,5 @@ +# For tests exercising the fd store we need the unit in the rootfs to have these +# settings, or the fdstore content will be dropped in the initrd -> rootfs transition +[Service] +FileDescriptorStoreMax=20 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.images/minimal-base/mkosi.postinst b/mkosi/mkosi.images/minimal-base/mkosi.postinst index 6feaebc19a33f..ba3f4aec31d42 100755 --- a/mkosi/mkosi.images/minimal-base/mkosi.postinst +++ b/mkosi/mkosi.images/minimal-base/mkosi.postinst @@ -16,3 +16,8 @@ chmod +x "$BUILDROOT/sbin/init" if [ ! -e "$BUILDROOT/etc/os-release" ]; then ln -s ../usr/lib/os-release "$BUILDROOT/etc/os-release" fi + +# For use in the minimal containers, only needs libsystemd and libc +if [[ -x "$BUILDDIR/test-fdstore" ]]; then + cp "$BUILDDIR/test-fdstore" "$BUILDROOT/usr/bin/test-fdstore" +fi diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf new file mode 100644 index 0000000000000..3b023f7832175 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf @@ -0,0 +1,3 @@ +[Service] +FileDescriptorStoreMax=16 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf new file mode 100644 index 0000000000000..311feddad0640 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf @@ -0,0 +1,5 @@ +# For tests exercising the FD store we need the unit in the initrd to have these +# settings, or the fdstore content will be dropped in the initrd +[Service] +FileDescriptorStoreMax=20 +FileDescriptorStorePreserve=yes diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 0e532cf7b0699..16ea48eaf8850 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3782,9 +3782,13 @@ static int setup_notify_child(const void *directory) { if (r < 0) log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m"); - r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false); - if (r < 0) - log_debug_errno(r, "Failed to turn off SO_PASSRIGHTS, ignoring: %m"); + /* Only allow the container payload to pass file descriptors to us if we ourselves are + * supervised by a service manager that enabled the FD store. */ + if (!fdstore_detected()) { + r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false); + if (r < 0) + log_debug_errno(r, "Failed to turn off SO_PASSRIGHTS, ignoring: %m"); + } return TAKE_FD(fd); } @@ -4613,6 +4617,76 @@ static int setup_uid_map( return 0; } +static int forward_fd_store(char **tags, FDSet *fds) { + int r; + + /* Forward fd-store related messages to our own service manager, so that file descriptors stored + * by the inner payload propagate up the chain and are preserved across restarts. Skip entirely + * if we have no upstream supervisor (no NOTIFY_SOCKET) or no fd store available (no FDSTORE). + * + * Forwarded entries are namespaced with a "payload-" prefix on their FDNAME so that they + * cannot collide with fd-store entries that nspawn itself might want to push to its own + * upstream supervisor (the container payload and nspawn share a single upstream fdstore + * namespace, since there's only one init system per container). */ + if (!getenv("NOTIFY_SOCKET") || !fdstore_detected()) + return 0; + + if (strv_contains(tags, "FDSTOREREMOVE=1")) { + const char *fdname = strv_find_startswith(tags, "FDNAME="); + if (!fdname) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Got FDSTOREREMOVE=1 from container payload without FDNAME=, ignoring."); + if (!fdname_is_valid(fdname)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Got FDSTOREREMOVE=1 from container payload with invalid FDNAME='%s', ignoring.", + fdname); + + r = sd_notifyf(/* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=payload-%s", fdname); + if (r < 0) + return log_warning_errno(r, "Failed to forward FDSTOREREMOVE upstream, ignoring: %m"); + } else if (strv_contains(tags, "FDSTORE=1")) { + if (fdset_isempty(fds)) { + log_debug("Got FDSTORE=1 from container payload without any attached file descriptors, ignoring."); + return 0; + } + + _cleanup_free_ int *fds_array = NULL; + int n; + + n = fdset_to_array(fds, &fds_array); + if (n < 0) + return log_warning_errno(n, "Failed to convert fdset to array, ignoring FDSTORE forward: %m"); + + const char *fdname = strv_find_startswith(tags, "FDNAME="); + bool fdpoll_off = strv_contains(tags, "FDPOLL=0"); + _cleanup_free_ char *msg = NULL; + unsigned n_fds = (unsigned) n; + + if (fdname && !fdname_is_valid(fdname)) { + log_warning("Got FDSTORE=1 from container payload with invalid FDNAME='%s', ignoring name.", fdname); + fdname = NULL; + } + + if (asprintf(&msg, "FDSTORE=1\nFDNAME=payload-%s%s%s", + fdname ?: "stored", + fdpoll_off ? "\nFDPOLL=" : "", + fdpoll_off ? "0" : "") < 0) + return log_oom(); + + r = sd_pid_notify_with_fds( + 0, + /* unset_environment= */ false, + msg, + fds_array, + n_fds); + if (r < 0) + return log_warning_errno(r, "Failed to forward FDSTORE upstream, ignoring: %m"); + } + + return 0; +} + static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { PidRef *inner_child_pid = ASSERT_PTR(userdata); int r; @@ -4621,7 +4695,8 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL; _cleanup_strv_free_ char **tags = NULL; - r = notify_recv_strv(fd, &tags, /* ret_ucred= */ NULL, &sender_pid); + _cleanup_(fdset_freep) FDSet *fds = NULL; + r = notify_recv_with_fds_strv(fd, &tags, /* ret_ucred= */ NULL, &sender_pid, &fds); if (r == -EAGAIN) return 0; if (r < 0) @@ -4656,6 +4731,8 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r (void) sd_notifyf(/* unset_environment= */ false, "STATUS=Container running."); } + (void) forward_fd_store(tags, fds); + return 0; } diff --git a/src/test/meson.build b/src/test/meson.build index faf075c10326e..587a5c6159cd0 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -323,6 +323,11 @@ executables += [ 'sources' : files('test-fd-util.c'), 'dependencies' : libseccomp_cflags, }, + test_template + { + 'sources' : files('test-fdstore.c'), + 'link_with' : libsystemd, + 'type' : 'manual', + }, test_template + { 'sources' : files( 'test-hashmap.c', diff --git a/src/test/test-fdstore.c b/src/test/test-fdstore.c new file mode 100644 index 0000000000000..6469d396e1cea --- /dev/null +++ b/src/test/test-fdstore.c @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* In 'store' mode pushes a couple of memfds with known content into the supervisor's fd store via FDSTORE=1 + * sd_notify() messages. In 'check' mode reads back the fds passed via LISTEN_FDS and verifies the content + * matches what was pushed. + * + * This binary is intentionally linked against libsystemd only so that it can go in the minimal image. */ + +#include +#include +#include +#include +#include +#include + +#include "sd-daemon.h" + +#define DATA_A "fdstore-data-a" +#define DATA_B "fdstore-data-b" + +#define _cleanup_(f) __attribute__((cleanup(f))) + +static void closep(int *fd) { + if (!fd || *fd < 0) + return; + + close(*fd); + *fd = -EBADF; +} + +static int push_one(const char *fdname, const char *content) { + _cleanup_(closep) int fd = -EBADF; + int r; + + assert(fdname); + assert(content); + + fd = memfd_create(fdname, MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) { + fprintf(stderr, "memfd_create(%s) failed: %m\n", fdname); + return -errno; + } + + size_t len = strlen(content); + if (write(fd, content, len) != (ssize_t) len) { + fprintf(stderr, "write(%s) failed: %m\n", fdname); + return -errno; + } + + char msg[256]; + r = snprintf(msg, sizeof(msg), "FDSTORE=1\nFDNAME=%s", fdname); + if (r < 0 || (size_t) r >= sizeof(msg)) { + if (r >= 0) + errno = ENOBUFS; + fprintf(stderr, "FDSTORE message for fdname=%s did not fit in buffer\n", fdname); + return -errno; + } + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ 0, msg, &fd, 1); + if (r < 0) { + errno = -r; + fprintf(stderr, "sd_pid_notify_with_fds(%s) failed: %m\n", fdname); + return r; + } + if (r == 0) { + fprintf(stderr, "NOTIFY_SOCKET not set\n"); + return -ENOENT; + } + + return 0; +} + +static int do_store(void) { + int r; + + if (push_one("test-fd-a", DATA_A) < 0) + return EXIT_FAILURE; + + if (push_one("test-fd-b", DATA_B) < 0) + return EXIT_FAILURE; + + /* Wait for our supervisor to actually process the FDSTORE messages before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone. */ + r = sd_notify_barrier(0, 5 * 1000 * 1000); + if (r < 0) { + errno = -r; + fprintf(stderr, "sd_notify_barrier failed: %m\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int do_check(void) { + bool seen_a = false, seen_b = false; + int n; + + n = sd_listen_fds(/* unset_environment= */ 0); + if (n < 0) { + errno = -n; + fprintf(stderr, "sd_listen_fds failed: %m\n"); + return EXIT_FAILURE; + } + if (n < 2) { + fprintf(stderr, "Expected at least 2 fds via LISTEN_FDS, got %d\n", n); + return EXIT_FAILURE; + } + + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; + char buf[256] = {}; + ssize_t k; + + if (lseek(fd, 0, SEEK_SET) < 0) { + fprintf(stderr, "lseek(fd=%d) failed: %m\n", fd); + return EXIT_FAILURE; + } + k = read(fd, buf, sizeof(buf) - 1); + if (k < 0) { + fprintf(stderr, "read(fd=%d) failed: %m\n", fd); + return EXIT_FAILURE; + } + buf[k] = 0; + + if (strcmp(buf, DATA_A) == 0) + seen_a = true; + else if (strcmp(buf, DATA_B) == 0) + seen_b = true; + else + fprintf(stderr, "Unexpected fd content: '%s'\n", buf); + } + + if (!seen_a || !seen_b) { + fprintf(stderr, "Missing expected fds: seen_a=%d seen_b=%d\n", seen_a, seen_b); + return EXIT_FAILURE; + } + + printf("Payload received both preserved fds with matching content.\n"); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + int r; + + if (argc < 2) { + fprintf(stderr, "Usage: %s store|check\n", argv[0]); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "store") == 0) + r = do_store(); + else if (strcmp(argv[1], "check") == 0) + r = do_check(); + else { + fprintf(stderr, "Unknown verb: %s\n", argv[1]); + return EXIT_FAILURE; + } + + if (r != EXIT_SUCCESS) + return r; + + /* On success, become sleep so if we are a container payload it can stay alive. */ + execlp("sleep", "sleep", "infinity", (char *) NULL); + fprintf(stderr, "execlp(sleep) failed: %m\n"); + return EXIT_FAILURE; +} diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index e0516449c70b0..19b1d445c89d9 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -22,14 +22,17 @@ at_exit() { rm -rf /home/testuser/.local/state/machines/inodetest ||: rm -rf /home/testuser/.local/state/machines/inodetest2 ||: rm -rf /home/testuser/.local/state/machines/mangletest ||: + rm -rf /home/testuser/.local/state/machines/fdstore ||: machinectl terminate zurps ||: machinectl terminate exfiltrate ||: systemctl --user --machine testuser@ stop exfiltrate.service ||: + systemctl --user --machine testuser@ stop systemd-nspawn@fdstore.service ||: rm -f /etc/polkit-1/rules.d/registermachinetest.rules machinectl terminate nurps ||: machinectl terminate kurps ||: machinectl terminate wumms ||: machinectl terminate wamms ||: + machinectl terminate fdstore ||: rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules rm -rf /var/tmp/mangletest rm -f /var/tmp/mangletest.tar.gz @@ -307,4 +310,85 @@ tar -C /var/tmp/mangletest/ -cvzf /var/tmp/mangletest.tar.gz mangletest-0.1 run0 --pipe -u testuser importctl -m --user import-tar /var/tmp/mangletest.tar.gz cmp /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release /home/testuser/.local/state/machines/mangletest/usr/lib/os-release +# Verify the fd-store preservation chain works end-to-end across: +# payload (inside container) -> systemd-nspawn (user manager) -> user manager +# -> system PID 1 (user@.service fd store) +# Then restart the nspawn service and verify the inner payload actually +# receives the preserved fds back via LISTEN_FDS, with their original content. +create_dummy_container /home/testuser/.local/state/machines/fdstore +# The container init execs the helper directly so the FDSTORE notification is +# sent from PID 1 (nspawn rejects notify messages from anyone but the inner +# payload's init). The helper itself execs sleep on success to keep the +# container alive, and on failure it exits non-zero making the systemd-nspawn +# service fail. +cat >/home/testuser/.local/state/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +if [[ "${LISTEN_FDS:-0}" -gt 0 ]]; then + exec /usr/bin/test-fdstore check +else + exec /usr/bin/test-fdstore store +fi +EOF +chmod +x /home/testuser/.local/state/machines/fdstore/sbin/init +systemd-dissect --shift /home/testuser/.local/state/machines/fdstore foreign + +run0 -u testuser mkdir -p .config/systemd/nspawn/ +run0 -u testuser -i "cat >.config/systemd/nspawn/fdstore.nspawn <.config/systemd/user/systemd-nspawn@fdstore.service.d/fdstore.conf < nspawn (user-side systemd-nspawn@fdstore.service fd store) +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 + +# 2) nspawn -> user manager -> system PID 1 (user@.service fd store) +TESTUSER_UID=$(id -u testuser) +timeout 30s bash -c \ + "until [[ \"\$(systemctl show -P NFileDescriptorStore user@${TESTUSER_UID}.service)\" -ge 2 ]]; do sleep 0.5; done" +n_user_at_fds=$(systemctl show -P NFileDescriptorStore "user@${TESTUSER_UID}.service") +test "${n_user_at_fds}" -ge 2 + +# 3) Stop the nspawn service: payload is gone but FileDescriptorStorePreserve=yes +# must keep the fds in the user-side fdstore (and propagated copy in PID 1). +run0 -u testuser systemctl --user stop systemd-nspawn@fdstore.service +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 + +# 4) Restart the service: nspawn must receive the preserved fds via LISTEN_FDS +# and forward them into the inner payload, which verifies the content matches. +run0 -u testuser systemctl start --user systemd-nspawn@fdstore.service +run0 -u testuser systemctl is-active --user systemd-nspawn@fdstore.service + +# 5) Stop the nspawn service and the user session +run0 -u testuser systemctl --user stop systemd-nspawn@fdstore.service +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 +systemctl stop "user@${TESTUSER_UID}.service" +n_user_at_fds=$(systemctl show -P NFileDescriptorStore "user@${TESTUSER_UID}.service") +test "${n_user_at_fds}" -ge 2 + +# 6) Restart the user session and container payload +systemctl start "user@${TESTUSER_UID}.service" +timeout 30s bash -c \ + "until systemctl is-active 'user@${TESTUSER_UID}.service' >/dev/null; do sleep 0.5; done" +run0 -u testuser systemctl --user start systemd-nspawn@fdstore.service +run0 -u testuser systemctl is-active --user systemd-nspawn@fdstore.service + +run0 -u testuser systemctl --user stop systemd-nspawn@fdstore.service +machinectl terminate fdstore 2>/dev/null || true + loginctl disable-linger testuser From bc1c56543f57042c808b85f3c2313bac89346b09 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 30 Mar 2026 15:22:40 +0100 Subject: [PATCH 1742/2155] Import linux/liveupdate.h UAPI header From kernel 7.0 --- src/include/uapi/linux/liveupdate.h | 216 ++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/include/uapi/linux/liveupdate.h diff --git a/src/include/uapi/linux/liveupdate.h b/src/include/uapi/linux/liveupdate.h new file mode 100644 index 0000000000000..30bc66ee9436a --- /dev/null +++ b/src/include/uapi/linux/liveupdate.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +/* + * Userspace interface for /dev/liveupdate + * Live Update Orchestrator + * + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +#ifndef _UAPI_LIVEUPDATE_H +#define _UAPI_LIVEUPDATE_H + +#include +#include + +/** + * DOC: General ioctl format + * + * The ioctl interface follows a general format to allow for extensibility. Each + * ioctl is passed in a structure pointer as the argument providing the size of + * the structure in the first u32. The kernel checks that any structure space + * beyond what it understands is 0. This allows userspace to use the backward + * compatible portion while consistently using the newer, larger, structures. + * + * ioctls use a standard meaning for common errnos: + * + * - ENOTTY: The IOCTL number itself is not supported at all + * - E2BIG: The IOCTL number is supported, but the provided structure has + * non-zero in a part the kernel does not understand. + * - EOPNOTSUPP: The IOCTL number is supported, and the structure is + * understood, however a known field has a value the kernel does not + * understand or support. + * - EINVAL: Everything about the IOCTL was understood, but a field is not + * correct. + * - ENOENT: A provided token does not exist. + * - ENOMEM: Out of memory. + * - EOVERFLOW: Mathematics overflowed. + * + * As well as additional errnos, within specific ioctls. + */ + +/* The ioctl type, documented in ioctl-number.rst */ +#define LIVEUPDATE_IOCTL_TYPE 0xBA + +/* The maximum length of session name including null termination */ +#define LIVEUPDATE_SESSION_NAME_LENGTH 64 + +/* The /dev/liveupdate ioctl commands */ +enum { + LIVEUPDATE_CMD_BASE = 0x00, + LIVEUPDATE_CMD_CREATE_SESSION = LIVEUPDATE_CMD_BASE, + LIVEUPDATE_CMD_RETRIEVE_SESSION = 0x01, +}; + +/* ioctl commands for session file descriptors */ +enum { + LIVEUPDATE_CMD_SESSION_BASE = 0x40, + LIVEUPDATE_CMD_SESSION_PRESERVE_FD = LIVEUPDATE_CMD_SESSION_BASE, + LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41, + LIVEUPDATE_CMD_SESSION_FINISH = 0x42, +}; + +/** + * struct liveupdate_ioctl_create_session - ioctl(LIVEUPDATE_IOCTL_CREATE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_create_session) + * @fd: Output; The new file descriptor for the created session. + * @name: Input; A null-terminated string for the session name, max + * length %LIVEUPDATE_SESSION_NAME_LENGTH including termination + * character. + * + * Creates a new live update session for managing preserved resources. + * This ioctl can only be called on the main /dev/liveupdate device. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_ioctl_create_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_CREATE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_CREATE_SESSION) + +/** + * struct liveupdate_ioctl_retrieve_session - ioctl(LIVEUPDATE_IOCTL_RETRIEVE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_retrieve_session) + * @fd: Output; The new file descriptor for the retrieved session. + * @name: Input; A null-terminated string identifying the session to retrieve. + * The name must exactly match the name used when the session was + * created in the previous kernel. + * + * Retrieves a handle (a new file descriptor) for a preserved session by its + * name. This is the primary mechanism for a userspace agent to regain control + * of its preserved resources after a live update. + * + * The userspace application provides the null-terminated `name` of a session + * it created before the live update. If a preserved session with a matching + * name is found, the kernel instantiates it and returns a new file descriptor + * in the `fd` field. This new session FD can then be used for all file-specific + * operations, such as restoring individual file descriptors with + * LIVEUPDATE_SESSION_RETRIEVE_FD. + * + * It is the responsibility of the userspace application to know the names of + * the sessions it needs to retrieve. If no session with the given name is + * found, the ioctl will fail with -ENOENT. + * + * This ioctl can only be called on the main /dev/liveupdate device when the + * system is in the LIVEUPDATE_STATE_UPDATED state. + */ +struct liveupdate_ioctl_retrieve_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_RETRIEVE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RETRIEVE_SESSION) + +/* Session specific IOCTLs */ + +/** + * struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD) + * @size: Input; sizeof(struct liveupdate_session_preserve_fd) + * @fd: Input; The user-space file descriptor to be preserved. + * @token: Input; An opaque, unique token for preserved resource. + * + * Holds parameters for preserving a file descriptor. + * + * User sets the @fd field identifying the file descriptor to preserve + * (e.g., memfd, kvm, iommufd, VFIO). The kernel validates if this FD type + * and its dependencies are supported for preservation. If validation passes, + * the kernel marks the FD internally and *initiates the process* of preparing + * its state for saving. The actual snapshotting of the state typically occurs + * during the subsequent %LIVEUPDATE_IOCTL_PREPARE execution phase, though + * some finalization might occur during freeze. + * On successful validation and initiation, the kernel uses the @token + * field with an opaque identifier representing the resource being preserved. + * This token confirms the FD is targeted for preservation and is required for + * the subsequent %LIVEUPDATE_SESSION_RETRIEVE_FD call after the live update. + * + * Return: 0 on success (validation passed, preservation initiated), negative + * error code on failure (e.g., unsupported FD type, dependency issue, + * validation failed). + */ +struct liveupdate_session_preserve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_PRESERVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_PRESERVE_FD) + +/** + * struct liveupdate_session_retrieve_fd - ioctl(LIVEUPDATE_SESSION_RETRIEVE_FD) + * @size: Input; sizeof(struct liveupdate_session_retrieve_fd) + * @fd: Output; The new file descriptor representing the fully restored + * kernel resource. + * @token: Input; An opaque, token that was used to preserve the resource. + * + * Retrieve a previously preserved file descriptor. + * + * User sets the @token field to the value obtained from a successful + * %LIVEUPDATE_IOCTL_FD_PRESERVE call before the live update. On success, + * the kernel restores the state (saved during the PREPARE/FREEZE phases) + * associated with the token and populates the @fd field with a new file + * descriptor referencing the restored resource in the current (new) kernel. + * This operation must be performed *before* signaling completion via + * %LIVEUPDATE_IOCTL_FINISH. + * + * Return: 0 on success, negative error code on failure (e.g., invalid token). + */ +struct liveupdate_session_retrieve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_RETRIEVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_RETRIEVE_FD) + +/** + * struct liveupdate_session_finish - ioctl(LIVEUPDATE_SESSION_FINISH) + * @size: Input; sizeof(struct liveupdate_session_finish) + * @reserved: Input; Must be zero. Reserved for future use. + * + * Signals the completion of the restoration process for a retrieved session. + * This is the final operation that should be performed on a session file + * descriptor after a live update. + * + * This ioctl must be called once all required file descriptors for the session + * have been successfully retrieved (using %LIVEUPDATE_SESSION_RETRIEVE_FD) and + * are fully restored from the userspace and kernel perspective. + * + * Upon success, the kernel releases its ownership of the preserved resources + * associated with this session. This allows internal resources to be freed, + * typically by decrementing reference counts on the underlying preserved + * objects. + * + * If this operation fails, the resources remain preserved in memory. Userspace + * may attempt to call finish again. The resources will otherwise be reset + * during the next live update cycle. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_session_finish { + __u32 size; + __u32 reserved; +}; + +#define LIVEUPDATE_SESSION_FINISH \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FINISH) + +#endif /* _UAPI_LIVEUPDATE_H */ From 79ac52b810766f2cc6557b67a01a6ca089b6ca09 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 30 Mar 2026 15:43:29 +0100 Subject: [PATCH 1743/2155] Add helpers for talking to /dev/liveupdate This character device is the main kernel interface to deal with the Live Update Orchestrator (LUO) feature. Add some basic wrappers for the ioctls. https://docs.kernel.org/userspace-api/liveupdate.html --- src/shared/luo-util.c | 105 +++++++++++++++++++++++++++++++++++++++++ src/shared/luo-util.h | 11 +++++ src/shared/meson.build | 1 + 3 files changed, 117 insertions(+) create mode 100644 src/shared/luo-util.c create mode 100644 src/shared/luo-util.h diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c new file mode 100644 index 0000000000000..12f5dbc282249 --- /dev/null +++ b/src/shared/luo-util.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "luo-util.h" +#include "string-util.h" + +/* Kernel API defined at https://docs.kernel.org/userspace-api/liveupdate.html The /dev/liveupdate is a + * single-owner singleton, only a single process at any given time can open it. Callers can create named + * "sessions", and then add FDs to them. The session name can be used to retrieve the session after reboot. + * To identify an FD, a 64bit token (what we would call an 'index' in our codebase) is passed in, and the + * caller is responsible for coming up with the token and tracking them. */ + +int luo_open_device(void) { + return RET_NERRNO(open("/dev/liveupdate", O_RDWR|O_CLOEXEC)); +} + +int luo_create_session(int device_fd, const char *name) { + struct liveupdate_ioctl_create_session args = { + .size = sizeof(args), + .fd = -EBADF, + }; + + assert(device_fd >= 0); + assert(name); + + if (strlen(name) >= sizeof(args.name)) + return -ENAMETOOLONG; + + strncpy_exact((char *) args.name, name, sizeof(args.name)); + + if (ioctl(device_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args) < 0) + return -errno; + + return args.fd; +} + +int luo_retrieve_session(int device_fd, const char *name) { + struct liveupdate_ioctl_retrieve_session args = { + .size = sizeof(args), + .fd = -EBADF, + }; + + assert(device_fd >= 0); + assert(name); + + if (strlen(name) >= sizeof(args.name)) + return -ENAMETOOLONG; + + strncpy_exact((char *) args.name, name, sizeof(args.name)); + + if (ioctl(device_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &args) < 0) + return -errno; + + return args.fd; +} + +int luo_session_preserve_fd(int session_fd, int fd, uint64_t token) { + struct liveupdate_session_preserve_fd args = { + .size = sizeof(args), + .fd = fd, + .token = token, + }; + + assert(session_fd >= 0); + assert(fd >= 0); + + return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args)); +} + +int luo_session_retrieve_fd(int session_fd, uint64_t token) { + struct liveupdate_session_retrieve_fd args = { + .size = sizeof(args), + .fd = -EBADF, + .token = token, + }; + int r; + + assert(session_fd >= 0); + + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &args) < 0) + return -errno; + + r = fd_cloexec(args.fd, true); + if (r < 0) { + safe_close(args.fd); + return r; + } + + return args.fd; +} + +int luo_session_finish(int session_fd) { + struct liveupdate_session_finish args = { + .size = sizeof(args), + }; + + assert(session_fd >= 0); + + return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &args)); +} diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h new file mode 100644 index 0000000000000..8ff70c17e0d31 --- /dev/null +++ b/src/shared/luo-util.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "basic-forward.h" + +int luo_open_device(void); +int luo_create_session(int device_fd, const char *name); +int luo_retrieve_session(int device_fd, const char *name); +int luo_session_preserve_fd(int session_fd, int fd, uint64_t token); +int luo_session_retrieve_fd(int session_fd, uint64_t token); +int luo_session_finish(int session_fd); diff --git a/src/shared/meson.build b/src/shared/meson.build index d4cd5ca431233..9b539f79839d9 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -129,6 +129,7 @@ shared_sources = files( 'loop-util.c', 'loopback-setup.c', 'lsm-util.c', + 'luo-util.c', 'machine-bind-user.c', 'machine-credential.c', 'machine-id-setup.c', From 257c35c1a3936f53b80f16397a6909f4cd81124d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 1 May 2026 14:25:11 +0100 Subject: [PATCH 1744/2155] core: support FD Store preservation through kexec via LUO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel Live Update Orchestrator (LUO) exposes /dev/liveupdate, which allows userspace to hand a set of "preservable" kernel objects to the new kernel across a kexec-based reboot. For now it only supports memfds, with more object types (virtio devices, etc.) expected to be added later. This is a natural fit for systemd's FD Store feature: services hand memfds (containing serialized state or other service data) to PID 1 via FDSTORE=1 sd_notify() messages, and get them back on their next start. Today this works across service restarts, soft reboots and initrd→rootfs transitions. With LUO we can extend the same mechanism to work across kexec, too. The protocol on the PID 1 side works roughly as follows: * All preservable fds are collected into a single LUO session named "systemd". Each FD gets uploaded with a token. Token 0 in that session is reserved for a "mapping" memfd, which carries a JSON object describing how to dispatch the other tokens back to units on the next boot: { "foo.service": [ { "type": "fd", "name": "stateA", "token": 1 }, { "type": "fd", "name": "stateB", "token": 2 } ], ... } unit IDs are used as the unit identifier, as they're stable across daemon-reexec, switch-root and kexec. token refers to the LUO token assigned to the object in the session. * On shutdown for MANAGER_KEXEC, just before manager_free(), systemd walks all services and serializes their persistent fd store contents (fds + FDNAMEs + unit IDs) into a JSON memfd. The FDs themselves are gathered into a FDSet to be kept around. The fdset and the serialization memfd are passed to systemd-shutdown via the SYSTEMD_LUO_SERIALIZE_FD environment variable providing the fd number, so the actual LUO session creation and ioctls can happen as the very last step before kexec (shutdown implementation is the next commit). * On boot, manager_luo_restore_fd_stores() opens /dev/liveupdate, tries to retrieve the "systemd" session, reads the mapping memfd, then for each entry retrieves the fd from the session and attempts to attach it to the matching unit's fd store. * The FDs are injected in the appropriate unit's FD stores using the same mechanism as the LISTEN_FDS propagation that was set up earlier. Non-kexec shutdown paths are unaffected: if MANAGER_KEXEC is not the final objective, no serialization file is produced and no LUO session is ever created. Likewise if /dev/liveupdate does not exist, nothing happens. --- src/core/luo.c | 252 ++++++++++++++++++++++++++++++++++++++++++ src/core/luo.h | 7 ++ src/core/main.c | 23 +++- src/core/manager.c | 5 + src/core/meson.build | 1 + src/shared/luo-util.h | 19 ++++ 6 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 src/core/luo.c create mode 100644 src/core/luo.h diff --git a/src/core/luo.c b/src/core/luo.c new file mode 100644 index 0000000000000..4b5632c6dbac0 --- /dev/null +++ b/src/core/luo.c @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "json-util.h" +#include "log.h" +#include "luo.h" +#include "luo-util.h" +#include "manager.h" +#include "serialize.h" +#include "service.h" +#include "unit.h" +#include "unit-name.h" + +static int luo_read_mapping(int session_fd, sd_json_variant **ret) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_close_ int mapping_fd = -EBADF; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(session_fd >= 0); + assert(ret); + + mapping_fd = luo_session_retrieve_fd(session_fd, LUO_MAPPING_INDEX); + if (mapping_fd < 0) + return log_warning_errno(mapping_fd, "Failed to retrieve LUO mapping fd (fd_index 0): %m"); + + r = fdopen_independent(mapping_fd, "r", &f); + if (r < 0) + return log_warning_errno(r, "Failed to open LUO mapping fd for reading: %m"); + + r = sd_json_parse_file(f, "luo-mapping", SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse LUO mapping JSON: %m"); + + *ret = TAKE_PTR(v); + return 0; +} + +static void luo_session_finishp(int *fd) { + assert(fd); + + if (*fd >= 0) + (void) luo_session_finish(*fd); + safe_close(*fd); +} + +int manager_luo_restore_fd_stores(Manager *m) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL; + _cleanup_close_ int device_fd = -EBADF; + _cleanup_(luo_session_finishp) int session_fd = -EBADF; + const char *unit_id; + sd_json_variant *fds_json; + int r, n_total = 0; + + assert(m); + + if (MANAGER_IS_USER(m)) + return 0; + + device_fd = luo_open_device(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { + log_debug_errno(device_fd, "No /dev/liveupdate device found, skipping LUO fd store restoration."); + return 0; + } + if (device_fd < 0) + return log_warning_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_retrieve_session(device_fd, LUO_SESSION_NAME); + if (session_fd == -ENOENT) { + log_debug("No LUO session '%s' found, skipping fd store restoration.", LUO_SESSION_NAME); + return 0; + } + if (session_fd < 0) + return log_warning_errno(session_fd, "Failed to retrieve LUO session '%s': %m", LUO_SESSION_NAME); + + log_debug("Found LUO session '%s', restoring fd stores.", LUO_SESSION_NAME); + + r = luo_read_mapping(session_fd, &mapping); + if (r < 0) + return r; + + /* Retrieve all fds from the session and dispatch each to the named unit, eagerly loading the + * unit if necessary. */ + JSON_VARIANT_OBJECT_FOREACH(unit_id, fds_json, mapping) { + sd_json_variant *entry; + + if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) { + log_warning("Invalid unit name '%s' in LUO mapping, skipping.", unit_id); + continue; + } + + if (!sd_json_variant_is_array(fds_json)) { + log_warning("LUO mapping for unit '%s' is not a JSON array, skipping.", unit_id); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { + struct { + const char *type; + const char *name; + uint64_t token; + } p = { + .token = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "token", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, token), 0 }, + {} + }; + + _cleanup_close_ int fd = -EBADF; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (streq(p.type, "fd")) { + if (p.token == UINT64_MAX) { + log_warning("LUO mapping for unit '%s' fd '%s': missing 'token' field.", unit_id, p.name); + continue; + } + if (p.token == LUO_MAPPING_INDEX) { + log_warning("LUO mapping for unit '%s' fd '%s': token %" PRIu64 " is reserved for the mapping memfd.", unit_id, p.name, p.token); + continue; + } + + fd = luo_session_retrieve_fd(session_fd, p.token); + if (fd < 0) { + log_warning_errno(fd, "Failed to retrieve LUO fd for unit '%s' name '%s' token %" PRIu64 ": %m", + unit_id, p.name, p.token); + continue; + } + } else { + log_warning("LUO mapping for unit '%s' fd '%s': unknown type '%s', skipping.", + unit_id, p.name, p.type); + continue; + } + + r = manager_dispatch_external_fd_to_unit(m, unit_id, p.name, /* index= */ 0, TAKE_FD(fd), "LUO"); + if (r > 0) + n_total++; + /* On error fd is already consumed by manager_dispatch_external_fd_to_unit. */ + } + } + + if (n_total > 0) + log_debug("Restored %d fd(s) total from LUO session.", n_total); + + return n_total; +} + +int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_fdset_free_ FDSet *fds = NULL; + Unit *u; + int r, n_serialized = 0; + + assert(m); + assert(ret_f); + assert(ret_fds); + + if (MANAGER_IS_USER(m)) { + *ret_f = NULL; + *ret_fds = NULL; + return 0; + } + + fds = fdset_new(); + if (!fds) + return log_oom(); + + /* Build a JSON object: { "unit_id": [ { "type": "fd", "name": "...", "fd": N }, ... ], ... } + * This is passed to systemd-shutdown which will create a LUO session and preserve the fds. */ + HASHMAP_FOREACH(u, m->units) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; + Service *s; + + if (u->type != UNIT_SERVICE) + continue; + + s = SERVICE(u); + + if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) + continue; + + if (!s->fd_store) + continue; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + int copy; + + copy = fdset_put_dup(fds, fs->fd); + if (copy < 0) + return log_error_errno(copy, "Failed to duplicate fd for LUO serialization: %m"); + + r = sd_json_variant_append_arraybo( + &entries, + SD_JSON_BUILD_PAIR_STRING("type", "fd"), + SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), + SD_JSON_BUILD_PAIR_INTEGER("fd", copy)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON for LUO serialization: %m"); + + n_serialized++; + } + + r = sd_json_variant_set_field(&root, u->id, entries); + if (r < 0) + return log_error_errno(r, "Failed to add unit to LUO serialization JSON: %m"); + } + + if (n_serialized == 0) { + log_debug("No fd store entries to serialize for LUO."); + *ret_f = NULL; + *ret_fds = NULL; + return 0; + } + + r = open_serialization_file("luo-fd-store", &f); + if (r < 0) + return log_error_errno(r, "Failed to create LUO serialization file: %m"); + + r = sd_json_variant_dump(root, /* flags= */ 0, f, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump LUO serialization JSON: %m"); + + r = finish_serialization_file(f); + if (r < 0) + return log_error_errno(r, "Failed to finish LUO serialization file: %m"); + + r = fd_cloexec(fileno(f), false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization: %m"); + + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization fds: %m"); + + log_info("Serialized %d fd store entries for LUO.", n_serialized); + + *ret_f = TAKE_PTR(f); + *ret_fds = TAKE_PTR(fds); + return n_serialized; +} diff --git a/src/core/luo.h b/src/core/luo.h new file mode 100644 index 0000000000000..314006c76b4db --- /dev/null +++ b/src/core/luo.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int manager_luo_restore_fd_stores(Manager *m); +int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds); diff --git a/src/core/main.c b/src/core/main.c index 8d5ab57e3df54..f98d0edb2d004 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -72,6 +72,7 @@ #include "load-fragment.h" #include "log.h" #include "loopback-setup.h" +#include "luo.h" #include "machine-id-setup.h" #include "main.h" #include "manager.h" @@ -164,6 +165,7 @@ static RestrictFileSystemAccess arg_restrict_filesystem_access; static nsec_t arg_timer_slack_nsec; static Set* arg_syscall_archs; static FILE* arg_serialization; +static FILE* arg_luo_serialization; static sd_id128_t arg_machine_id; static bool arg_machine_id_from_firmware = false; static EmergencyAction arg_cad_burst_action; @@ -1806,6 +1808,13 @@ static int become_shutdown(int objective, int retval) { if (arg_minimum_uptime_usec != USEC_INFINITY) (void) strv_extendf(&env_block, "MINIMUM_UPTIME_USEC=" USEC_FMT, arg_minimum_uptime_usec); + /* If we have a LUO serialization file, pass the fd to systemd-shutdown so it can + * preserve FD store entries across kexec via the kernel Live Update Orchestrator. */ + if (arg_luo_serialization) { + log_debug("Passing LUO serialization fd to systemd-shutdown."); + (void) strv_extendf(&env_block, "SYSTEMD_LUO_SERIALIZE_FD=%i", fileno(arg_luo_serialization)); + } + (void) write_boot_or_shutdown_osc("shutdown"); execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); @@ -3777,6 +3786,12 @@ int main(int argc, char *argv[]) { if (m) { arg_reboot_watchdog = manager_get_watchdog(m, WATCHDOG_REBOOT); arg_kexec_watchdog = manager_get_watchdog(m, WATCHDOG_KEXEC); + + /* For kexec, serialize fd stores now. Services have stopped and sent + * their FDs to the store, but the manager (and its fd stores) is still alive. */ + if (r == MANAGER_KEXEC) + (void) manager_luo_serialize_fd_stores(m, &arg_luo_serialization, &fds); + m = manager_free(m); } @@ -3794,7 +3809,13 @@ int main(int argc, char *argv[]) { &error_message); /* This only returns if reexecution failed */ arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); + + /* For kexec, the FDSet and LUO serialization file must survive until become_shutdown() calls + * execve() (CLOEXEC is already cleared on these FDs). For all other paths, free them now. */ + if (r != MANAGER_KEXEC) { + fds = fdset_free(fds); + arg_luo_serialization = safe_fclose(arg_luo_serialization); + } saved_env = strv_free(saved_env); diff --git a/src/core/manager.c b/src/core/manager.c index 1bd35a7d21b53..accf9c8ff94f1 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -57,6 +57,7 @@ #include "libaudit-util.h" #include "locale-setup.h" #include "log.h" +#include "luo.h" #include "manager-dump.h" #include "manager-serialize.h" #include "manager.h" @@ -2191,6 +2192,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_ if (m->previous_objective == MANAGER_SOFT_REBOOT) m->soft_reboots_count++; + /* If a LUO (Live Update Orchestrator) session from a previous kexec is available, restore + * preserved file descriptors into the appropriate service fd stores now, before coldplug. */ + (void) manager_luo_restore_fd_stores(m); + /* Pick up fds passed via the LISTEN_FDS=/LISTEN_FDNAMES= protocol that are tagged with a * unit id ("unit-id|fdname"), and route them into the matching unit's fd store. Untagged * fds remain in 'fds' and are handed to socket units below as before. */ diff --git a/src/core/meson.build b/src/core/meson.build index 98cf02aef8879..2bd8170c6a2eb 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -43,6 +43,7 @@ libcore_sources = files( 'kill.c', 'load-dropin.c', 'load-fragment.c', + 'luo.c', 'manager-dump.c', 'manager-serialize.c', 'manager.c', diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index 8ff70c17e0d31..a4f9a01223a1b 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -3,6 +3,25 @@ #include "basic-forward.h" +#define LUO_SESSION_NAME "systemd" + +/* Index (token) 0 in the LUO session is always the mapping memfd, which contains a JSON document mapping + * unit ids to arrays of fd store entries: + * + * { + * "unit-name.service": [ + * { "type": "fd", "name": "fdname1", "token": 1 }, + * { "type": "fd", "name": "fdname2", "token": 2 }, + * ], + * "other-unit.service": [ + * { "type": "fd", "name": "stored", "token": 3 } + * ] + * } + * + * type=fd: the fd was preserved in the "systemd" LUO session with the given token. + */ +#define LUO_MAPPING_INDEX UINT64_C(0) + int luo_open_device(void); int luo_create_session(int device_fd, const char *name); int luo_retrieve_session(int device_fd, const char *name); From 3dddbee290105aca53488f0d3c1202feee838675 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 31 Mar 2026 00:29:19 +0100 Subject: [PATCH 1745/2155] shutdown: prepare LUO session for FD Stores before kexec Wires up the systemd-shutdown side of the kexec-via-LUO fd store preservation. When rebooting via kexec, systemd builds a JSON description of the fd stores of all loaded services and passes it to systemd-shutdown through the SYSTEMD_LUO_SERIALIZE_FD environment variable. The FDs themselves come in as part of the normal shutdown FDSet. systemd-shutdown's job is then, at the very last moment before invoking the kexec syscall, to move that state into a kernel LUO session so it survives the reboot. Doing the LUO session creation here, rather than in PID 1, is deliberate: * It's the last point where we can be sure all other processes have already been killed, so nothing else can race us into creating (or worse, hijacking) the "systemd" session, as /dev/liveupdate is a singleton and a session name is global. * Any kernel-visible side effects of preserving objects (memory pinning etc.) are delayed until the absolute last moment, minimizing the window in which they could affect the running system No behaviour change for shutdown paths other than kexec, or for kexec when systemd didn't hand over a serialization fd (e.g. because no service had any fds stored, or because LUO wasn't supported at serialization time). --- src/shared/luo-util.c | 208 ++++++++++++++++++++++++++++++++++++++++ src/shared/luo-util.h | 4 + src/shutdown/shutdown.c | 17 +++- 3 files changed, 227 insertions(+), 2 deletions(-) diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c index 12f5dbc282249..c1a579a0a00f5 100644 --- a/src/shared/luo-util.c +++ b/src/shared/luo-util.c @@ -4,9 +4,17 @@ #include #include +#include "sd-json.h" + +#include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fileio.h" +#include "json-util.h" +#include "log.h" #include "luo-util.h" +#include "memfd-util.h" +#include "parse-util.h" #include "string-util.h" /* Kernel API defined at https://docs.kernel.org/userspace-api/liveupdate.html The /dev/liveupdate is a @@ -103,3 +111,203 @@ int luo_session_finish(int session_fd) { return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &args)); } + +int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_free_ int *fd_list = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t n_fds = 0; + int serialize_fd = -EBADF, r; + + assert(ret); + assert(ret_fds); + assert(ret_n_fds); + + const char *luo_fd_str = secure_getenv("SYSTEMD_LUO_SERIALIZE_FD"); + if (!luo_fd_str) { + *ret = NULL; + *ret_fds = NULL; + *ret_n_fds = 0; + return 0; + } + + serialize_fd = parse_fd(luo_fd_str); + if (serialize_fd < 0) + return log_warning_errno(serialize_fd, + "Failed to parse SYSTEMD_LUO_SERIALIZE_FD='%s': %m", luo_fd_str); + + r = fdopen_independent(serialize_fd, "r", &f); + if (r < 0) + return log_warning_errno(r, "Failed to open LUO serialization fd %d: %m", serialize_fd); + + r = sd_json_parse_file(f, /* path= */ NULL, SD_JSON_PARSE_MUST_BE_OBJECT, &root, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse LUO serialization JSON: %m"); + + /* Collect all fd numbers referenced in the JSON (plus the serialization fd itself) + * so the caller can protect them from close_all_fds(). */ + const char *unit_id _unused_; + sd_json_variant *unit_entries; + + JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_entries, root) { + sd_json_variant *entry; + + JSON_VARIANT_ARRAY_FOREACH(entry, unit_entries) { + struct { + int fd; + } p = { + .fd = -EBADF, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fd", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd), 0 }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (p.fd < 0) + continue; + + if (!GREEDY_REALLOC(fd_list, n_fds + 1)) + return log_oom(); + + fd_list[n_fds++] = p.fd; + } + } + + /* Also protect the serialization fd itself */ + if (!GREEDY_REALLOC(fd_list, n_fds + 1)) + return log_oom(); + fd_list[n_fds++] = serialize_fd; + + log_debug("Parsed LUO serialization with %zu fd(s) to preserve.", n_fds); + + *ret = TAKE_PTR(root); + *ret_fds = TAKE_PTR(fd_list); + *ret_n_fds = n_fds; + return 0; +} + +int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) { + _cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL; + const char *unit_id; + sd_json_variant *entries; + uint64_t token = LUO_MAPPING_INDEX + 1; + int r; + + assert(ret_session_fd); + + if (!serialization) { + *ret_session_fd = -EBADF; + return 0; /* No LUO serialization, nothing to preserve */ + } + + device_fd = luo_open_device(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { + *ret_session_fd = -EBADF; + return 0; /* LUO not supported, ignore */ + } + if (device_fd < 0) + return log_error_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_create_session(device_fd, LUO_SESSION_NAME); + if (session_fd < 0) + return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", LUO_SESSION_NAME); + + /* Build the mapping JSON for the new kernel's PID 1 and preserve each fd. + * JSON format: { "unit_id": [ {"type": "fd", "name": "...", "token": N}, ... ], ... } + * + * For regular fds: type=fd, preserved in the systemd session with the given LUO token. */ + JSON_VARIANT_OBJECT_FOREACH(unit_id, entries, serialization) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fd_list = NULL; + sd_json_variant *entry; + + JSON_VARIANT_ARRAY_FOREACH(entry, entries) { + struct { + const char *type; + const char *name; + int fd; + const char *session_name; + } p = { + .fd = -EBADF, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "fd", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd), 0 }, + { "sessionName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, session_name), 0 }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (streq(p.type, "fd")) { + if (p.fd < 0) { + log_warning("LUO mapping for unit '%s' fd '%s': missing or negative fd, skipping.", unit_id, p.name); + continue; + } + + /* Regular fd, preserve in the systemd session */ + r = luo_session_preserve_fd(session_fd, p.fd, token); + if (r < 0) { + log_warning_errno(r, "Failed to preserve LUO fd %i (name '%s') with token %" PRIu64 ", will be lost across kexec: %m", p.fd, p.name, token); + continue; + } + + r = sd_json_variant_append_arraybo( + &fd_list, + SD_JSON_BUILD_PAIR_STRING("type", "fd"), + SD_JSON_BUILD_PAIR_STRING("name", p.name), + SD_JSON_BUILD_PAIR_UNSIGNED("token", token)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO mapping: %m"); + + ++token; + } else + log_warning("Unknown fd type '%s' for unit '%s' fd '%s', skipping.", p.type, unit_id, p.name); + } + + if (fd_list) { + r = sd_json_variant_set_field(&mapping, unit_id, fd_list); + if (r < 0) + return log_error_errno(r, "Failed to add unit to LUO mapping: %m"); + } + } + + if (!mapping) { + log_debug("No fds were preserved in LUO session."); + *ret_session_fd = -EBADF; + return 0; + } + + /* Store the mapping as a memfd at LUO token 0 */ + _cleanup_free_ char *mapping_text = NULL; + + r = sd_json_variant_format(mapping, /* flags= */ 0, &mapping_text); + if (r < 0) + return log_error_errno(r, "Failed to format LUO mapping JSON: %m"); + + _cleanup_close_ int mapping_fd = -EBADF; + + mapping_fd = memfd_new_and_seal_string("luo-mapping", mapping_text); + if (mapping_fd < 0) + return log_error_errno(mapping_fd, "Failed to create LUO mapping memfd: %m"); + + r = luo_session_preserve_fd(session_fd, mapping_fd, LUO_MAPPING_INDEX); + if (r < 0) + return log_error_errno(r, "Failed to preserve LUO mapping memfd: %m"); + + log_info("Preserved fd stores in LUO session '%s' for kexec.", LUO_SESSION_NAME); + + /* Return the session fd to the caller as it must stay open until the kexec syscall, + * otherwise the kernel discards the session. */ + *ret_session_fd = TAKE_FD(session_fd); + return 1; +} diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index a4f9a01223a1b..9f6c061910ae0 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -2,6 +2,7 @@ #pragma once #include "basic-forward.h" +#include "sd-forward.h" #define LUO_SESSION_NAME "systemd" @@ -28,3 +29,6 @@ int luo_retrieve_session(int device_fd, const char *name); int luo_session_preserve_fd(int session_fd, int fd, uint64_t token); int luo_session_retrieve_fd(int session_fd, uint64_t token); int luo_session_finish(int session_fd); + +int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds); +int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index c3c114a6674c4..c310d5c98144a 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -10,6 +10,7 @@ #include #include "sd-daemon.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" @@ -31,6 +32,7 @@ #include "initrd-util.h" #include "killall.h" #include "log.h" +#include "luo-util.h" #include "options.h" #include "parse-util.h" #include "pidref.h" @@ -363,14 +365,21 @@ int main(int argc, char *argv[]) { SYSTEM_SHUTDOWN_PATH, NULL }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *luo_serialization = NULL; + _cleanup_close_ int luo_session_fd = -EBADF; + _cleanup_free_ int *luo_fds = NULL; _cleanup_free_ char *cgroup = NULL; + size_t n_luo_fds = 0; int cmd, r; + /* If PID 1 passed us an LUO serialization fd, parse it first so we know which fds to keep open. */ + (void) luo_parse_serialization(&luo_serialization, &luo_fds, &n_luo_fds); + /* Close random fds we might have get passed, just for paranoia, before we open any new fds, for * example for logging. After all this tool's purpose is about detaching any pinned resources, and * open file descriptors are the primary way to pin resources. Note that we don't really expect any - * fds to be passed here. */ - (void) close_all_fds(NULL, 0); + * fds to be passed here, except for LUO fds that need to survive until kexec. */ + (void) close_all_fds(luo_fds, n_luo_fds); /* The log target defaults to console, but the original systemd process will pass its log target in through a * command line argument, which will override this default. Also, ensure we'll never log to the journal or @@ -646,6 +655,10 @@ int main(int argc, char *argv[]) { case LINUX_REBOOT_CMD_KEXEC: if (!in_container) { + /* Preserve fd stores via the kernel Live Update Orchestrator before kexec. + * The session fd must stay open until the kexec syscall. */ + (void) luo_preserve_fd_stores(luo_serialization, &luo_session_fd); + log_info("Rebooting with kexec."); (void) kexec(); From 14f773ce5ead01d740b68770b8a9794257990f6d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 30 Mar 2026 23:14:38 +0100 Subject: [PATCH 1746/2155] test: add integration test for LUO --- src/test/meson.build | 4 + src/test/test-luo.c | 170 +++++++++++++++ .../TEST-91-LIVEUPDATE/meson.build | 21 ++ test/integration-tests/meson.build | 1 + test/units/TEST-91-LIVEUPDATE.sh | 199 ++++++++++++++++++ 5 files changed, 395 insertions(+) create mode 100644 src/test/test-luo.c create mode 100644 test/integration-tests/TEST-91-LIVEUPDATE/meson.build create mode 100755 test/units/TEST-91-LIVEUPDATE.sh diff --git a/src/test/meson.build b/src/test/meson.build index 587a5c6159cd0..20960d0b9587f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -369,6 +369,10 @@ executables += [ 'sources' : files('test-loopback.c'), 'dependencies' : common_test_dependencies, }, + test_template + { + 'sources' : files('test-luo.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-math-util.c'), 'dependencies' : libm, diff --git a/src/test/test-luo.c b/src/test/test-luo.c new file mode 100644 index 0000000000000..95ccc84b1a60b --- /dev/null +++ b/src/test/test-luo.c @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Helper for TEST-91-LIVEUPDATE: creates memfds and stores them in the fd store, + * or verifies that inherited fd store entries contain the expected content. + * + * Usage: + * test-luo store - create memfds with test data and push them to the fd store + * test-luo check - verify fd store content matches expectations + */ + +#include +#include +#include + +#include "sd-daemon.h" + +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +#define TEST_DATA_1 "liveupdate-test-data-1" +#define TEST_DATA_2 "liveupdate-test-data-2" + +static int do_store(void) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + int r; + + fd1 = memfd_new_and_seal("luo-test-1", TEST_DATA_1, strlen(TEST_DATA_1)); + if (fd1 < 0) + return log_error_errno(fd1, "Failed to create memfd 1: %m"); + + fd2 = memfd_new_and_seal("luo-test-2", TEST_DATA_2, strlen(TEST_DATA_2)); + if (fd2 < 0) + return log_error_errno(fd2, "Failed to create memfd 2: %m"); + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ false, "FDSTORE=1\nFDNAME=testfd1", &fd1, 1); + if (r < 0) + return log_error_errno(r, "Failed to store memfd 1 in fd store: %m"); + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ false, "FDSTORE=1\nFDNAME=testfd2", &fd2, 1); + if (r < 0) + return log_error_errno(r, "Failed to store memfd 2 in fd store: %m"); + + log_info("Stored 2 memfds in fd store."); + + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for notification barrier: %m"); + + return 0; +} + +static int do_check(void) { + const char *e; + _cleanup_strv_free_ char **names = NULL; + size_t n_fds; + int r; + + /* sd_listen_fds_with_names() checks LISTEN_PID which won't match since we're a child process. + * Read LISTEN_FDS and LISTEN_FDNAMES directly from the environment instead. */ + e = getenv("LISTEN_FDS"); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDS environment variable set"); + + r = safe_atozu(e, &n_fds); + if (r < 0) + return log_error_errno(r, "Failed to parse LISTEN_FDS='%s': %m", e); + if (n_fds == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No file descriptors in fd store after kexec"); + + log_info("Got %zu fd(s) in fd store after kexec.", n_fds); + + /* Parse LISTEN_FDNAMES to match fds by name, not position */ + e = getenv("LISTEN_FDNAMES"); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDNAMES environment variable set"); + + names = strv_split(e, ":"); + if (!names) + return log_oom(); + assert(n_fds == strv_length(names)); + + static const struct { + const char *name; + const char *expected; + } checks[] = { + { "testfd1", TEST_DATA_1 }, + { "testfd2", TEST_DATA_2 }, + }; + + if (n_fds < ELEMENTSOF(checks)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Not enough fds in fd store after kexec: expected at least %zu, got %zu", + ELEMENTSOF(checks), n_fds); + + for (size_t i = 0; i < ELEMENTSOF(checks); i++) { + char buf[256]; + ssize_t n; + size_t idx = 0; + int fd = -EBADF; + + /* Find the fd by name */ + STRV_FOREACH(name, names) { + if (idx >= n_fds) + break; + if (streq(*name, checks[i].name)) { + fd = SD_LISTEN_FDS_START + idx; + break; + } + idx++; + } + + if (fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "fd '%s' not found in LISTEN_FDNAMES", checks[i].name); + + /* memfds are sealed; pread() avoids needing a separate lseek() */ + n = pread(fd, buf, sizeof(buf) - 1, 0); + if (n < 0) + return log_error_errno(errno, "Failed to read fd %d: %m", fd); + + buf[n] = '\0'; + + if (!streq(buf, checks[i].expected)) + return log_error_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Content mismatch for '%s': expected '%s', got '%s'", + checks[i].name, checks[i].expected, buf); + + /* Remove the fd from the fd store so we don't keep accumulating duplicates across + * repeated invocations (and across repeated kexec cycles). */ + r = sd_pid_notifyf(0, /* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=%s", checks[i].name); + if (r < 0) + return log_error_errno(r, "Failed to remove fd '%s' from fd store: %m", checks[i].name); + + log_info("Verified fd '%s': content matches.", checks[i].name); + } + + log_info("All fd store checks passed."); + + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for notification barrier: %m"); + + return 0; +} + +static int run(int argc, char *argv[]) { + if (argc != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check", argv[0]); + + if (streq(argv[1], "store")) + return do_store(); + if (streq(argv[1], "check")) + return do_check(); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command: %s", argv[1]); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/test/integration-tests/TEST-91-LIVEUPDATE/meson.build b/test/integration-tests/TEST-91-LIVEUPDATE/meson.build new file mode 100644 index 0000000000000..460ef32476d66 --- /dev/null +++ b/test/integration-tests/TEST-91-LIVEUPDATE/meson.build @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : 'TEST-91-LIVEUPDATE', + 'configuration' : integration_test_template['configuration'] + { + 'unit' : integration_test_template['configuration']['unit'] + { + 'DefaultDependencies' : 'no', + }, + 'service' : integration_test_template['configuration']['service'] + { + 'FileDescriptorStoreMax' : '20', + 'FileDescriptorStorePreserve' : 'yes', + 'NotifyAccess' : 'all', + }, + }, + 'cmdline' : ['kho=on', 'liveupdate=on', 'systemd.minimum_uptime_sec=0'], + 'storage' : 'persistent', + 'vm' : true, + 'firmware' : 'uefi', + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index dd9eba9b673b0..867fdecda2d37 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -103,6 +103,7 @@ foreach dirname : [ 'TEST-88-UPGRADE', 'TEST-89-RESOLVED-MDNS', 'TEST-90-RESTRICT-FSACCESS', + 'TEST-91-LIVEUPDATE', ] subdir(dirname) endforeach diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh new file mode 100755 index 0000000000000..f51f2f39065d7 --- /dev/null +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -0,0 +1,199 @@ +#!/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 + +export SYSTEMD_LOG_LEVEL=debug + +# This test verifies that the Live Update Orchestrator (LUO) integration works: +# - PID 1 can serialize fd stores and pass them to systemd-shutdown +# - systemd-shutdown can preserve fds in a LUO session before kexec +# - After kexec, PID 1 restores the fd stores from the LUO session +# +# The test requires KHO (Kexec HandOver) and LUO (Live Update Orchestrator) kernel support. + +if [[ ! -e /dev/liveupdate ]]; then + echo "/dev/liveupdate not available, skipping test" + exit 77 +fi + +# Ensure user units can also manage sessions +chmod 666 /dev/liveupdate + +TESTUSER_UID=$(id -u testuser) +TESTUSER_USER_SVC="user@${TESTUSER_UID}.service" + +at_exit() { + systemctl stop systemd-nspawn@fdstore.service ||: + machinectl terminate fdstore ||: + rm -rf /var/lib/machines/fdstore ||: + rm -f /run/systemd/nspawn/fdstore.nspawn +} + +trap at_exit EXIT + +# To test the late-load path also create units that appear at runtime. +# Three variants exercise different fragment scenarios on second boot: +# - late.service: fragment present before fds are observed (daemon-reload triggered) +# - late-noreload.service: fragment dropped only after kexec, never daemon-reloaded explicitly +# to exercise lazy load via systemctl start +# - late-zerofds.service: fragment on second boot sets FileDescriptorStoreMax=0, +# the previously stored fds must be dropped +write_late_unit() { + local scope="${1:?}" name="${2:?}" cmd="${3:?}" maxfd="${4:-20}" + local dir + + case "${scope}" in + system) dir=/run/systemd/system ;; + user) dir=/run/systemd/user ;; + *) echo "unknown scope: ${scope}" >&2; return 1 ;; + esac + + mkdir -p "${dir}" + cat >"${dir}/${name}.service" </var/lib/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +exec /usr/bin/test-fdstore check +EOF + chmod +x /var/lib/machines/fdstore/sbin/init + mkdir -p /run/systemd/nspawn + cat >/run/systemd/nspawn/fdstore.nspawn < systemd-nspawn@fdstore.service fdstore + # -> LUO -> after kexec PID 1 restores the fdstore -> systemd-nspawn -> + # payload verifies content matches. + create_dummy_container /var/lib/machines/fdstore + cat >/var/lib/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +exec /usr/bin/test-fdstore store +EOF + chmod +x /var/lib/machines/fdstore/sbin/init + + mkdir -p /run/systemd/nspawn + cat >/run/systemd/nspawn/fdstore.nspawn < Date: Thu, 16 Apr 2026 22:32:22 +0100 Subject: [PATCH 1747/2155] docs: document LUO support --- docs/FILE_DESCRIPTOR_STORE.md | 25 +++++++++++++++++++++++++ man/sd_notify.xml | 11 +++++++++++ man/systemd.service.xml | 9 +++++++++ 3 files changed, 45 insertions(+) diff --git a/docs/FILE_DESCRIPTOR_STORE.md b/docs/FILE_DESCRIPTOR_STORE.md index 8fa2ae0127c96..e6141a4e01b6e 100644 --- a/docs/FILE_DESCRIPTOR_STORE.md +++ b/docs/FILE_DESCRIPTOR_STORE.md @@ -181,6 +181,31 @@ continuously). For further details see [Resource Pass-Through](https://www.freedesktop.org/software/systemd/man/latest/systemd-soft-reboot.service.html#Resource%20Pass-Through). +## Kernel Live Update (kexec) + +On kernels that support the [Live Update +Orchestrator](https://docs.kernel.org/userspace-api/liveupdate.html) +(LUO), the fdstore may also be preserved across a `kexec`-based reboot into a +new kernel. This allows updating the kernel itself without losing pinned +resources such as serialized service state, analogous to soft reboot, but for +the kernel. + +Only file descriptors that reference LUO-compatible kernel objects can be +preserved this way. Currently the kernel supports `memfd` only for LUO, but +more types are being worked on. Other kinds of file descriptors (sockets, +regular files, etc.) will be dropped from the store during the kexec transition. + +LUO preservation of the fdstore is triggered automatically whenever a +kexec-based reboot is initiated on an LUO-capable kernel, and is gated by a +similar rule as soft-reboot: the service must have +`FileDescriptorStorePreserve=yes` set, so that its fdstore remains loaded. On +the other side of the kexec, the system manager rebuilds the mapping of fds +back to their original service units, so that when those services are +re-activated the fds are passed to them using the normal fdstore protocol. +Adding a `FDNAME=…` string identifying the fd is also highly recommended, +otherwise in case multiple fds are stored, it will be impossible to +distinguish them, as they will all carry the default name (`stored`). + ## Initrd Transitions The fdstore may also be used to pass file descriptors for resources from the diff --git a/man/sd_notify.xml b/man/sd_notify.xml index f9bb56b2d4730..52685c404bb9b 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -428,6 +428,17 @@ For further information on the file descriptor store see the File Descriptor Store overview. + On kernels that support the Live Update Orchestrator, + compatible file descriptors stored this way (such as memfd_create2) + are additionally preserved across kexec-based reboots and handed back to the + service on the other side, provided FileDescriptorStorePreserve=yes is set on + the service (see + systemd.service5). + See the File Descriptor Store + overview for details. + diff --git a/man/systemd.service.xml b/man/systemd.service.xml index b25f1a90aabe0..028c1144b0820 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1266,6 +1266,15 @@ RestartMaxDelaySec=160s See the File Descriptor Store overview for details. + Setting this to yes also ensures the file descriptor store is kept loaded + across a kexec-based reboot on kernels supporting the Live Update Orchestrator, + so that compatible file descriptors (such as memfd_create2) + are preserved and handed back to the service on the other side. See the File Descriptor Store overview for + details. + Use systemctl clean --what=fdstore … to release the file descriptor store explicitly. From c10c2674f39929998009e735ff85ed7b8378ec04 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 15 Apr 2026 13:11:30 +0100 Subject: [PATCH 1748/2155] LUO: add support for preserving third party sessions LUO sessions cannot be nested under other sessions. This means we need to handle them explicitly, and held them open in the shutdown binary like we do with our own internal session, to allow services to create their own. The requirement to support third party sessions comes from VMMs that wish to preserve VM(s) state(s) across kexec, as some file descriptors (KVM's vmfd from the KVM_CREATE_VM ioctl) cannot be transfered between processes via SCM_RIGHTS, so they cannot be stashed in the FD Store directly. Also some file descriptors have to be handled all together or not at all, again to do with KVM and devices that are all part of the same vm. --- docs/FILE_DESCRIPTOR_STORE.md | 8 +++ src/core/luo.c | 42 +++++++++++++- src/shared/luo-util.c | 67 ++++++++++++++++++++- src/shared/luo-util.h | 6 ++ src/test/test-luo.c | 99 +++++++++++++++++++++++++++++--- test/units/TEST-91-LIVEUPDATE.sh | 23 ++++---- 6 files changed, 220 insertions(+), 25 deletions(-) diff --git a/docs/FILE_DESCRIPTOR_STORE.md b/docs/FILE_DESCRIPTOR_STORE.md index e6141a4e01b6e..c3336453781ca 100644 --- a/docs/FILE_DESCRIPTOR_STORE.md +++ b/docs/FILE_DESCRIPTOR_STORE.md @@ -206,6 +206,14 @@ Adding a `FDNAME=…` string identifying the fd is also highly recommended, otherwise in case multiple fds are stored, it will be impossible to distinguish them, as they will all carry the default name (`stored`). +Services that need to preserve additional kernel state may also create their +own LUO sessions by opening `/dev/liveupdate` directly (see the kernel +documentation linked above) and pushing the obtained session fd into their +fdstore (it is recommended to use a `FDNAME=…` string, as above). systemd +detects such fds and arranges for them to survive the kexec as well, so that +the session, and any supported file descriptors preserved inside it, is +handed back to the service on the other side of the reboot. + ## Initrd Transitions The fdstore may also be used to pass file descriptors for resources from the diff --git a/src/core/luo.c b/src/core/luo.c index 4b5632c6dbac0..9dc892d5d696e 100644 --- a/src/core/luo.c +++ b/src/core/luo.c @@ -104,6 +104,7 @@ int manager_luo_restore_fd_stores(Manager *m) { const char *type; const char *name; uint64_t token; + const char *session_name; } p = { .token = UINT64_MAX, }; @@ -112,6 +113,7 @@ int manager_luo_restore_fd_stores(Manager *m) { { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, { "token", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, token), 0 }, + { "sessionName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, session_name), 0 }, {} }; @@ -137,6 +139,20 @@ int manager_luo_restore_fd_stores(Manager *m) { unit_id, p.name, p.token); continue; } + } else if (streq(p.type, "luo_session")) { + if (!p.session_name) { + log_warning("LUO mapping for unit '%s' fd '%s': missing sessionName.", unit_id, p.name); + continue; + } + + fd = luo_retrieve_session(device_fd, p.session_name); + if (fd < 0) { + log_warning_errno(fd, "Failed to retrieve LUO session '%s' for unit '%s' name '%s': %m", + p.session_name, unit_id, p.name); + continue; + } + + log_debug("Retrieved LUO session '%s' for unit fd store '%s'.", p.session_name, p.name); } else { log_warning("LUO mapping for unit '%s' fd '%s': unknown type '%s', skipping.", unit_id, p.name, p.type); @@ -177,7 +193,8 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { if (!fds) return log_oom(); - /* Build a JSON object: { "unit_id": [ { "type": "fd", "name": "...", "fd": N }, ... ], ... } + /* Build a JSON object: { "unit_id": [ { "type": "fd", "name": "...", "fd": N }, + * { "type": "luo_session", "name": "...", "fd": N, "sessionName": "..." } ], ... } * This is passed to systemd-shutdown which will create a LUO session and preserve the fds. */ HASHMAP_FOREACH(u, m->units) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; @@ -195,17 +212,36 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { continue; LIST_FOREACH(fd_store, fs, s->fd_store) { + _cleanup_free_ char *session_name = NULL; int copy; + /* Check if this fd is itself a LUO session, as those cannot be nested and need + * special handling */ + r = fd_get_luo_session_name(fs->fd, &session_name); + if (r < 0 && r != -EMEDIUMTYPE) { + log_warning_errno(r, "Failed to check if fd '%s' of unit '%s' is a LUO session, skipping: %m", + fs->fdname, u->id); + continue; + } + + /* Ensure nobody tries to hijack our session, as we will create this later before + * kexec */ + if (streq_ptr(session_name, LUO_SESSION_NAME)) { + log_warning("Skipping fd '%s' of unit '%s' for LUO serialization, as the session name '%s' infringes systemd's namespace.", + fs->fdname, u->id, session_name); + continue; + } + copy = fdset_put_dup(fds, fs->fd); if (copy < 0) return log_error_errno(copy, "Failed to duplicate fd for LUO serialization: %m"); r = sd_json_variant_append_arraybo( &entries, - SD_JSON_BUILD_PAIR_STRING("type", "fd"), + SD_JSON_BUILD_PAIR_STRING("type", session_name ? "luo_session" : "fd"), SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), - SD_JSON_BUILD_PAIR_INTEGER("fd", copy)); + SD_JSON_BUILD_PAIR_INTEGER("fd", copy), + JSON_BUILD_PAIR_STRING_NON_EMPTY("sessionName", session_name)); if (r < 0) return log_error_errno(r, "Failed to build JSON for LUO serialization: %m"); diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c index c1a579a0a00f5..898e51efa52c0 100644 --- a/src/shared/luo-util.c +++ b/src/shared/luo-util.c @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include "sd-json.h" @@ -15,6 +17,7 @@ #include "luo-util.h" #include "memfd-util.h" #include "parse-util.h" +#include "stat-util.h" #include "string-util.h" /* Kernel API defined at https://docs.kernel.org/userspace-api/liveupdate.html The /dev/liveupdate is a @@ -219,9 +222,12 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", LUO_SESSION_NAME); /* Build the mapping JSON for the new kernel's PID 1 and preserve each fd. - * JSON format: { "unit_id": [ {"type": "fd", "name": "...", "token": N}, ... ], ... } + * JSON format: { "unit_id": [ {"type": "fd", "name": "...", "token": N}, + * {"type": "luo_session", "name": "...", "sessionName": "..."} ], ... } * - * For regular fds: type=fd, preserved in the systemd session with the given LUO token. */ + * For regular fds: type=fd, preserved in the systemd session with the given LUO token. + * For LUO session fds: type=luo_session, the session survives kexec independently, as it cannot be + * nested. */ JSON_VARIANT_OBJECT_FOREACH(unit_id, entries, serialization) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *fd_list = NULL; sd_json_variant *entry; @@ -270,6 +276,23 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) return log_error_errno(r, "Failed to build LUO mapping: %m"); ++token; + } else if (streq(p.type, "luo_session")) { + if (!p.session_name) { + log_warning("LUO mapping for unit '%s' fd '%s': missing sessionName, skipping.", unit_id, p.name); + continue; + } + + /* Remember the FDStore name to session name mapping */ + r = sd_json_variant_append_arraybo( + &fd_list, + SD_JSON_BUILD_PAIR_STRING("type", "luo_session"), + SD_JSON_BUILD_PAIR_STRING("name", p.name), + SD_JSON_BUILD_PAIR_STRING("sessionName", p.session_name)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO mapping for session fd: %m"); + + log_debug("LUO session fd '%s' (session '%s') recorded in mapping.", + p.name, p.session_name); } else log_warning("Unknown fd type '%s' for unit '%s' fd '%s', skipping.", p.type, unit_id, p.name); } @@ -311,3 +334,43 @@ int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) *ret_session_fd = TAKE_FD(session_fd); return 1; } + +int fd_get_luo_session_name(int fd, char **ret) { + _cleanup_free_ char *path = NULL; + int r; + + assert(fd >= 0); + + // TODO: switch to LUO specific inode magic once available + r = fd_is_fs_type(fd, ANON_INODE_FS_MAGIC); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + r = fd_get_path(fd, &path); + if (r < 0) + return r; + + /* Path is "anon_inode:[luo_session] " */ + const char *suffix = startswith(path, "anon_inode:[luo_session] "); + if (isempty(suffix)) + return -EMEDIUMTYPE; + + if (ret) + return strdup_to(ret, suffix); + + return 0; +} + +int fd_is_luo_session(int fd) { + int r; + + r = fd_get_luo_session_name(fd, /* ret= */ NULL); + if (r == -EMEDIUMTYPE) + return false; + if (r < 0) + return r; + + return true; +} diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h index 9f6c061910ae0..1ca7ca6d71235 100644 --- a/src/shared/luo-util.h +++ b/src/shared/luo-util.h @@ -13,6 +13,7 @@ * "unit-name.service": [ * { "type": "fd", "name": "fdname1", "token": 1 }, * { "type": "fd", "name": "fdname2", "token": 2 }, + * { "type": "luo_session", "name": "fdname3", "sessionName": "unit.service/myapp" } * ], * "other-unit.service": [ * { "type": "fd", "name": "stored", "token": 3 } @@ -20,6 +21,8 @@ * } * * type=fd: the fd was preserved in the "systemd" LUO session with the given token. + * type=luo_session: a service-owned LUO session that survives kexec independently, + * retrieved by session_name on the next boot. */ #define LUO_MAPPING_INDEX UINT64_C(0) @@ -32,3 +35,6 @@ int luo_session_finish(int session_fd); int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds); int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd); + +int fd_is_luo_session(int fd); +int fd_get_luo_session_name(int fd, char **ret); diff --git a/src/test/test-luo.c b/src/test/test-luo.c index 95ccc84b1a60b..97ba66459d236 100644 --- a/src/test/test-luo.c +++ b/src/test/test-luo.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* Helper for TEST-91-LIVEUPDATE: creates memfds and stores them in the fd store, - * or verifies that inherited fd store entries contain the expected content. + * creates a LUO session directly via /dev/liveupdate and stores a memfd in it, + * or verifies everything after kexec. * * Usage: - * test-luo store - create memfds with test data and push them to the fd store - * test-luo check - verify fd store content matches expectations + * test-luo store - create memfds and a LUO session, push all to the fd store + * test-luo check - verify fd store content and LUO session memfd after kexec */ #include @@ -16,6 +17,7 @@ #include "fd-util.h" #include "log.h" +#include "luo-util.h" #include "main-func.h" #include "memfd-util.h" #include "parse-util.h" @@ -25,8 +27,10 @@ #define TEST_DATA_1 "liveupdate-test-data-1" #define TEST_DATA_2 "liveupdate-test-data-2" +#define SESSION_MEMFD_DATA "luo-session-memfd-test-data" +#define SESSION_MEMFD_TOKEN UINT64_C(42) -static int do_store(void) { +static int do_store(const char *prefix) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; int r; @@ -48,6 +52,33 @@ static int do_store(void) { log_info("Stored 2 memfds in fd store."); + /* Create a LUO session directly via /dev/liveupdate, put a memfd in it, and store the session fd */ + _cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF, session_memfd = -EBADF; + const char *session_name = strjoina(prefix, "-direct"); + + device_fd = luo_open_device(); + if (device_fd < 0) + return log_error_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_create_session(device_fd, session_name); + if (session_fd < 0) + return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", session_name); + + session_memfd = memfd_new_and_seal("session-test", SESSION_MEMFD_DATA, strlen(SESSION_MEMFD_DATA)); + if (session_memfd < 0) + return log_error_errno(session_memfd, "Failed to create session memfd: %m"); + + r = luo_session_preserve_fd(session_fd, session_memfd, SESSION_MEMFD_TOKEN); + if (r < 0) + return log_error_errno(r, "Failed to preserve memfd in session: %m"); + + r = sd_pid_notifyf_with_fds(0, false, &session_fd, 1, "FDSTORE=1\nFDNAME=%s-direct", prefix); + if (r < 0) + return log_error_errno(r, "Failed to store session fd in fd store: %m"); + TAKE_FD(session_fd); + + log_info("Stored LUO session '%s' with memfd in fd store.", session_name); + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ r = sd_notify_barrier(0, 5 * USEC_PER_SEC); @@ -57,9 +88,10 @@ static int do_store(void) { return 0; } -static int do_check(void) { +static int do_check(const char *prefix) { const char *e; _cleanup_strv_free_ char **names = NULL; + const char *session_fdname = strjoina(prefix, "-direct"); size_t n_fds; int r; @@ -146,6 +178,53 @@ static int do_check(void) { log_info("All fd store checks passed."); + /* Verify the LUO session fd survived and its memfd content is intact */ + int session_fd = -EBADF; + size_t idx = 0; + STRV_FOREACH(name, names) { + if (idx >= n_fds) + break; + if (streq(*name, session_fdname)) { + session_fd = SD_LISTEN_FDS_START + idx; + break; + } + idx++; + } + + if (session_fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "LUO session fd '%s' not found in fd store", session_fdname); + + r = fd_is_luo_session(session_fd); + if (r < 0) + return log_error_errno(r, "Failed to check if fd is LUO session: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "fd '%s' is not a LUO session!", session_fdname); + + _cleanup_close_ int session_memfd = luo_session_retrieve_fd(session_fd, SESSION_MEMFD_TOKEN); + if (session_memfd < 0) + return log_error_errno(session_memfd, "Failed to retrieve memfd from session: %m"); + + char sbuf[256]; + ssize_t sn = pread(session_memfd, sbuf, sizeof(sbuf) - 1, 0); + if (sn < 0) + return log_error_errno(errno, "Failed to read session memfd: %m"); + sbuf[sn] = '\0'; + + if (!streq(sbuf, SESSION_MEMFD_DATA)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Session memfd content mismatch: expected '%s', got '%s'", + SESSION_MEMFD_DATA, sbuf); + + /* Remove the LUO session fd from the fd store as well. */ + r = sd_pid_notifyf(0, /* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=%s", session_fdname); + if (r < 0) + return log_error_errno(r, "Failed to remove fd '%s' from fd store: %m", session_fdname); + + log_info("Verified LUO session memfd content matches."); + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ r = sd_notify_barrier(0, 5 * USEC_PER_SEC); @@ -156,13 +235,15 @@ static int do_check(void) { } static int run(int argc, char *argv[]) { - if (argc != 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check", argv[0]); + if (argc < 2 || argc > 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check [PREFIX]", argv[0]); + + const char *prefix = argc > 2 ? argv[2] : "luosession"; if (streq(argv[1], "store")) - return do_store(); + return do_store(prefix); if (streq(argv[1], "check")) - return do_check(); + return do_check(prefix); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command: %s", argv[1]); } diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh index f51f2f39065d7..1d2df255cf2c2 100755 --- a/test/units/TEST-91-LIVEUPDATE.sh +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -69,13 +69,13 @@ if grep -qw luo_nboot=1 /proc/cmdline; then # Verify that the user manager also preserved its FD store n_user_at_fds=$(systemctl show -P NFileDescriptorStore "${TESTUSER_USER_SVC}") - test "${n_user_at_fds}" -ge 2 + test "${n_user_at_fds}" -ge 3 write_late_unit user TEST-91-LIVEUPDATE-user-late \ "/usr/lib/systemd/tests/unit-tests/manual/test-luo check user-late" systemctl restart "${TESTUSER_USER_SVC}" timeout 30s bash -c "until systemctl is-active --quiet '${TESTUSER_USER_SVC}'; do sleep 0.5; done" n_user_unit_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore TEST-91-LIVEUPDATE-user-late.service) - test "${n_user_unit_fds}" -eq 2 + test "${n_user_unit_fds}" -eq 3 run0 -u testuser systemctl --user start TEST-91-LIVEUPDATE-user-late.service # nspawn fdstore variant: after kexec, PID 1 propagated the @@ -102,22 +102,22 @@ EOF # late.service: rewrite the fragment with the second-boot ExecStart and # exercise the daemon-reload + daemon-reexec preservation paths. write_late_unit system TEST-91-LIVEUPDATE-late \ - "/usr/lib/systemd/tests/unit-tests/manual/test-luo check" + "/usr/lib/systemd/tests/unit-tests/manual/test-luo check late" n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) - test "$n_fds" -eq 2 + test "$n_fds" -eq 3 systemctl daemon-reload # Verify the late unit doesn't get GC'ed during daemon-reload n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) - test "$n_fds" -eq 2 + test "$n_fds" -eq 3 systemctl daemon-reexec # Verify the late unit doesn't get GC'ed during daemon-reexec n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) - test "$n_fds" -eq 2 + test "$n_fds" -eq 3 systemctl start TEST-91-LIVEUPDATE-late.service @@ -127,7 +127,7 @@ EOF write_late_unit system TEST-91-LIVEUPDATE-late-noreload \ "/usr/lib/systemd/tests/unit-tests/manual/test-luo check late-noreload" n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late-noreload.service) - test "$n_fds" -eq 2 + test "$n_fds" -eq 3 systemctl start TEST-91-LIVEUPDATE-late-noreload.service # Zero-fds variant: fragment on second boot sets FileDescriptorStoreMax=0, @@ -140,6 +140,7 @@ EOF systemctl start TEST-91-LIVEUPDATE-late-zerofds.service else # Create memfds with known content and push them to our fd store. + # Also request a LUO session, store a memfd in it, and push the session fd to the fd store. /usr/lib/systemd/tests/unit-tests/manual/test-luo store # Exercise the user manager FD preservation across kexec too @@ -149,9 +150,9 @@ else "/usr/lib/systemd/tests/unit-tests/manual/test-luo store user-late" run0 -u testuser systemctl --user start TEST-91-LIVEUPDATE-user-late.service n_user_unit_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore TEST-91-LIVEUPDATE-user-late.service) - test "${n_user_unit_fds}" -eq 2 + test "${n_user_unit_fds}" -eq 3 n_user_at_fds=$(systemctl show -P NFileDescriptorStore "${TESTUSER_USER_SVC}") - test "${n_user_at_fds}" -ge 2 + test "${n_user_at_fds}" -ge 3 # Exercise the FD-store preservation chain across a kexec for a privileged # nspawn container managed as a system service: @@ -180,11 +181,11 @@ EOF # to avoid collisions in the LUO session namespace. for variant in late late-noreload late-zerofds; do write_late_unit system "TEST-91-LIVEUPDATE-${variant}" \ - "/usr/lib/systemd/tests/unit-tests/manual/test-luo store" + "/usr/lib/systemd/tests/unit-tests/manual/test-luo store ${variant}" systemctl start "TEST-91-LIVEUPDATE-${variant}.service" n_fds=$(systemctl show -P NFileDescriptorStore "TEST-91-LIVEUPDATE-${variant}.service") - test "$n_fds" -eq 2 + test "$n_fds" -eq 3 done # 'systemctl kexec' auto-loads the default boot entry (i.e. the booted UKI, From 76ac669737984507f12fc666a82bf260d039ead9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 10:37:19 +0000 Subject: [PATCH 1749/2155] mkosi: Make sure mkosi.clangd maps to mkosi/mkosi.tools The tools tree is now stored in mkosi/mkosi.tools, so update the script to match. --- mkosi/mkosi.clangd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.clangd b/mkosi/mkosi.clangd index 7cac6cecbfd0a..c1971cff258af 100755 --- a/mkosi/mkosi.clangd +++ b/mkosi/mkosi.clangd @@ -13,5 +13,5 @@ exec "${SPAWN[@]}" \ clangd \ --compile-commands-dir=build \ --path-mappings="\ -$(pwd)/mkosi.tools/usr/include=/usr/include" \ +$(pwd)/mkosi/mkosi.tools/usr/include=/usr/include" \ "$@" From 6fbb3c55f1c99b99ce2a233d7402c0a2cd3e616b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Fri, 15 May 2026 17:54:42 +0200 Subject: [PATCH 1750/2155] varlink: MethodNotImplemented: drop extraneous dot --- src/libsystemd/sd-varlink/varlink-org.varlink.service.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/varlink-org.varlink.service.c b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c index d91670899af3a..6c7b9d5abe5f8 100644 --- a/src/libsystemd/sd-varlink/varlink-org.varlink.service.c +++ b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c @@ -34,7 +34,7 @@ static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR( MethodNotImplemented, - SD_VARLINK_FIELD_COMMENT("Name of method that was called but is not implemented."), + SD_VARLINK_FIELD_COMMENT("Name of method that was called but is not implemented"), SD_VARLINK_DEFINE_FIELD(method, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_ERROR( From 0304ba263a2b4753872d4b616fa25fbe288ebe10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Fri, 15 May 2026 17:55:20 +0200 Subject: [PATCH 1751/2155] varlink: vl_error_MethodNotFound: an method --- src/libsystemd/sd-varlink/varlink-org.varlink.service.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/varlink-org.varlink.service.c b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c index 6c7b9d5abe5f8..57acf052e522a 100644 --- a/src/libsystemd/sd-varlink/varlink-org.varlink.service.c +++ b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c @@ -59,7 +59,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_InterfaceNotFound, SD_VARLINK_SYMBOL_COMMENT("Error returned if an unknown method is called on an known interface"), &vl_error_MethodNotFound, - SD_VARLINK_SYMBOL_COMMENT("Error returned if an method is called that is known but not implemented"), + SD_VARLINK_SYMBOL_COMMENT("Error returned if a method is called that is known but not implemented"), &vl_error_MethodNotImplemented, SD_VARLINK_SYMBOL_COMMENT("Error returned if a method is called with an invalid parameter"), &vl_error_InvalidParameter, From 321c894828d5d5dd4cc11592178874bfc56e92d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 13:33:58 +0200 Subject: [PATCH 1752/2155] homectl: reorder verb functions to match order in --help Just a hand-crafted moving of blocks of code up and down, no other changes. The net diff is -2 because add_signing_keys_from_credentials forward declaration was dropped. --- src/home/homectl.c | 5024 ++++++++++++++++++++++---------------------- 1 file changed, 2511 insertions(+), 2513 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index e9161dfb751e5..42d210d1c6c95 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -627,85 +627,6 @@ static int acquire_passed_secrets(const char *user_name, UserRecord **ret) { return 0; } -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(user_record_unrefp) UserRecord *secret = NULL; - - r = acquire_passed_secrets(*i, &secret); - if (r < 0) - return r; - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = bus_message_append_secret(m, secret); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - r = handle_generic_user_record_error(*i, secret, &error, r, /* emphasize_current_password= */ false); - if (r < 0) { - if (ret == 0) - ret = r; - - break; - } - } else - break; - } - } - - return ret; -} - -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; - } - } - - return ret; -} - static void dump_home_record(UserRecord *hr) { int r; @@ -799,65 +720,6 @@ static int verb_inspect_homes(int argc, char *argv[], uintptr_t _data, void *use } } -static int authenticate_home(sd_bus *bus, const char *name) { - _cleanup_(user_record_unrefp) UserRecord *secret = NULL; - int r; - - r = acquire_passed_secrets(name, &secret); - if (r < 0) - return r; - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "AuthenticateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", name); - if (r < 0) - return bus_log_create_error(r); - - r = bus_message_append_secret(m, secret); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - r = handle_generic_user_record_error(name, secret, &error, r, false); - if (r >= 0) - continue; - } - return r; - } -} - -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - char **args = strv_skip(argv, 1); - if (args) { - STRV_FOREACH(arg, args) - RET_GATHER(r, authenticate_home(bus, *arg)); - - return r; - } else { - _cleanup_free_ char *myself = getusername_malloc(); - if (!myself) - return log_oom(); - - return authenticate_home(bus, myself); - } -} - static int update_last_change(sd_json_variant **v, bool with_password, bool override) { sd_json_variant *c; usec_t n; @@ -1592,265 +1454,89 @@ static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userd return create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ true); } -static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r, ret = 0; +static int acquire_updated_home_record( + sd_bus *bus, + const char *username, + UserRecord **ret) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + int r; - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + assert(ret); - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "AdoptHome"); - if (r < 0) - return bus_log_create_error(r); + if (arg_identity) { + unsigned line = 0, column = 0; + sd_json_variant *un; - r = sd_bus_message_append(m, "st", *i, UINT64_C(0)); + r = sd_json_parse_file( + streq(arg_identity, "-") ? stdin : NULL, + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + &line, + &column); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to adopt home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; + un = sd_json_variant_by_key(json, "userName"); + if (un) { + if (!sd_json_variant_is_string(un) || (username && !streq(sd_json_variant_string(un), username))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match."); + } else { + if (!username) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified."); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); + if (r < 0) + return log_error_errno(r, "Failed to set userName field: %m"); } - } - return ret; -} + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int incomplete; + const char *text; -static int register_home_common(sd_bus *bus, sd_json_variant *v) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *_bus = NULL; - int r; + if (!identity_properties_specified()) + return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified."); - assert(v); + r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", username); + if (r < 0) + return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r)); - if (!bus) { - r = acquire_bus(&_bus); + r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL); if (r < 0) - return r; - bus = _bus; - } + return bus_log_parse_error(r); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "RegisterHome"); - if (r < 0) - return bus_log_create_error(r); + if (incomplete) + return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record."); - _cleanup_free_ char *formatted = NULL; - r = sd_json_variant_format(v, /* flags= */ 0, &formatted); - if (r < 0) - return log_error_errno(r, "Failed to format JSON record: %m"); + 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"); - r = sd_bus_message_append(m, "s", formatted); - if (r < 0) - return bus_log_create_error(r); + reply = sd_bus_message_unref(reply); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to register home: %s", bus_error_message(&error, r)); + r = sd_json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest")); + if (r < 0) + return log_error_errno(r, "Failed to strip binding and status from record to update: %m"); + } - return 0; -} + r = apply_identity_changes(&json); + if (r < 0) + return r; -static int register_home_one(sd_bus *bus, FILE *f, const char *path) { - int r; - - assert(bus); - assert(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_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); - - return register_home_common(bus, v); -} - -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; - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - if (arg_identity) { - if (argc > 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not accepting an arguments if --identity= is specified, refusing."); - - return register_home_one(bus, /* f= */ NULL, arg_identity); - } - - if (argc == 1 || (argc == 2 && streq(argv[1], "-"))) - return register_home_one(bus, /* f= */ stdin, ""); - - r = 0; - STRV_FOREACH(i, strv_skip(argv, 1)) { - if (streq(*i, "-")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing reading from standard input if multiple user records are specified."); - - RET_GATHER(r, register_home_one(bus, /* f= */ NULL, *i)); - } - - return r; -} - -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; - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - int ret = 0; - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "UnregisterHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL); - if (r < 0) - RET_GATHER(ret, log_error_errno(r, "Failed to unregister home: %s", bus_error_message(&error, r))); - } - - return ret; -} - -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; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "RemoveHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; - } - } - - return ret; -} - -static int acquire_updated_home_record( - sd_bus *bus, - const char *username, - UserRecord **ret) { - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - _cleanup_(user_record_unrefp) UserRecord *hr = NULL; - int r; - - assert(ret); - - if (arg_identity) { - unsigned line = 0, column = 0; - sd_json_variant *un; - - r = sd_json_parse_file( - streq(arg_identity, "-") ? stdin : NULL, - 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); - - un = sd_json_variant_by_key(json, "userName"); - if (un) { - if (!sd_json_variant_is_string(un) || (username && !streq(sd_json_variant_string(un), username))) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match."); - } else { - if (!username) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified."); - - r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); - if (r < 0) - return log_error_errno(r, "Failed to set userName field: %m"); - } - - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int incomplete; - const char *text; - - if (!identity_properties_specified()) - return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified."); - - r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", username); - if (r < 0) - return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL); - if (r < 0) - return bus_log_parse_error(r); - - 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_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"); - - reply = sd_bus_message_unref(reply); - - r = sd_json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest")); - if (r < 0) - return log_error_errno(r, "Failed to strip binding and status from record to update: %m"); - } - - r = apply_identity_changes(&json); - if (r < 0) - return r; - - STRV_FOREACH(i, arg_pkcs11_token_uri) { - r = identity_add_pkcs11_key_data(&json, *i); - if (r < 0) - return r; - } + STRV_FOREACH(i, arg_pkcs11_token_uri) { + r = identity_add_pkcs11_key_data(&json, *i); + if (r < 0) + return r; + } STRV_FOREACH(i, arg_fido2_device) { r = identity_add_fido2_parameters(&json, *i, arg_fido2_lock_with, arg_fido2_cred_alg); @@ -2265,7 +1951,7 @@ static int verb_resize_home(int argc, char *argv[], uintptr_t _data, void *userd return 0; } -static int verb_lock_home(int argc, char *argv[], uintptr_t _data, 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; @@ -2273,11 +1959,13 @@ static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdat if (r < 0) return r; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + STRV_FOREACH(i, strv_skip(argv, 1)) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "LockHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "RemoveHome"); if (r < 0) return bus_log_create_error(r); @@ -2287,7 +1975,7 @@ static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdat r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r)); + log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r)); if (ret == 0) ret = r; } @@ -2296,7 +1984,7 @@ static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdat return ret; } -static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, 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; @@ -2315,7 +2003,7 @@ static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userd _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "UnlockHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHome"); if (r < 0) return bus_log_create_error(r); @@ -2329,7 +2017,7 @@ static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userd r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - r = handle_generic_user_record_error(argv[1], secret, &error, r, false); + r = handle_generic_user_record_error(*i, secret, &error, r, /* emphasize_current_password= */ false); if (r < 0) { if (ret == 0) ret = r; @@ -2344,6 +2032,58 @@ static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userd return ret; } +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateHome"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; + } + } + + return ret; +} + +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; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r)); + + return 0; +} + 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; @@ -2467,51 +2207,41 @@ static int verb_with_home(int argc, char *argv[], uintptr_t _data, void *userdat return ret; } -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; +static int authenticate_home(sd_bus *bus, const char *name) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL; int r; - r = acquire_bus(&bus); + r = acquire_passed_secrets(name, &secret); if (r < 0) return r; - r = bus_message_new_method_call(bus, &m, bus_mgr, "LockAllHomes"); - if (r < 0) - return bus_log_create_error(r); + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r)); + r = bus_message_new_method_call(bus, &m, bus_mgr, "AuthenticateHome"); + if (r < 0) + return bus_log_create_error(r); - return 0; -} + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return bus_log_create_error(r); -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; - int r; + r = bus_message_append_secret(m, secret); + if (r < 0) + return bus_log_create_error(r); - r = acquire_bus(&bus); - if (r < 0) + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + r = handle_generic_user_record_error(name, secret, &error, r, false); + if (r >= 0) + continue; + } return r; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r)); - - return 0; + } } -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; +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; @@ -2519,855 +2249,937 @@ static int verb_rebalance(int argc, char *argv[], uintptr_t _data, void *userdat if (r < 0) return r; - r = bus_message_new_method_call(bus, &m, bus_mgr, "Rebalance"); - if (r < 0) - return bus_log_create_error(r); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_REBALANCE_NOT_NEEDED)) - log_info("No homes needed rebalancing."); - else - return log_error_errno(r, "Failed to rebalance: %s", bus_error_message(&error, r)); - } else - log_info("Completed rebalancing."); + char **args = strv_skip(argv, 1); + if (args) { + STRV_FOREACH(arg, args) + RET_GATHER(r, authenticate_home(bus, *arg)); - return 0; -} + return r; + } else { + _cleanup_free_ char *myself = getusername_malloc(); + if (!myself) + return log_oom(); -static int create_or_register_from_credentials(void) { - int r; + return authenticate_home(bus, myself); + } +} - _cleanup_close_ int fd = open_credentials_dir(); - if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ - return 0; - if (fd < 0) - return log_error_errno(fd, "Failed to open credentials directory: %m"); +static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r, ret = 0; - _cleanup_free_ DirectoryEntries *des = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to enumerate credentials: %m"); + return r; - int ret = 0, n_processed = 0; - FOREACH_ARRAY(i, des->entries, des->n_entries) { - struct dirent *de = *i; - if (de->d_type != DT_REG) - continue; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - enum { - OPERATION_CREATE, - OPERATION_REGISTER, - } op; - const char *e; - if ((e = startswith(de->d_name, "home.create."))) - op = OPERATION_CREATE; - else if ((e = startswith(de->d_name, "home.register."))) - op = OPERATION_REGISTER; - else - continue; + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "AdoptHome"); + if (r < 0) + return bus_log_create_error(r); - if (!valid_user_group_name(e, /* flags= */ 0)) { - log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); - continue; - } + r = sd_bus_message_append(m, "st", *i, UINT64_C(0)); + if (r < 0) + return bus_log_create_error(r); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *identity = NULL; - unsigned line = 0, column = 0; - r = sd_json_parse_file_at( - /* f= */ NULL, - fd, - de->d_name, - /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, - &identity, - &line, - &column); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - log_warning_errno(r, "[%s:%u:%u] Failed to parse user record in credential, ignoring: %m", de->d_name, line, column); - continue; + log_error_errno(r, "Failed to adopt home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; } + } - sd_json_variant *un = sd_json_variant_by_key(identity, "userName"); - if (un) { - if (!sd_json_variant_is_string(un)) { - log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name); - continue; - } - - if (!streq(sd_json_variant_string(un), e)) { - log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, sd_json_variant_string(un), e); - continue; - } - } else { - r = sd_json_variant_set_field_string(&identity, "userName", e); - if (r < 0) - return log_warning_errno(r, "Failed to set userName field: %m"); - } + return ret; +} - log_notice("Processing user '%s' from credentials.", e); +static int register_home_common(sd_bus *bus, sd_json_variant *v) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *_bus = NULL; + int r; - if (op == OPERATION_CREATE) - r = create_home_common(identity, /* show_enforce_password_policy_hint= */ false); - else - r = register_home_common(/* bus= */ NULL, identity); - if (r >= 0) - n_processed++; + assert(v); - RET_GATHER(ret, r); + if (!bus) { + r = acquire_bus(&_bus); + if (r < 0) + return r; + bus = _bus; } - return ret < 0 ? ret : n_processed; -} - -static int has_regular_user(void) { - _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - UserDBMatch match = USERDB_MATCH_NULL; - int r; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "RegisterHome"); + if (r < 0) + return bus_log_create_error(r); - match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + _cleanup_free_ char *formatted = NULL; + r = sd_json_variant_format(v, /* flags= */ 0, &formatted); + if (r < 0) + return log_error_errno(r, "Failed to format JSON record: %m"); - r = userdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); + r = sd_bus_message_append(m, "s", formatted); if (r < 0) - return log_error_errno(r, "Failed to create user enumerator: %m"); + return bus_log_create_error(r); - r = userdb_iterator_get(iterator, &match, /* ret= */ NULL); - if (r == -ESRCH) - return false; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to enumerate users: %m"); + return log_error_errno(r, "Failed to register home: %s", bus_error_message(&error, r)); - return true; + return 0; } -static int username_is_ok(const char *name, void *userdata) { +static int register_home_one(sd_bus *bus, FILE *f, const char *path) { int r; - assert(name); - - if (!valid_user_group_name(name, /* flags= */ 0)) { - log_notice("Specified user name is not a valid UNIX user name, try again: %s", name); - return false; - } + assert(bus); + assert(path); - r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL); - if (r == -ESRCH) - return true; + _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_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) - return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", name); + return log_error_errno(r, "[%s:%u:%u] Failed to parse user record: %m", path, line, column); - log_notice("Specified user '%s' exists already, try again.", name); - return false; + return register_home_common(bus, v); } -static int create_interactively(void) { - _cleanup_free_ char *username = NULL; +static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - if (!arg_prompt_new_user) { - log_debug("Prompting for user creation was not requested."); - return 0; - } - - /* Needs to be called before mute_console or it will garble the screen */ - (void) plymouth_hide_splash(); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; - (void) mute_console(&mute_console_link); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); + if (arg_identity) { + if (argc > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not accepting an arguments if --identity= is specified, refusing."); - if (arg_chrome) - chrome_show("Create a User Account", /* bottom= */ NULL); + return register_home_one(bus, /* f= */ NULL, arg_identity); + } - DEFER_VOID_CALL(chrome_hide); + if (argc == 1 || (argc == 2 && streq(argv[1], "-"))) + return register_home_one(bus, /* f= */ stdin, ""); - if (emoji_enabled()) { - fputs(glyph(GLYPH_HOME), stdout); - putchar(' '); + r = 0; + STRV_FOREACH(i, strv_skip(argv, 1)) { + if (streq(*i, "-")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing reading from standard input if multiple user records are specified."); + + RET_GATHER(r, register_home_one(bus, /* f= */ NULL, *i)); } - printf("Please create your user account!\n\n"); - r = prompt_loop("Please enter user name to create", - GLYPH_IDCARD, - /* menu= */ NULL, - /* accepted= */ NULL, - /* ellipsize_percentage= */ 60, - /* n_columns= */ 3, - /* column_width= */ 20, - username_is_ok, - /* refresh= */ NULL, - /* userdata= */ NULL, - PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, - &username); + return r; +} + +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; + r = acquire_bus(&bus); if (r < 0) return r; - if (isempty(username)) - return 0; - r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + int ret = 0; + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "UnregisterHome"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to unregister home: %s", bus_error_message(&error, r))); + } + + return ret; +} + +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; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to set userName field: %m"); + return r; - /* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is - * really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no" - * because after all the tool is called from the boot process, and not from an interactive - * shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we - * don't need to hard enforce some policy on password strength some organization or OS vendor - * requires. Note that this just disables the *strict* enforcement of the password policy. Even with - * this disabled we'll still tell the user in the UI that the password is too weak and suggest better - * ones, even if we then accept the weak ones if the user insists, by repeating it. */ - r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + r = bus_call_method(bus, bus_mgr, "ListSigningKeys", &error, &reply, NULL); if (r < 0) - return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); + return log_error_errno(r, "Failed to list signing keys: %s", bus_error_message(&error, r)); - if (arg_prompt_groups) { - _cleanup_strv_free_ char **groups = NULL; + _cleanup_(table_unrefp) Table *table = table_new("name", "key"); + if (!table) + return log_oom(); - putchar('\n'); + r = sd_bus_message_enter_container(reply, 'a', "(sst)"); + if (r < 0) + return bus_log_parse_error(r); - r = prompt_groups(username, &groups); + for (;;) { + const char *name, *pem; + + r = sd_bus_message_read(reply, "(sst)", &name, &pem, NULL); if (r < 0) - return r; + return bus_log_parse_error(r); + if (r == 0) + break; - if (!strv_isempty(groups)) { - r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + _cleanup_free_ char *h = NULL; + if (!sd_json_format_enabled(arg_json_format_flags)) { + /* 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 log_error_errno(r, "Failed to set memberOf field: %m"); + 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 = sym_i2d_PUBKEY(key, (unsigned char**) &der); + if (n < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to encode key as DER."); + + ssize_t m = base64mem(der, MIN(n, 64), &h); + if (m < 0) + return log_oom(); + if (n > 64) /* check if we truncated the original version */ + if (!strextend(&h, glyph(GLYPH_ELLIPSIS))) + return log_oom(); } + + r = table_add_many( + table, + TABLE_STRING, name, + TABLE_STRING, h ?: pem); + if (r < 0) + return table_log_add_error(r); } - if (arg_prompt_shell) { - _cleanup_free_ char *shell = NULL; + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); - putchar('\n'); + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); - r = prompt_shell(username, &shell); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return r; - - if (!isempty(shell)) { - log_info("Selected %s as the shell for user %s", shell, username); - - r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); - if (r < 0) - return log_error_errno(r, "Failed to set shell field: %m"); - } } - putchar('\n'); - - r = create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false); - if (r < 0) - return r; + if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + if (table_isempty(table)) + printf("No signing keys.\n"); + else + printf("\n%zu signing keys listed.\n", table_get_rows(table) - 1); + } - log_info("Successfully created account '%s'.", username); return 0; } -static int add_signing_keys_from_credentials(void); - -static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { +static int verb_get_signing_key(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 - * tool. */ - - bool enabled; - r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); - if (r > 0 && !enabled) { - log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); - arg_prompt_new_user = false; - } + return r; + char **keys = argc >= 2 ? strv_skip(argv, 1) : STRV_MAKE("local.public"); int ret = 0; + STRV_FOREACH(k, keys) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + r = bus_call_method(bus, bus_mgr, "GetSigningKey", &error, &reply, "s", *k); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to get signing key '%s': %s", *k, bus_error_message(&error, r))); + continue; + } - RET_GATHER(ret, add_signing_keys_from_credentials()); - - r = create_or_register_from_credentials(); - RET_GATHER(ret, r); - bool existing_users = r > 0; - - r = getenv_bool("SYSTEMD_HOME_FIRSTBOOT_OVERRIDE"); - if (r == 0) - return 0; - if (r < 0) { - if (r != -ENXIO) - log_warning_errno(r, "Failed to parse $SYSTEMD_HOME_FIRSTBOOT_OVERRIDE, ignoring: %m"); + const char *pem; + r = sd_bus_message_read(reply, "st", &pem, NULL); + if (r < 0) { + RET_GATHER(ret, bus_log_parse_error(r)); + continue; + } - if (!existing_users) { - r = has_regular_user(); - if (r < 0) - return r; + fputs(pem, stdout); + if (!endswith(pem, "\n")) + fputc('\n', stdout); - existing_users = r > 0; - } - if (existing_users) { - log_info("Regular user already present in user database, skipping interactive user creation."); - return 0; - } + fflush(stdout); } - RET_GATHER(ret, create_interactively()); return ret; } -#define drop_from_identity(...) _drop_from_identity(STRV_MAKE(__VA_ARGS__)) - -static int _drop_from_identity(char **fields) { +static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { int r; - /* If we are called to update an identity record and drop some field, let's keep track of what to - * remove from the old record */ - r = strv_extend_strv(&arg_identity_filter, fields, /* filter_duplicates= */ true); - if (r < 0) - return log_oom(); - - /* Let's also drop the field if it was previously set to a new value on the same command line */ - r = sd_json_variant_filter(&arg_identity_extra, fields); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); + assert_se(bus); + assert_se(fn); + assert_se(key); - r = sd_json_variant_filter(&arg_identity_extra_this_machine, fields); + _cleanup_free_ char *pem = NULL; + r = read_full_stream(key, &pem, /* ret_size= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return log_error_errno(r, "Failed to read key '%s': %m", fn); - r = sd_json_variant_filter(&arg_identity_extra_privileged, fields); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_mgr, "AddSigningKey", &error, /* ret_reply= */ NULL, "sst", fn, pem, UINT64_C(0)); if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return log_error_errno(r, "Failed to add signing key '%s': %s", fn, bus_error_message(&error, r)); return 0; } -static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_strv_free_ char **l = NULL, **add = NULL; +static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(identity); - assert(field); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - if (isempty(arg)) - return drop_from_identity(field); + int ret = EXIT_SUCCESS; + if (argc < 2 || streq(argv[1], "-")) { + if (!arg_key_name) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key name must be specified via --key-name= when reading key from standard input, refusing."); - if (arg[0] == '@') { - /* If prefixed with '@', read from a file */ + RET_GATHER(ret, add_signing_key_one(bus, arg_key_name, stdin)); + } else { + /* Refuse if more han one key is specified in combination with --key-name= */ + if (argc >= 3 && arg_key_name) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--key-name= is not supported if multiple signing keys are specified, refusing."); - _cleanup_fclose_ FILE *f = fopen(arg + 1, "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", arg + 1); + STRV_FOREACH(k, strv_skip(argv, 1)) { - for (;;) { - _cleanup_free_ char *line = NULL; + if (streq(*k, "-")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing to read from standard input if multiple keys are specified."); - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return log_error_errno(r, "Failed to read from '%s': %m", arg + 1); - if (r == 0) - break; + _cleanup_free_ char *fn = NULL; + if (!arg_key_name) { + r = path_extract_filename(*k, &fn); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to extract filename from path '%s': %m", *k)); + continue; + } + } - if (isempty(line) || line[0] == '#') + _cleanup_fclose_ FILE *f = fopen(*k, "re"); + if (!f) { + RET_GATHER(ret, log_error_errno(errno, "Failed to open '%s': %m", *k)); continue; + } - r = strv_consume(&add, TAKE_PTR(line)); - if (r < 0) - return log_oom(); + RET_GATHER(ret, add_signing_key_one(bus, fn ?: arg_key_name, f)); } - } else { - /* Otherwise, assume it's a literal key. Let's do some superficial checks - * before accepting it though. */ + } - if (string_has_cc(arg, NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Authorized key contains control characters, refusing."); - if (arg[0] == '#') - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); + return ret; +} - add = strv_new(arg); - if (!add) - return log_oom(); - } +static int add_signing_keys_from_credentials(void) { + int r; - v = sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); - if (v) { - r = sd_json_variant_strv(v, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse %s list: %m", field); - } + _cleanup_close_ int fd = open_credentials_dir(); + if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ + return 0; + if (fd < 0) + return log_error_errno(fd, "Failed to open credentials directory: %m"); - r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to enumerate credentials: %m"); - v = sd_json_variant_unref(v); + int ret = 0; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + if (de->d_type != DT_REG) + continue; - r = sd_json_variant_new_array_strv(&v, l); - if (r < 0) - return log_oom(); + const char *e = startswith(de->d_name, "home.add-signing-key."); + if (!e) + continue; - r = sd_json_variant_set_field(identity, field, v); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + if (!filename_is_valid(e)) + continue; - return 0; + if (!bus) { + r = acquire_bus(&bus); + if (r < 0) + return r; + } + + _cleanup_fclose_ FILE *f = NULL; + r = xfopenat(fd, de->d_name, "re", O_NOFOLLOW, &f); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to open credential '%s': %m", de->d_name)); + continue; + } + + RET_GATHER(ret, add_signing_key_one(bus, e, f)); + } + + return ret; } -static int parse_string_field(sd_json_variant **identity, const char *field, const char *arg) { +static int remove_signing_key_one(sd_bus *bus, const char *fn) { int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + assert_se(bus); + assert_se(fn); - r = sd_json_variant_set_field_string(identity, field, arg); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_mgr, "RemoveSigningKey", &error, /* ret_reply= */ NULL, "st", fn, UINT64_C(0)); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to remove signing key '%s': %s", fn, bus_error_message(&error, r)); + return 0; } -static int parse_home_directory_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_free_ char *hd = NULL; +static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(identity); - assert(field); - - if (!isempty(arg)) { - r = parse_path_argument(arg, /* suppress_root= */ false, &hd); - if (r < 0) - return r; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - if (!valid_home(hd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); - } + r = EXIT_SUCCESS; + STRV_FOREACH(k, strv_skip(argv, 1)) + RET_GATHER(r, remove_signing_key_one(bus, *k)); - return parse_string_field(identity, field, hd); + return r; } -static int parse_realm_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; +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; - assert(identity); - assert(field); + r = acquire_bus(&bus); + if (r < 0) + return r; - if (!isempty(arg)) { - r = dns_name_is_valid(arg); + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "LockHome"); if (r < 0) - return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", arg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", arg); + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; + } } - return parse_string_field(identity, field, arg); + return ret; } -static int parse_path_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_free_ char *v = NULL; - int r; +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; - assert(identity); - assert(field); + r = acquire_bus(&bus); + if (r < 0) + return r; - if (!isempty(arg)) { - r = parse_path_argument(arg, /* suppress_root= */ false, &v); + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL; + + r = acquire_passed_secrets(*i, &secret); if (r < 0) return r; - } - return parse_string_field(identity, field, v); -} + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; -static int parse_filename_field(sd_json_variant **identity, const char *field, const char *arg) { - assert(identity); - assert(field); + r = bus_message_new_method_call(bus, &m, bus_mgr, "UnlockHome"); + if (r < 0) + return bus_log_create_error(r); - if (!isempty(arg) && !filename_is_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for %s field not a valid filename: %s", field, arg); + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); - return parse_string_field(identity, field, arg); + r = bus_message_append_secret(m, secret); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + r = handle_generic_user_record_error(argv[1], secret, &error, r, false); + if (r < 0) { + if (ret == 0) + ret = r; + + break; + } + } else + break; + } + } + + return ret; } -static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { +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; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + r = acquire_bus(&bus); + if (r < 0) + return r; - unsigned n; - r = safe_atou(arg, &n); + r = bus_message_new_method_call(bus, &m, bus_mgr, "LockAllHomes"); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return bus_log_create_error(r); - r = sd_json_variant_set_field_unsigned(identity, field, n); + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r)); + return 0; } -static int parse_u64_field(sd_json_variant **identity, const char *field, const char *arg) { +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; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); - - uint64_t n; - r = safe_atou64(arg, &n); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return r; - r = sd_json_variant_set_field_unsigned(identity, field, n); + r = bus_message_new_method_call(bus, &m, bus_mgr, "Rebalance"); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_REBALANCE_NOT_NEEDED)) + log_info("No homes needed rebalancing."); + else + return log_error_errno(r, "Failed to rebalance: %s", bus_error_message(&error, r)); + } else + log_info("Completed rebalancing."); + return 0; } -static int parse_size_field(sd_json_variant **identity, const char *field, const char *arg) { +static int create_or_register_from_credentials(void) { int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); - - uint64_t n; - r = parse_size(arg, 1024, &n); - if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + _cleanup_close_ int fd = open_credentials_dir(); + if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ + return 0; + if (fd < 0) + return log_error_errno(fd, "Failed to open credentials directory: %m"); - r = sd_json_variant_set_field_unsigned(identity, field, n); + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + return log_error_errno(r, "Failed to enumerate credentials: %m"); -static int parse_boolean_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + int ret = 0, n_processed = 0; + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + if (de->d_type != DT_REG) + continue; - assert(identity); - assert(field); + enum { + OPERATION_CREATE, + OPERATION_REGISTER, + } op; + const char *e; + if ((e = startswith(de->d_name, "home.create."))) + op = OPERATION_CREATE; + else if ((e = startswith(de->d_name, "home.register."))) + op = OPERATION_REGISTER; + else + continue; - if (isempty(arg)) - return drop_from_identity(field); + if (!valid_user_group_name(e, /* flags= */ 0)) { + log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); + continue; + } - r = parse_boolean(arg); - if (r < 0) - return log_error_errno(r, "Failed to parse boolean parameter %s: %s", field, arg); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *identity = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse_file_at( + /* f= */ NULL, + fd, + de->d_name, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, + &identity, + &line, + &column); + if (r < 0) { + log_warning_errno(r, "[%s:%u:%u] Failed to parse user record in credential, ignoring: %m", de->d_name, line, column); + continue; + } - r = sd_json_variant_set_field_boolean(identity, field, r > 0); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + sd_json_variant *un = sd_json_variant_by_key(identity, "userName"); + if (un) { + if (!sd_json_variant_is_string(un)) { + log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name); + continue; + } -static int parse_mode_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + if (!streq(sd_json_variant_string(un), e)) { + log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, sd_json_variant_string(un), e); + continue; + } + } else { + r = sd_json_variant_set_field_string(&identity, "userName", e); + if (r < 0) + return log_warning_errno(r, "Failed to set userName field: %m"); + } - assert(identity); - assert(field); + log_notice("Processing user '%s' from credentials.", e); - if (isempty(arg)) - return drop_from_identity(field); + if (op == OPERATION_CREATE) + r = create_home_common(identity, /* show_enforce_password_policy_hint= */ false); + else + r = register_home_common(/* bus= */ NULL, identity); + if (r >= 0) + n_processed++; - mode_t mode; - r = parse_mode(arg, &mode); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", arg); + RET_GATHER(ret, r); + } - r = sd_json_variant_set_field_unsigned(identity, field, mode); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + return ret < 0 ? ret : n_processed; } -static int parse_timestamp_field(sd_json_variant **identity, const char *field, const char *arg) { +static int has_regular_user(void) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + UserDBMatch match = USERDB_MATCH_NULL; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); - usec_t n; - r = parse_timestamp(arg, &n); + r = userdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return log_error_errno(r, "Failed to create user enumerator: %m"); - r = sd_json_variant_set_field_unsigned(identity, field, n); + r = userdb_iterator_get(iterator, &match, /* ret= */ NULL); + if (r == -ESRCH) + return false; if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + return log_error_errno(r, "Failed to enumerate users: %m"); + + return true; } -static int parse_time_field(sd_json_variant **identity, const char *field, const char *arg) { +static int username_is_ok(const char *name, void *userdata) { int r; - assert(identity); - assert(field); + assert(name); - if (isempty(arg)) - return drop_from_identity(field); + if (!valid_user_group_name(name, /* flags= */ 0)) { + log_notice("Specified user name is not a valid UNIX user name, try again: %s", name); + return false; + } - usec_t n; - r = parse_sec(arg, &n); + r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL); + if (r == -ESRCH) + return true; if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", name); - r = sd_json_variant_set_field_unsigned(identity, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + log_notice("Specified user '%s' exists already, try again.", name); + return false; } -static int parse_uid_field(sd_json_variant **identity, const char *field, const char *arg) { - uid_t uid; +static int create_interactively(void) { + _cleanup_free_ char *username = NULL; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + if (!arg_prompt_new_user) { + log_debug("Prompting for user creation was not requested."); + return 0; + } - r = parse_uid(arg, &uid); - if (r < 0) - return log_error_errno(r, "Failed to parse UID '%s'.", arg); + /* Needs to be called before mute_console or it will garble the screen */ + (void) plymouth_hide_splash(); - const char *bad_range = - uid_is_system(uid) ? "in system range" : - uid_is_greeter(uid) ? "in greeter range" : - uid_is_dynamic(uid) ? "in dynamic ragne" : - uid == UID_NOBODY ? "nobody UID" : NULL; - if (bad_range) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID "UID_FMT" is %s, refusing.", uid, bad_range); + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + (void) mute_console(&mute_console_link); - r = sd_json_variant_set_field_unsigned(identity, field, uid); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); -static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { - int nc, r; + if (arg_chrome) + chrome_show("Create a User Account", /* bottom= */ NULL); - assert(identity); - assert(field); + DEFER_VOID_CALL(chrome_hide); - if (isempty(arg)) - return drop_from_identity(field); + if (emoji_enabled()) { + fputs(glyph(GLYPH_HOME), stdout); + putchar(' '); + } + printf("Please create your user account!\n\n"); - r = parse_nice(arg, &nc); + r = prompt_loop("Please enter user name to create", + GLYPH_IDCARD, + /* menu= */ NULL, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 60, + /* n_columns= */ 3, + /* column_width= */ 20, + username_is_ok, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, + &username); if (r < 0) - return log_error_errno(r, "Failed to parse nice level '%s': %m", arg); + return r; + if (isempty(username)) + return 0; - r = sd_json_variant_set_field_integer(identity, field, nc); + r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + return log_error_errno(r, "Failed to set userName field: %m"); -static int parse_auto_resize_mode_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + /* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is + * really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no" + * because after all the tool is called from the boot process, and not from an interactive + * shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we + * don't need to hard enforce some policy on password strength some organization or OS vendor + * requires. Note that this just disables the *strict* enforcement of the password policy. Even with + * this disabled we'll still tell the user in the UI that the password is too weak and suggest better + * ones, even if we then accept the weak ones if the user insists, by repeating it. */ + r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + if (r < 0) + return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); - assert(identity); - assert(field); + if (arg_prompt_groups) { + _cleanup_strv_free_ char **groups = NULL; - if (!isempty(arg)) { - r = auto_resize_mode_from_string(arg); - if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - arg = auto_resize_mode_to_string(r); - } + putchar('\n'); - return parse_string_field(identity, field, arg); -} + r = prompt_groups(username, &groups); + if (r < 0) + return r; -static int parse_rebalance_weight(sd_json_variant **identity, const char *field, const char *arg) { - int r; + if (!strv_isempty(groups)) { + r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + if (r < 0) + return log_error_errno(r, "Failed to set memberOf field: %m"); + } + } - assert(identity); - assert(field); + if (arg_prompt_shell) { + _cleanup_free_ char *shell = NULL; - if (isempty(arg)) - return drop_from_identity(field); + putchar('\n'); - uint64_t u; - if (streq(arg, "off")) - u = REBALANCE_WEIGHT_OFF; - else { - r = safe_atou64(arg, &u); + r = prompt_shell(username, &shell); if (r < 0) - return log_error_errno(r, "Failed to parse rebalance weight parameter: %s", arg); + return r; - if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", - REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, - arg); + if (!isempty(shell)) { + log_info("Selected %s as the shell for user %s", shell, username); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + if (r < 0) + return log_error_errno(r, "Failed to set shell field: %m"); + } } - /* Drop from per machine stuff and everywhere */ - r = drop_from_identity(field); + putchar('\n'); + + r = create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false); if (r < 0) return r; - r = sd_json_variant_set_field_unsigned(identity, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + log_info("Successfully created account '%s'.", username); return 0; } -static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { +static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(identity); - assert(field); - - if (isempty(arg)) { - /* Remove all resource limits */ - - r = drop_from_identity(field); - if (r < 0) - return r; + /* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot + * tool. */ - arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); - *identity = sd_json_variant_unref(*identity); - return 0; + bool enabled; + r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled); + if (r < 0) + return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); + if (r > 0 && !enabled) { + log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); + arg_prompt_new_user = false; } - const char *eq = strchr(arg, '='); - if (!eq) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", arg); - - _cleanup_free_ char *s = strndup(arg, eq - arg); - if (!s) - return log_oom(); + int ret = 0; - int limit = rlimit_from_string_harder(s); - if (limit < 0) - return log_error_errno(limit, "Unknown resource limit type: %s", s); + RET_GATHER(ret, add_signing_keys_from_credentials()); - const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(limit)); + r = create_or_register_from_credentials(); + RET_GATHER(ret, r); + bool existing_users = r > 0; - if (isempty(eq + 1)) { - /* Remove only the specific rlimit */ + r = getenv_bool("SYSTEMD_HOME_FIRSTBOOT_OVERRIDE"); + if (r == 0) + return 0; + if (r < 0) { + if (r != -ENXIO) + log_warning_errno(r, "Failed to parse $SYSTEMD_HOME_FIRSTBOOT_OVERRIDE, ignoring: %m"); - r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); - if (r < 0) - return r; + if (!existing_users) { + r = has_regular_user(); + if (r < 0) + return r; - r = sd_json_variant_filter(identity, STRV_MAKE(rlimit_field)); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); - return 0; + existing_users = r > 0; + } + if (existing_users) { + log_info("Regular user already present in user database, skipping interactive user creation."); + return 0; + } } - _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; - struct rlimit rl; + RET_GATHER(ret, create_interactively()); + return ret; +} - r = rlimit_parse(limit, eq + 1, &rl); +#define drop_from_identity(...) _drop_from_identity(STRV_MAKE(__VA_ARGS__)) + +static int _drop_from_identity(char **fields) { + int r; + + /* If we are called to update an identity record and drop some field, let's keep track of what to + * remove from the old record */ + r = strv_extend_strv(&arg_identity_filter, fields, /* filter_duplicates= */ true); if (r < 0) - return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); + return log_oom(); - r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); + /* Let's also drop the field if it was previously set to a new value on the same command line */ + r = sd_json_variant_filter(&arg_identity_extra, fields); if (r < 0) - return log_error_errno(r, "Failed to allocate json variant: %m"); + return log_error_errno(r, "Failed to filter JSON identity data: %m"); - r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); + r = sd_json_variant_filter(&arg_identity_extra_this_machine, fields); if (r < 0) - return log_error_errno(r, "Failed to allocate json variant: %m"); + return log_error_errno(r, "Failed to filter JSON identity data: %m"); - r = sd_json_variant_set_fieldbo(identity, rlimit_field, - SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), - SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); + r = sd_json_variant_filter(&arg_identity_extra_privileged, fields); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return 0; } -static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { +static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **l = NULL, **add = NULL; int r; assert(identity); + assert(field); - if (isempty(arg)) { - r = drop_from_identity("diskSize", "diskSizeRelative", "rebalanceWeight"); - if (r < 0) - return r; + if (isempty(arg)) + return drop_from_identity(field); - arg_disk_size = arg_disk_size_relative = UINT64_MAX; - return 0; - } + if (arg[0] == '@') { + /* If prefixed with '@', read from a file */ - r = parse_permyriad(arg); - if (r < 0) { - r = parse_disk_size(arg, &arg_disk_size); - if (r < 0) - return r; + _cleanup_fclose_ FILE *f = fopen(arg + 1, "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", arg + 1); - r = drop_from_identity("diskSizeRelative"); - if (r < 0) - return r; + for (;;) { + _cleanup_free_ char *line = NULL; - r = sd_json_variant_set_field_unsigned(identity, "diskSize", arg_disk_size); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "diskSize"); + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read from '%s': %m", arg + 1); + if (r == 0) + break; - arg_disk_size_relative = UINT64_MAX; + if (isempty(line) || line[0] == '#') + continue; + + r = strv_consume(&add, TAKE_PTR(line)); + if (r < 0) + return log_oom(); + } } else { - /* Normalize to UINT32_MAX == 100% */ - arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); + /* Otherwise, assume it's a literal key. Let's do some superficial checks + * before accepting it though. */ - r = drop_from_identity("diskSize"); - if (r < 0) - return r; + if (string_has_cc(arg, NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Authorized key contains control characters, refusing."); + if (arg[0] == '#') + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); - r = sd_json_variant_set_field_unsigned(identity, "diskSizeRelative", arg_disk_size_relative); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "diskSizeRelative"); + add = strv_new(arg); + if (!add) + return log_oom(); + } - arg_disk_size = UINT64_MAX; + v = sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); + if (v) { + r = sd_json_variant_strv(v, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse %s list: %m", field); } - /* Automatically turn off the rebalance logic if user configured a size explicitly */ - r = sd_json_variant_set_field_unsigned(identity, "rebalanceWeight", REBALANCE_WEIGHT_OFF); + r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "rebalanceWeight"); - return 0; -} + return log_oom(); -static int parse_sector_size_field(sd_json_variant **identity, const char *field, const char *arg) { - uint64_t ss; - int r; + v = sd_json_variant_unref(v); - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); - - r = parse_sector_size(arg, &ss); + r = sd_json_variant_new_array_strv(&v, l); if (r < 0) - return r; + return log_oom(); - r = sd_json_variant_set_field_unsigned(identity, field, ss); + r = sd_json_variant_set_field(identity, field, v); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_weight_field(sd_json_variant **identity, const char *field, const char *arg) { +static int parse_string_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3376,96 +3188,76 @@ static int parse_weight_field(sd_json_variant **identity, const char *field, con if (isempty(arg)) return drop_from_identity(field); - uint64_t u; - r = safe_atou64(arg, &u); - if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - - if (!CGROUP_WEIGHT_IS_OK(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Weight %" PRIu64 " is out of valid range for field %s.", u, field); - - r = sd_json_variant_set_field_unsigned(identity, field, u); + r = sd_json_variant_set_field_string(identity, field, arg); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_strv_free_ char **l = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; +static int parse_home_directory_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *hd = NULL; int r; assert(identity); assert(field); - if (isempty(arg)) - return drop_from_identity(field); - - sd_json_variant *e = sd_json_variant_by_key(*identity, field); - if (e) { - r = sd_json_variant_strv(e, &l); + if (!isempty(arg)) { + r = parse_path_argument(arg, /* suppress_root= */ false, &hd); if (r < 0) - return log_error_errno(r, "Failed to parse JSON environment field: %m"); + return r; + + if (!valid_home(hd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); } - r = strv_env_replace_strdup_passthrough(&l, arg); - if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + return parse_string_field(identity, field, hd); +} - strv_sort(l); +static int parse_realm_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - r = sd_json_variant_new_array_strv(&ne, l); - if (r < 0) - return log_error_errno(r, "Failed to allocate json list: %m"); + assert(identity); + assert(field); - r = sd_json_variant_set_field(identity, field, ne); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + if (!isempty(arg)) { + r = dns_name_is_valid(arg); + if (r < 0) + return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", arg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", arg); + } + + return parse_string_field(identity, field, arg); } -static int parse_language_field(char ***languages, const char *arg) { +static int parse_path_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *v = NULL; int r; - assert(languages); + assert(identity); + assert(field); - if (isempty(arg)) { - r = drop_from_identity("preferredLanguage", "additionalLanguages"); + if (!isempty(arg)) { + r = parse_path_argument(arg, /* suppress_root= */ false, &v); if (r < 0) return r; - - strv_freep(languages); - return 0; } - for (const char *p = arg;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",:", /* flags= */ 0); - if (r < 0) - return log_error_errno(r, "Failed to parse locale list: %m"); - if (r == 0) - return 0; - - if (!locale_is_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); + return parse_string_field(identity, field, v); +} - if (locale_is_installed(word) <= 0) - log_warning("Locale '%s' is not installed, accepting anyway.", word); +static int parse_filename_field(sd_json_variant **identity, const char *field, const char *arg) { + assert(identity); + assert(field); - r = strv_consume(languages, TAKE_PTR(word)); - if (r < 0) - return log_oom(); + if (!isempty(arg) && !filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for %s field not a valid filename: %s", field, arg); - strv_uniq(*languages); - } + return parse_string_field(identity, field, arg); } -static int parse_group_field( - sd_json_variant **identity, - const char *field, - const char *arg) { +static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3474,1848 +3266,2054 @@ static int parse_group_field( if (isempty(arg)) return drop_from_identity(field); - for (const char *p = arg;;) { - _cleanup_free_ char *word = NULL; - _cleanup_strv_free_ char **list = NULL; - - r = extract_first_word(&p, &word, ",", /* flags= */ 0); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); - if (r == 0) - return 0; - - if (!valid_user_group_name(word, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); + unsigned n; + r = safe_atou(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = - sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - r = sd_json_variant_strv(mo, &list); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); +static int parse_u64_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - r = strv_extend(&list, word); - if (r < 0) - return log_oom(); + assert(identity); + assert(field); - strv_sort_uniq(list); + if (isempty(arg)) + return drop_from_identity(field); - mo = sd_json_variant_unref(mo); - r = sd_json_variant_new_array_strv(&mo, list); - if (r < 0) - return log_error_errno(r, "Failed to allocate json list: %m"); + uint64_t n; + r = safe_atou64(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = sd_json_variant_set_field(identity, field, mo); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - } + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_capability_set_field( - sd_json_variant **identity, - uint64_t *capability_set, - const char *field, - const char *arg) { - - _cleanup_strv_free_ char **l = NULL; +static int parse_size_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); - assert(capability_set); assert(field); - assert(arg); - - r = parse_capability_set(arg, CAP_MASK_UNSET, capability_set); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", arg); - if (r < 0) - return log_error_errno(r, "Failed to parse capability string '%s': %m", arg); - if (*capability_set == CAP_MASK_UNSET) + if (isempty(arg)) return drop_from_identity(field); - if (capability_set_to_strv(*capability_set, &l) < 0) - return log_oom(); + uint64_t n; + r = parse_size(arg, 1024, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = sd_json_variant_set_field_strv(identity, field, l); + r = sd_json_variant_set_field_unsigned(identity, field, n); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_tmpfs_limit_field( - sd_json_variant **identity, - const char *field, - const char *field_scale, - const char *arg) { +static int parse_boolean_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); assert(field); - assert(field_scale); if (isempty(arg)) - return drop_from_identity(field, field_scale); + return drop_from_identity(field); - r = parse_permyriad(arg); - if (r < 0) { - uint64_t u; + r = parse_boolean(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean parameter %s: %s", field, arg); - r = parse_size(arg, 1024, &u); - if (r < 0) - return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, arg); + r = sd_json_variant_set_field_boolean(identity, field, r > 0); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - r = sd_json_variant_set_field_unsigned(identity, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); +static int parse_mode_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - return drop_from_identity(field_scale); - } + assert(identity); + assert(field); - r = sd_json_variant_set_field_unsigned(identity, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + if (isempty(arg)) + return drop_from_identity(field); + + mode_t mode; + r = parse_mode(arg, &mode); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field_scale); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", arg); - return drop_from_identity(field); + r = sd_json_variant_set_field_unsigned(identity, field, mode); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_pkcs11_token_uri_field(const char *arg) { +static int parse_timestamp_field(sd_json_variant **identity, const char *field, const char *arg) { int r; - assert(arg); + assert(identity); + assert(field); - if (streq(arg, "list")) - return pkcs11_list_tokens(); + if (isempty(arg)) + return drop_from_identity(field); - /* If --pkcs11-token-uri= is specified we always drop everything old */ - r = drop_from_identity("pkcs11TokenUri", "pkcs11EncryptedKey"); + usec_t n; + r = parse_timestamp(arg, &n); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - if (isempty(arg)) { - arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); - return 1; - } + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - if (streq(arg, "auto")) { - char *found; - r = pkcs11_find_token_auto(&found); - if (r < 0) - return r; +static int parse_time_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - r = strv_consume(&arg_pkcs11_token_uri, found); - } else { - if (!pkcs11_uri_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); + assert(identity); + assert(field); - r = strv_extend(&arg_pkcs11_token_uri, arg); - } + if (isempty(arg)) + return drop_from_identity(field); + + usec_t n; + r = parse_sec(arg, &n); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - strv_uniq(arg_pkcs11_token_uri); - return 1; + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_fido2_device_field(const char *arg) { +static int parse_uid_field(sd_json_variant **identity, const char *field, const char *arg) { + uid_t uid; int r; - assert(arg); + assert(identity); + assert(field); - if (streq(arg, "list")) - return fido2_list_devices(); + if (isempty(arg)) + return drop_from_identity(field); - r = drop_from_identity("fido2HmacCredential", "fido2HmacSalt"); + r = parse_uid(arg, &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse UID '%s'.", arg); + + const char *bad_range = + uid_is_system(uid) ? "in system range" : + uid_is_greeter(uid) ? "in greeter range" : + uid_is_dynamic(uid) ? "in dynamic ragne" : + uid == UID_NOBODY ? "nobody UID" : NULL; + if (bad_range) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID "UID_FMT" is %s, refusing.", uid, bad_range); + + r = sd_json_variant_set_field_unsigned(identity, field, uid); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { + int nc, r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_nice(arg, &nc); + if (r < 0) + return log_error_errno(r, "Failed to parse nice level '%s': %m", arg); + + r = sd_json_variant_set_field_integer(identity, field, nc); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_auto_resize_mode_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (!isempty(arg)) { + r = auto_resize_mode_from_string(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + arg = auto_resize_mode_to_string(r); + } + + return parse_string_field(identity, field, arg); +} + +static int parse_rebalance_weight(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t u; + if (streq(arg, "off")) + u = REBALANCE_WEIGHT_OFF; + else { + r = safe_atou64(arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse rebalance weight parameter: %s", arg); + + if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", + REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, + arg); + } + + /* Drop from per machine stuff and everywhere */ + r = drop_from_identity(field); if (r < 0) return r; + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + if (isempty(arg)) { - arg_fido2_device = strv_free(arg_fido2_device); - return 1; + /* Remove all resource limits */ + + r = drop_from_identity(field); + if (r < 0) + return r; + + arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); + *identity = sd_json_variant_unref(*identity); + return 0; } - if (streq(arg, "auto")) { - char *found; - r = fido2_find_device_auto(&found); + const char *eq = strchr(arg, '='); + if (!eq) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", arg); + + _cleanup_free_ char *s = strndup(arg, eq - arg); + if (!s) + return log_oom(); + + int limit = rlimit_from_string_harder(s); + if (limit < 0) + return log_error_errno(limit, "Unknown resource limit type: %s", s); + + const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(limit)); + + if (isempty(eq + 1)) { + /* Remove only the specific rlimit */ + + r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); if (r < 0) return r; - r = strv_consume(&arg_fido2_device, found); - } else - r = strv_extend(&arg_fido2_device, arg); + r = sd_json_variant_filter(identity, STRV_MAKE(rlimit_field)); + if (r < 0) + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; + struct rlimit rl; + + r = rlimit_parse(limit, eq + 1, &rl); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); - strv_uniq(arg_fido2_device); - return 1; + r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); + + r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); + + r = sd_json_variant_set_fieldbo(identity, rlimit_field, + SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), + SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); + return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; +static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { int r; - pager_open(arg_pager_flags); + assert(identity); - r = terminal_urlify_man("homectl", "1", &link); + if (isempty(arg)) { + r = drop_from_identity("diskSize", "diskSizeRelative", "rebalanceWeight"); + if (r < 0) + return r; + + arg_disk_size = arg_disk_size_relative = UINT64_MAX; + return 0; + } + + r = parse_permyriad(arg); + if (r < 0) { + r = parse_disk_size(arg, &arg_disk_size); + if (r < 0) + return r; + + r = drop_from_identity("diskSizeRelative"); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, "diskSize", arg_disk_size); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "diskSize"); + + arg_disk_size_relative = UINT64_MAX; + } else { + /* Normalize to UINT32_MAX == 100% */ + arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); + + r = drop_from_identity("diskSize"); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, "diskSizeRelative", arg_disk_size_relative); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "diskSizeRelative"); + + arg_disk_size = UINT64_MAX; + } + + /* Automatically turn off the rebalance logic if user configured a size explicitly */ + r = sd_json_variant_set_field_unsigned(identity, "rebalanceWeight", REBALANCE_WEIGHT_OFF); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to set %s field: %m", "rebalanceWeight"); + return 0; +} - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sCreate, manipulate or inspect home directories.%3$s\n" - "\n%4$sBasic User Manipulation Commands:%5$s\n" - " list List home areas\n" - " inspect USER… Inspect a home area\n" - " create USER Create a home area\n" - " update USER Update a home area\n" - " passwd USER Change password of a home area\n" - " resize USER SIZE Resize a home area\n" - " remove USER… Remove a home area\n" - "\n%4$sAdvanced User Manipulation Commands:%5$s\n" - " activate USER… Activate a home area\n" - " deactivate USER… Deactivate a home area\n" - " deactivate-all Deactivate all active home areas\n" - " with USER [COMMAND…] Run shell or command with access to a home area\n" - " authenticate USER… Authenticate a home area\n" - "\n%4$sUser Migration Commands:%5$s\n" - " adopt PATH… Add an existing home area on this system\n" - " register PATH… Register a user record locally\n" - " unregister USER… Unregister a user record locally\n" - "\n%4$sSigning Keys Commands:%5$s\n" - " list-signing-keys List home signing keys\n" - " get-signing-key [NAME…] Get a named home signing key\n" - " add-signing-key FILE… Add home signing key\n" - " remove-signing-key NAME… Remove home signing key\n" - "\n%4$sLock/Unlock Commands:%5$s\n" - " lock USER… Temporarily lock an active home area\n" - " unlock USER… Unlock a temporarily locked home area\n" - " lock-all Lock all suitable home areas\n" - "\n%4$sOther Commands:%5$s\n" - " rebalance Rebalance free space between home areas\n" - " firstboot Run first-boot home area creation wizard\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" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " --offline Don't update record embedded in home directory\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --identity=PATH Read JSON identity from file\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" - " --export-format= Strip JSON inspection data (full, stripped,\n" - " minimal)\n" - " -E When specified once equals -j --export-format=\n" - " stripped, when specified twice equals\n" - " -j --export-format=minimal\n" - " --key-name=NAME Key name when adding a signing key\n" - " --seize=no Do not strip existing signatures of user record\n" - " when creating\n" - " --prompt-new-user firstboot: Query user interactively for user\n" - " to create\n" - " --prompt-groups=no In first-boot mode, don't prompt for auxiliary\n" - " group memberships\n" - " --prompt-shell=no In first-boot mode, don't prompt for shells\n" - " --chrome=no In first-boot mode, don't show colour bar at top\n" - " and bottom of terminal\n" - " --mute-console=yes In first-boot mode, tell kernel/PID 1 to not\n" - " write to the console while running\n" - "\n%4$sGeneral User Record Properties:%5$s\n" - " -c --real-name=REALNAME Real name for user\n" - " --realm=REALM Realm to create user in\n" - " --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" - " -G --member-of=GROUP Add user to group\n" - " --capability-bounding-set=CAPS\n" - " Bounding POSIX capability set\n" - " --capability-ambient-set=CAPS\n" - " Ambient POSIX capability set\n" - " --access-mode=MODE User home directory access mode\n" - " --umask=MODE Umask for user when logging in\n" - " --skel=PATH Skeleton directory to use\n" - " --shell=PATH Shell for account\n" - " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" - " --timezone=TIMEZONE Set a time-zone\n" - " --language=LOCALE Set preferred languages\n" - " --default-area=AREA Select default area\n" - "\n%4$sAuthentication User Record Properties:%5$s\n" - " --ssh-authorized-keys=KEYS\n" - " Specify SSH public keys\n" - " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" - " private key and matching X.509 certificate\n" - " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" - " extension\n" - " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the\n" - " account\n" - " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the\n" - " account\n" - " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock\n" - " the account\n" - " --recovery-key=BOOL Add a recovery key\n" - "\n%4$sBlob Directory User Record Properties:%5$s\n" - " -b --blob=[FILENAME=]PATH\n" - " Path to a replacement blob directory, or replace\n" - " an individual files in the blob directory.\n" - " --avatar=PATH Path to user avatar picture\n" - " --login-background=PATH Path to user login background picture\n" - "\n%4$sAccount Management User Record Properties:%5$s\n" - " --locked=BOOL Set locked account state\n" - " --not-before=TIMESTAMP Do not allow logins before\n" - " --not-after=TIMESTAMP Do not allow logins after\n" - " --rate-limit-interval=SECS\n" - " Login rate-limit interval in seconds\n" - " --rate-limit-burst=NUMBER\n" - " Login rate-limit attempts per interval\n" - "\n%4$sPassword Policy User Record Properties:%5$s\n" - " --password-hint=HINT Set Password hint\n" - " --enforce-password-policy=BOOL\n" - " Control whether to enforce system's password\n" - " policy for this user\n" - " -P Same as --enforce-password-policy=no\n" - " --password-change-now=BOOL\n" - " Require the password to be changed on next login\n" - " --password-change-min=TIME\n" - " Require minimum time between password changes\n" - " --password-change-max=TIME\n" - " Require maximum time between password changes\n" - " --password-change-warn=TIME\n" - " How much time to warn before password expiry\n" - " --password-change-inactive=TIME\n" - " How much time to block password after expiry\n" - "\n%4$sResource Management User Record Properties:%5$s\n" - " --disk-size=BYTES Size to assign the user on disk\n" - " --nice=NICE Nice level for user\n" - " --rlimit=LIMIT=VALUE[:VALUE]\n" - " Set resource limits\n" - " --tasks-max=MAX Set maximum number of per-user tasks\n" - " --memory-high=BYTES Set high memory threshold in bytes\n" - " --memory-max=BYTES Set maximum memory limit\n" - " --cpu-weight=WEIGHT Set CPU weight\n" - " --io-weight=WEIGHT Set IO weight\n" - " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" - " --dev-shm-limit=BYTES|PERCENT\n" - " Set limit on /dev/shm/\n" - "\n%4$sStorage User Record Properties:%5$s\n" - " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" - " subvolume, cifs)\n" - " --image-path=PATH Path to image file/directory\n" - " --drop-caches=BOOL Whether to automatically drop caches on logout\n" - "\n%4$sLUKS Storage User Record Properties:%5$s\n" - " --fs-type=TYPE File system type to use in case of luks\n" - " storage (btrfs, ext4, xfs)\n" - " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" - " when activated (mounted)\n" - " --luks-offline-discard=BOOL\n" - " Whether to trim file on logout\n" - " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" - " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" - " --luks-volume-key-size=BITS\n" - " Volume key size to use for LUKS encryption\n" - " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" - " --luks-pbkdf-hash-algorithm=ALGORITHM\n" - " PBKDF hash algorithm to use\n" - " --luks-pbkdf-time-cost=SECS\n" - " Time cost for PBKDF in seconds\n" - " --luks-pbkdf-memory-cost=BYTES\n" - " Memory cost for PBKDF in bytes\n" - " --luks-pbkdf-parallel-threads=NUMBER\n" - " Number of parallel threads for PKBDF\n" - " --luks-sector-size=BYTES\n" - " Sector size for LUKS encryption in bytes\n" - " --luks-extra-mount-options=OPTIONS\n" - " LUKS extra mount options\n" - " --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n" - " --rebalance-weight=WEIGHT Weight while rebalancing\n" - "\n%4$sMounting User Record Properties:%5$s\n" - " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" - " --nodev=BOOL Control the 'nodev' flag of the home mount\n" - " --noexec=BOOL Control the 'noexec' flag of the home mount\n" - "\n%4$sCIFS User Record Properties:%5$s\n" - " --cifs-domain=DOMAIN CIFS (Windows) domain\n" - " --cifs-user-name=USER CIFS (Windows) user name\n" - " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" - " --cifs-extra-mount-options=OPTIONS\n" - " CIFS (Windows) extra mount options\n" - "\n%4$sLogin Behaviour User Record Properties:%5$s\n" - " --stop-delay=SECS How long to leave user services running after\n" - " logout\n" - " --kill-processes=BOOL Whether to kill user processes when sessions\n" - " terminate\n" - " --auto-login=BOOL Try to log this user in automatically\n" - " --session-launcher=LAUNCHER\n" - " Preferred session launcher file\n" - " --session-type=TYPE Preferred session type\n" - "\nSee the %6$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); +static int parse_sector_size_field(sd_json_variant **identity, const char *field, const char *arg) { + uint64_t ss; + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_sector_size(arg, &ss); + if (r < 0) + return r; + r = sd_json_variant_set_field_unsigned(identity, field, ss); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); +static int parse_weight_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t u; + r = safe_atou64(arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + if (!CGROUP_WEIGHT_IS_OK(u)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Weight %" PRIu64 " is out of valid range for field %s.", u, field); + + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_argv(int argc, char *argv[]) { - _cleanup_strv_free_ char **arg_languages = NULL; +static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; + int r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_OFFLINE, - ARG_REALM, - ARG_ALIAS, - ARG_EMAIL_ADDRESS, - ARG_DISK_SIZE, - ARG_ACCESS_MODE, - ARG_STORAGE, - ARG_FS_TYPE, - ARG_IMAGE_PATH, - ARG_UMASK, - ARG_LUKS_DISCARD, - ARG_LUKS_OFFLINE_DISCARD, - ARG_JSON, - ARG_SETENV, - ARG_TIMEZONE, - ARG_LANGUAGE, - ARG_LOCKED, - ARG_SSH_AUTHORIZED_KEYS, - ARG_LOCATION, - ARG_BIRTH_DATE, - ARG_ICON_NAME, - ARG_PASSWORD_HINT, - ARG_NICE, - ARG_RLIMIT, - ARG_NOT_BEFORE, - ARG_NOT_AFTER, - ARG_LUKS_CIPHER, - ARG_LUKS_CIPHER_MODE, - ARG_LUKS_VOLUME_KEY_SIZE, - ARG_NOSUID, - ARG_NODEV, - ARG_NOEXEC, - ARG_CIFS_DOMAIN, - ARG_CIFS_USER_NAME, - ARG_CIFS_SERVICE, - ARG_CIFS_EXTRA_MOUNT_OPTIONS, - ARG_TASKS_MAX, - ARG_MEMORY_HIGH, - ARG_MEMORY_MAX, - ARG_CPU_WEIGHT, - ARG_IO_WEIGHT, - ARG_LUKS_PBKDF_TYPE, - ARG_LUKS_PBKDF_HASH_ALGORITHM, - ARG_LUKS_PBKDF_FORCE_ITERATIONS, - ARG_LUKS_PBKDF_TIME_COST, - ARG_LUKS_PBKDF_MEMORY_COST, - ARG_LUKS_PBKDF_PARALLEL_THREADS, - ARG_LUKS_SECTOR_SIZE, - ARG_RATE_LIMIT_INTERVAL, - ARG_RATE_LIMIT_BURST, - ARG_STOP_DELAY, - ARG_KILL_PROCESSES, - ARG_ENFORCE_PASSWORD_POLICY, - ARG_PASSWORD_CHANGE_NOW, - ARG_PASSWORD_CHANGE_MIN, - ARG_PASSWORD_CHANGE_MAX, - ARG_PASSWORD_CHANGE_WARN, - ARG_PASSWORD_CHANGE_INACTIVE, - ARG_EXPORT_FORMAT, - ARG_AUTO_LOGIN, - ARG_SESSION_LAUNCHER, - ARG_SESSION_TYPE, - ARG_PKCS11_TOKEN_URI, - ARG_FIDO2_DEVICE, - ARG_FIDO2_WITH_PIN, - ARG_FIDO2_WITH_UP, - ARG_FIDO2_WITH_UV, - ARG_RECOVERY_KEY, - ARG_DROP_CACHES, - ARG_LUKS_EXTRA_MOUNT_OPTIONS, - ARG_AUTO_RESIZE_MODE, - ARG_REBALANCE_WEIGHT, - ARG_FIDO2_CRED_ALG, - ARG_CAPABILITY_BOUNDING_SET, - ARG_CAPABILITY_AMBIENT_SET, - ARG_PROMPT_NEW_USER, - ARG_AVATAR, - ARG_LOGIN_BACKGROUND, - ARG_TMP_LIMIT, - ARG_DEV_SHM_LIMIT, - ARG_DEFAULT_AREA, - ARG_KEY_NAME, - ARG_SEIZE, - ARG_MATCH, - ARG_PROMPT_SHELL, - ARG_PROMPT_GROUPS, - ARG_CHROME, - ARG_MUTE_CONSOLE, - }; - - 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 }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "identity", required_argument, NULL, 'I' }, - { "real-name", required_argument, NULL, 'c' }, - { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "realm", required_argument, NULL, ARG_REALM }, - { "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) */ - { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ - { "member-of", required_argument, NULL, 'G' }, - { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ - { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "language", required_argument, NULL, ARG_LANGUAGE }, - { "locked", required_argument, NULL, ARG_LOCKED }, - { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, - { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, - { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, - { "umask", required_argument, NULL, ARG_UMASK }, - { "nice", required_argument, NULL, ARG_NICE }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, - { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, - { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, - { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, - { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, - { "storage", required_argument, NULL, ARG_STORAGE }, - { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, - { "fs-type", required_argument, NULL, ARG_FS_TYPE }, - { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, - { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, - { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, - { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, - { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, - { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, - { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, - { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, - { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, - { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, - { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, - { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, - { "nosuid", required_argument, NULL, ARG_NOSUID }, - { "nodev", required_argument, NULL, ARG_NODEV }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, - { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, - { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, - { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, - { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, - { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, - { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, - { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, - { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, - { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, - { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, - { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, - { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, - { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, - { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, - { "session-launcher", required_argument, NULL, ARG_SESSION_LAUNCHER, }, - { "session-type", required_argument, NULL, ARG_SESSION_TYPE, }, - { "json", required_argument, NULL, ARG_JSON }, - { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, - { "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-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 }, - { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, - { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, - { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, - { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, - { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, - { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, - { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, - { "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER }, - { "blob", required_argument, NULL, 'b' }, - { "avatar", required_argument, NULL, ARG_AVATAR }, - { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, - { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, - { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, - { "default-area", required_argument, NULL, ARG_DEFAULT_AREA }, - { "key-name", required_argument, NULL, ARG_KEY_NAME }, - { "seize", required_argument, NULL, ARG_SEIZE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "prompt-shell", required_argument, NULL, ARG_PROMPT_SHELL }, - { "prompt-groups", required_argument, NULL, ARG_PROMPT_GROUPS }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; + assert(identity); + assert(field); - int r; + if (isempty(arg)) + return drop_from_identity(field); - /* This points to one of arg_identity_extra, arg_identity_extra_this_machine, - * arg_identity_extra_other_machines, in order to redirect changes on the next property being set to - * this part of the identity, instead of the default. */ - sd_json_variant **match_identity = NULL; + sd_json_variant *e = sd_json_variant_by_key(*identity, field); + if (e) { + r = sd_json_variant_strv(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse JSON environment field: %m"); + } - assert(argc >= 0); - assert(argv); + r = strv_env_replace_strdup_passthrough(&l, arg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); - /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not - * hooked up everywhere let's make it an environment variable only. */ - r = getenv_bool("SYSTEMD_HOME_DRY_RUN"); - if (r >= 0) - arg_dry_run = r; - else if (r != -ENXIO) - log_debug_errno(r, "Unable to parse $SYSTEMD_HOME_DRY_RUN, ignoring: %m"); + strv_sort(l); - for (;;) { - int c; + r = sd_json_variant_new_array_strv(&ne, l); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); - c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPENAT", options, NULL); - if (c < 0) - break; + r = sd_json_variant_set_field(identity, field, ne); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - switch (c) { +static int parse_language_field(char ***languages, const char *arg) { + int r; - case 'h': - return help(); + assert(languages); - case ARG_VERSION: - return version(); + if (isempty(arg)) { + r = drop_from_identity("preferredLanguage", "additionalLanguages"); + if (r < 0) + return r; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + strv_freep(languages); + return 0; + } - case ARG_NO_LEGEND: - arg_legend = false; - break; + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; + r = extract_first_word(&p, &word, ",:", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse locale list: %m"); + if (r == 0) + return 0; - case ARG_OFFLINE: - arg_offline = true; - break; + if (!locale_is_valid(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; + if (locale_is_installed(word) <= 0) + log_warning("Locale '%s' is not installed, accepting anyway.", word); - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; - break; + r = strv_consume(languages, TAKE_PTR(word)); + if (r < 0) + return log_oom(); - case 'I': - arg_identity = optarg; - break; + strv_uniq(*languages); + } +} - case 'c': - if (!isempty(optarg) && !valid_gecos(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid GECOS field '%s'.", optarg); +static int parse_group_field( + sd_json_variant **identity, + const char *field, + const char *arg) { + int r; - r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", optarg); - if (r < 0) - return r; - break; + assert(identity); + assert(field); - case ARG_ALIAS: - r = parse_group_field(&arg_identity_extra, "aliases", optarg); - if (r < 0) - return r; - break; + if (isempty(arg)) + return drop_from_identity(field); - case 'd': - r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); - if (r < 0) - return r; - break; + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; + _cleanup_strv_free_ char **list = NULL; - case ARG_REALM: - r = parse_realm_field(&arg_identity_extra, "realm", optarg); - if (r < 0) - return r; - break; + r = extract_first_word(&p, &word, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); + if (r == 0) + return 0; - case ARG_EMAIL_ADDRESS: - case ARG_LOCATION: - case ARG_ICON_NAME: - case ARG_CIFS_USER_NAME: - case ARG_CIFS_DOMAIN: - case ARG_CIFS_EXTRA_MOUNT_OPTIONS: - case ARG_LUKS_EXTRA_MOUNT_OPTIONS: - case ARG_SESSION_LAUNCHER: - case ARG_SESSION_TYPE: { - const char *field = - c == ARG_EMAIL_ADDRESS ? "emailAddress" : - c == ARG_LOCATION ? "location" : - c == ARG_ICON_NAME ? "iconName" : - c == ARG_CIFS_USER_NAME ? "cifsUserName" : - c == ARG_CIFS_DOMAIN ? "cifsDomain" : - c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" : - c == ARG_LUKS_EXTRA_MOUNT_OPTIONS ? "luksExtraMountOptions" : - c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : - c == ARG_SESSION_TYPE ? "preferredSessionType" : - NULL; - assert(field); + if (!valid_user_group_name(word, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); - r = parse_string_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = + sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); - 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 = sd_json_variant_strv(mo, &list); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); - r = parse_string_field(&arg_identity_extra, "birthDate", optarg); - if (r < 0) - return r; - } - break; + r = strv_extend(&list, word); + if (r < 0) + return log_oom(); - case ARG_CIFS_SERVICE: - if (!isempty(optarg)) { - r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); - } + strv_sort_uniq(list); - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); - if (r < 0) - return r; - break; + mo = sd_json_variant_unref(mo); + r = sd_json_variant_new_array_strv(&mo, list); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); - case ARG_PASSWORD_HINT: - r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); - if (r < 0) - return r; + r = sd_json_variant_set_field(identity, field, mo); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + } +} - string_erase(optarg); - break; +static int parse_capability_set_field( + sd_json_variant **identity, + uint64_t *capability_set, + const char *field, + const char *arg) { - case ARG_NICE: - r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); - if (r < 0) - return r; - break; + _cleanup_strv_free_ char **l = NULL; + int r; - case ARG_RLIMIT: - r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); - if (r < 0) - return r; - break; + assert(identity); + assert(capability_set); + assert(field); + assert(arg); - case 'u': - r = parse_uid_field(&arg_identity_extra, "uid", optarg); - if (r < 0) - return r; - break; + r = parse_capability_set(arg, CAP_MASK_UNSET, capability_set); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", arg); + if (r < 0) + return log_error_errno(r, "Failed to parse capability string '%s': %m", arg); - case 'k': - case ARG_IMAGE_PATH: { - const char *field = c == 'k' ? "skeletonDirectory" : "imagePath"; + if (*capability_set == CAP_MASK_UNSET) + return drop_from_identity(field); - r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); - if (r < 0) - return r; - break; - } + if (capability_set_to_strv(*capability_set, &l) < 0) + return log_oom(); - case 's': - if (!isempty(optarg) && !valid_shell(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Shell '%s' not valid.", optarg); + r = sd_json_variant_set_field_strv(identity, field, l); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", optarg); - if (r < 0) - return r; - break; +static int parse_tmpfs_limit_field( + sd_json_variant **identity, + const char *field, + const char *field_scale, + const char *arg) { + int r; - case ARG_SETENV: - r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", optarg); - if (r < 0) - return r; - break; + assert(identity); + assert(field); + assert(field_scale); - case ARG_TIMEZONE: - if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone '%s' is not valid.", optarg); + if (isempty(arg)) + return drop_from_identity(field, field_scale); - r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", optarg); - if (r < 0) - return r; - break; + r = parse_permyriad(arg); + if (r < 0) { + uint64_t u; + + r = parse_size(arg, 1024, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + + return drop_from_identity(field_scale); + } + + r = sd_json_variant_set_field_unsigned(identity, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field_scale); + + return drop_from_identity(field); +} + +static int parse_pkcs11_token_uri_field(const char *arg) { + int r; + + assert(arg); + + if (streq(arg, "list")) + return pkcs11_list_tokens(); + + /* If --pkcs11-token-uri= is specified we always drop everything old */ + r = drop_from_identity("pkcs11TokenUri", "pkcs11EncryptedKey"); + if (r < 0) + return r; + + if (isempty(arg)) { + arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); + return 1; + } + + if (streq(arg, "auto")) { + char *found; + r = pkcs11_find_token_auto(&found); + if (r < 0) + return r; + + r = strv_consume(&arg_pkcs11_token_uri, found); + } else { + if (!pkcs11_uri_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); + + r = strv_extend(&arg_pkcs11_token_uri, arg); + } + if (r < 0) + return r; + + strv_uniq(arg_pkcs11_token_uri); + return 1; +} + +static int parse_fido2_device_field(const char *arg) { + int r; + + assert(arg); + + if (streq(arg, "list")) + return fido2_list_devices(); + + r = drop_from_identity("fido2HmacCredential", "fido2HmacSalt"); + if (r < 0) + return r; + + if (isempty(arg)) { + arg_fido2_device = strv_free(arg_fido2_device); + return 1; + } + + if (streq(arg, "auto")) { + char *found; + r = fido2_find_device_auto(&found); + if (r < 0) + return r; + + r = strv_consume(&arg_fido2_device, found); + } else + r = strv_extend(&arg_fido2_device, arg); + if (r < 0) + return r; + + strv_uniq(arg_fido2_device); + return 1; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + pager_open(arg_pager_flags); + + r = terminal_urlify_man("homectl", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%2$sCreate, manipulate or inspect home directories.%3$s\n" + "\n%4$sBasic User Manipulation Commands:%5$s\n" + " list List home areas\n" + " inspect USER… Inspect a home area\n" + " create USER Create a home area\n" + " update USER Update a home area\n" + " passwd USER Change password of a home area\n" + " resize USER SIZE Resize a home area\n" + " remove USER… Remove a home area\n" + "\n%4$sAdvanced User Manipulation Commands:%5$s\n" + " activate USER… Activate a home area\n" + " deactivate USER… Deactivate a home area\n" + " deactivate-all Deactivate all active home areas\n" + " with USER [COMMAND…] Run shell or command with access to a home area\n" + " authenticate USER… Authenticate a home area\n" + "\n%4$sUser Migration Commands:%5$s\n" + " adopt PATH… Add an existing home area on this system\n" + " register PATH… Register a user record locally\n" + " unregister USER… Unregister a user record locally\n" + "\n%4$sSigning Keys Commands:%5$s\n" + " list-signing-keys List home signing keys\n" + " get-signing-key [NAME…] Get a named home signing key\n" + " add-signing-key FILE… Add home signing key\n" + " remove-signing-key NAME… Remove home signing key\n" + "\n%4$sLock/Unlock Commands:%5$s\n" + " lock USER… Temporarily lock an active home area\n" + " unlock USER… Unlock a temporarily locked home area\n" + " lock-all Lock all suitable home areas\n" + "\n%4$sOther Commands:%5$s\n" + " rebalance Rebalance free space between home areas\n" + " firstboot Run first-boot home area creation wizard\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" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " --offline Don't update record embedded in home directory\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --identity=PATH Read JSON identity from file\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" + " --export-format= Strip JSON inspection data (full, stripped,\n" + " minimal)\n" + " -E When specified once equals -j --export-format=\n" + " stripped, when specified twice equals\n" + " -j --export-format=minimal\n" + " --key-name=NAME Key name when adding a signing key\n" + " --seize=no Do not strip existing signatures of user record\n" + " when creating\n" + " --prompt-new-user firstboot: Query user interactively for user\n" + " to create\n" + " --prompt-groups=no In first-boot mode, don't prompt for auxiliary\n" + " group memberships\n" + " --prompt-shell=no In first-boot mode, don't prompt for shells\n" + " --chrome=no In first-boot mode, don't show colour bar at top\n" + " and bottom of terminal\n" + " --mute-console=yes In first-boot mode, tell kernel/PID 1 to not\n" + " write to the console while running\n" + "\n%4$sGeneral User Record Properties:%5$s\n" + " -c --real-name=REALNAME Real name for user\n" + " --realm=REALM Realm to create user in\n" + " --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" + " -G --member-of=GROUP Add user to group\n" + " --capability-bounding-set=CAPS\n" + " Bounding POSIX capability set\n" + " --capability-ambient-set=CAPS\n" + " Ambient POSIX capability set\n" + " --access-mode=MODE User home directory access mode\n" + " --umask=MODE Umask for user when logging in\n" + " --skel=PATH Skeleton directory to use\n" + " --shell=PATH Shell for account\n" + " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" + " --timezone=TIMEZONE Set a time-zone\n" + " --language=LOCALE Set preferred languages\n" + " --default-area=AREA Select default area\n" + "\n%4$sAuthentication User Record Properties:%5$s\n" + " --ssh-authorized-keys=KEYS\n" + " Specify SSH public keys\n" + " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" + " private key and matching X.509 certificate\n" + " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" + " extension\n" + " --fido2-with-client-pin=BOOL\n" + " Whether to require entering a PIN to unlock the\n" + " account\n" + " --fido2-with-user-presence=BOOL\n" + " Whether to require user presence to unlock the\n" + " account\n" + " --fido2-with-user-verification=BOOL\n" + " Whether to require user verification to unlock\n" + " the account\n" + " --recovery-key=BOOL Add a recovery key\n" + "\n%4$sBlob Directory User Record Properties:%5$s\n" + " -b --blob=[FILENAME=]PATH\n" + " Path to a replacement blob directory, or replace\n" + " an individual files in the blob directory.\n" + " --avatar=PATH Path to user avatar picture\n" + " --login-background=PATH Path to user login background picture\n" + "\n%4$sAccount Management User Record Properties:%5$s\n" + " --locked=BOOL Set locked account state\n" + " --not-before=TIMESTAMP Do not allow logins before\n" + " --not-after=TIMESTAMP Do not allow logins after\n" + " --rate-limit-interval=SECS\n" + " Login rate-limit interval in seconds\n" + " --rate-limit-burst=NUMBER\n" + " Login rate-limit attempts per interval\n" + "\n%4$sPassword Policy User Record Properties:%5$s\n" + " --password-hint=HINT Set Password hint\n" + " --enforce-password-policy=BOOL\n" + " Control whether to enforce system's password\n" + " policy for this user\n" + " -P Same as --enforce-password-policy=no\n" + " --password-change-now=BOOL\n" + " Require the password to be changed on next login\n" + " --password-change-min=TIME\n" + " Require minimum time between password changes\n" + " --password-change-max=TIME\n" + " Require maximum time between password changes\n" + " --password-change-warn=TIME\n" + " How much time to warn before password expiry\n" + " --password-change-inactive=TIME\n" + " How much time to block password after expiry\n" + "\n%4$sResource Management User Record Properties:%5$s\n" + " --disk-size=BYTES Size to assign the user on disk\n" + " --nice=NICE Nice level for user\n" + " --rlimit=LIMIT=VALUE[:VALUE]\n" + " Set resource limits\n" + " --tasks-max=MAX Set maximum number of per-user tasks\n" + " --memory-high=BYTES Set high memory threshold in bytes\n" + " --memory-max=BYTES Set maximum memory limit\n" + " --cpu-weight=WEIGHT Set CPU weight\n" + " --io-weight=WEIGHT Set IO weight\n" + " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" + " --dev-shm-limit=BYTES|PERCENT\n" + " Set limit on /dev/shm/\n" + "\n%4$sStorage User Record Properties:%5$s\n" + " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" + " subvolume, cifs)\n" + " --image-path=PATH Path to image file/directory\n" + " --drop-caches=BOOL Whether to automatically drop caches on logout\n" + "\n%4$sLUKS Storage User Record Properties:%5$s\n" + " --fs-type=TYPE File system type to use in case of luks\n" + " storage (btrfs, ext4, xfs)\n" + " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" + " when activated (mounted)\n" + " --luks-offline-discard=BOOL\n" + " Whether to trim file on logout\n" + " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" + " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" + " --luks-volume-key-size=BITS\n" + " Volume key size to use for LUKS encryption\n" + " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" + " --luks-pbkdf-hash-algorithm=ALGORITHM\n" + " PBKDF hash algorithm to use\n" + " --luks-pbkdf-time-cost=SECS\n" + " Time cost for PBKDF in seconds\n" + " --luks-pbkdf-memory-cost=BYTES\n" + " Memory cost for PBKDF in bytes\n" + " --luks-pbkdf-parallel-threads=NUMBER\n" + " Number of parallel threads for PKBDF\n" + " --luks-sector-size=BYTES\n" + " Sector size for LUKS encryption in bytes\n" + " --luks-extra-mount-options=OPTIONS\n" + " LUKS extra mount options\n" + " --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n" + " --rebalance-weight=WEIGHT Weight while rebalancing\n" + "\n%4$sMounting User Record Properties:%5$s\n" + " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" + " --nodev=BOOL Control the 'nodev' flag of the home mount\n" + " --noexec=BOOL Control the 'noexec' flag of the home mount\n" + "\n%4$sCIFS User Record Properties:%5$s\n" + " --cifs-domain=DOMAIN CIFS (Windows) domain\n" + " --cifs-user-name=USER CIFS (Windows) user name\n" + " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" + " --cifs-extra-mount-options=OPTIONS\n" + " CIFS (Windows) extra mount options\n" + "\n%4$sLogin Behaviour User Record Properties:%5$s\n" + " --stop-delay=SECS How long to leave user services running after\n" + " logout\n" + " --kill-processes=BOOL Whether to kill user processes when sessions\n" + " terminate\n" + " --auto-login=BOOL Try to log this user in automatically\n" + " --session-launcher=LAUNCHER\n" + " Preferred session launcher file\n" + " --session-type=TYPE Preferred session type\n" + "\nSee the %6$s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal(), + link); + + 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[]) { + _cleanup_strv_free_ char **arg_languages = NULL; + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_NO_ASK_PASSWORD, + ARG_OFFLINE, + ARG_REALM, + ARG_ALIAS, + ARG_EMAIL_ADDRESS, + ARG_DISK_SIZE, + ARG_ACCESS_MODE, + ARG_STORAGE, + ARG_FS_TYPE, + ARG_IMAGE_PATH, + ARG_UMASK, + ARG_LUKS_DISCARD, + ARG_LUKS_OFFLINE_DISCARD, + ARG_JSON, + ARG_SETENV, + ARG_TIMEZONE, + ARG_LANGUAGE, + ARG_LOCKED, + ARG_SSH_AUTHORIZED_KEYS, + ARG_LOCATION, + ARG_BIRTH_DATE, + ARG_ICON_NAME, + ARG_PASSWORD_HINT, + ARG_NICE, + ARG_RLIMIT, + ARG_NOT_BEFORE, + ARG_NOT_AFTER, + ARG_LUKS_CIPHER, + ARG_LUKS_CIPHER_MODE, + ARG_LUKS_VOLUME_KEY_SIZE, + ARG_NOSUID, + ARG_NODEV, + ARG_NOEXEC, + ARG_CIFS_DOMAIN, + ARG_CIFS_USER_NAME, + ARG_CIFS_SERVICE, + ARG_CIFS_EXTRA_MOUNT_OPTIONS, + ARG_TASKS_MAX, + ARG_MEMORY_HIGH, + ARG_MEMORY_MAX, + ARG_CPU_WEIGHT, + ARG_IO_WEIGHT, + ARG_LUKS_PBKDF_TYPE, + ARG_LUKS_PBKDF_HASH_ALGORITHM, + ARG_LUKS_PBKDF_FORCE_ITERATIONS, + ARG_LUKS_PBKDF_TIME_COST, + ARG_LUKS_PBKDF_MEMORY_COST, + ARG_LUKS_PBKDF_PARALLEL_THREADS, + ARG_LUKS_SECTOR_SIZE, + ARG_RATE_LIMIT_INTERVAL, + ARG_RATE_LIMIT_BURST, + ARG_STOP_DELAY, + ARG_KILL_PROCESSES, + ARG_ENFORCE_PASSWORD_POLICY, + ARG_PASSWORD_CHANGE_NOW, + ARG_PASSWORD_CHANGE_MIN, + ARG_PASSWORD_CHANGE_MAX, + ARG_PASSWORD_CHANGE_WARN, + ARG_PASSWORD_CHANGE_INACTIVE, + ARG_EXPORT_FORMAT, + ARG_AUTO_LOGIN, + ARG_SESSION_LAUNCHER, + ARG_SESSION_TYPE, + ARG_PKCS11_TOKEN_URI, + ARG_FIDO2_DEVICE, + ARG_FIDO2_WITH_PIN, + ARG_FIDO2_WITH_UP, + ARG_FIDO2_WITH_UV, + ARG_RECOVERY_KEY, + ARG_DROP_CACHES, + ARG_LUKS_EXTRA_MOUNT_OPTIONS, + ARG_AUTO_RESIZE_MODE, + ARG_REBALANCE_WEIGHT, + ARG_FIDO2_CRED_ALG, + ARG_CAPABILITY_BOUNDING_SET, + ARG_CAPABILITY_AMBIENT_SET, + ARG_PROMPT_NEW_USER, + ARG_AVATAR, + ARG_LOGIN_BACKGROUND, + ARG_TMP_LIMIT, + ARG_DEV_SHM_LIMIT, + ARG_DEFAULT_AREA, + ARG_KEY_NAME, + ARG_SEIZE, + ARG_MATCH, + ARG_PROMPT_SHELL, + ARG_PROMPT_GROUPS, + ARG_CHROME, + ARG_MUTE_CONSOLE, + }; + + 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 }, + { "offline", no_argument, NULL, ARG_OFFLINE }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "identity", required_argument, NULL, 'I' }, + { "real-name", required_argument, NULL, 'c' }, + { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "realm", required_argument, NULL, ARG_REALM }, + { "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) */ + { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ + { "member-of", required_argument, NULL, 'G' }, + { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ + { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ + { "setenv", required_argument, NULL, ARG_SETENV }, + { "timezone", required_argument, NULL, ARG_TIMEZONE }, + { "language", required_argument, NULL, ARG_LANGUAGE }, + { "locked", required_argument, NULL, ARG_LOCKED }, + { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, + { "not-after", required_argument, NULL, ARG_NOT_AFTER }, + { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, + { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, + { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, + { "umask", required_argument, NULL, ARG_UMASK }, + { "nice", required_argument, NULL, ARG_NICE }, + { "rlimit", required_argument, NULL, ARG_RLIMIT }, + { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, + { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, + { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, + { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, + { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, + { "storage", required_argument, NULL, ARG_STORAGE }, + { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, + { "fs-type", required_argument, NULL, ARG_FS_TYPE }, + { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, + { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, + { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, + { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, + { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, + { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, + { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, + { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, + { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, + { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, + { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, + { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, + { "nosuid", required_argument, NULL, ARG_NOSUID }, + { "nodev", required_argument, NULL, ARG_NODEV }, + { "noexec", required_argument, NULL, ARG_NOEXEC }, + { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, + { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, + { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, + { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, + { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, + { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, + { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, + { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, + { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, + { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, + { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, + { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, + { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, + { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, + { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, + { "session-launcher", required_argument, NULL, ARG_SESSION_LAUNCHER, }, + { "session-type", required_argument, NULL, ARG_SESSION_TYPE, }, + { "json", required_argument, NULL, ARG_JSON }, + { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, + { "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-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 }, + { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, + { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, + { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, + { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, + { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, + { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, + { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, + { "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER }, + { "blob", required_argument, NULL, 'b' }, + { "avatar", required_argument, NULL, ARG_AVATAR }, + { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, + { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, + { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, + { "default-area", required_argument, NULL, ARG_DEFAULT_AREA }, + { "key-name", required_argument, NULL, ARG_KEY_NAME }, + { "seize", required_argument, NULL, ARG_SEIZE }, + { "match", required_argument, NULL, ARG_MATCH }, + { "prompt-shell", required_argument, NULL, ARG_PROMPT_SHELL }, + { "prompt-groups", required_argument, NULL, ARG_PROMPT_GROUPS }, + { "chrome", required_argument, NULL, ARG_CHROME }, + { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, + {} + }; - case ARG_LANGUAGE: - r = parse_language_field(&arg_languages, optarg); - if (r < 0) - return r; - break; + int r; - case ARG_NOSUID: - case ARG_NODEV: - case ARG_NOEXEC: - case ARG_LOCKED: - case ARG_KILL_PROCESSES: - case ARG_ENFORCE_PASSWORD_POLICY: - case ARG_AUTO_LOGIN: - case ARG_PASSWORD_CHANGE_NOW: { - const char *field = - c == ARG_LOCKED ? "locked" : - c == ARG_NOSUID ? "mountNoSuid" : - c == ARG_NODEV ? "mountNoDevices" : - c == ARG_NOEXEC ? "mountNoExecute" : - c == ARG_KILL_PROCESSES ? "killProcesses" : - c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" : - c == ARG_AUTO_LOGIN ? "autoLogin" : - c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" : - NULL; - assert(field); + /* This points to one of arg_identity_extra, arg_identity_extra_this_machine, + * arg_identity_extra_other_machines, in order to redirect changes on the next property being set to + * this part of the identity, instead of the default. */ + sd_json_variant **match_identity = NULL; - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + assert(argc >= 0); + assert(argv); - case 'P': - r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); - break; + /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not + * hooked up everywhere let's make it an environment variable only. */ + r = getenv_bool("SYSTEMD_HOME_DRY_RUN"); + if (r >= 0) + arg_dry_run = r; + else if (r != -ENXIO) + log_debug_errno(r, "Unable to parse $SYSTEMD_HOME_DRY_RUN, ignoring: %m"); - case ARG_DISK_SIZE: - r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); - if (r < 0) - return r; - break; + for (;;) { + int c; - case ARG_ACCESS_MODE: - r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); - if (r < 0) - return r; + c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPENAT", options, NULL); + if (c < 0) break; - case ARG_LUKS_DISCARD: - case ARG_LUKS_OFFLINE_DISCARD: { - const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; - - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + switch (c) { - case ARG_LUKS_VOLUME_KEY_SIZE: - case ARG_LUKS_PBKDF_FORCE_ITERATIONS: - case ARG_LUKS_PBKDF_PARALLEL_THREADS: - case ARG_RATE_LIMIT_BURST: { - const char *field = - c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" : - c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" : - c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" : - c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : - NULL; - assert(field); + case 'h': + return help(); - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + case ARG_VERSION: + return version(); - case ARG_LUKS_SECTOR_SIZE: - r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); - if (r < 0) - return r; + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_UMASK: - r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); - if (r < 0) - return r; + case ARG_NO_LEGEND: + arg_legend = false; break; - case ARG_SSH_AUTHORIZED_KEYS: - r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); - if (r < 0) - return r; - + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_NOT_BEFORE: - case ARG_NOT_AFTER: - case 'e': { - const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; - - r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; + case ARG_OFFLINE: + arg_offline = true; break; - } - - case ARG_PASSWORD_CHANGE_MIN: - case ARG_PASSWORD_CHANGE_MAX: - case ARG_PASSWORD_CHANGE_WARN: - case ARG_PASSWORD_CHANGE_INACTIVE: { - const char *field = - c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : - c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" : - c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" : - c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" : - NULL; - assert(field); - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; break; - } - - case ARG_STORAGE: - case ARG_FS_TYPE: - case ARG_LUKS_CIPHER: - case ARG_LUKS_CIPHER_MODE: - case ARG_LUKS_PBKDF_TYPE: - case ARG_LUKS_PBKDF_HASH_ALGORITHM: { - const char *field = - c == ARG_STORAGE ? "storage" : - c == ARG_FS_TYPE ? "fileSystemType" : - c == ARG_LUKS_CIPHER ? "luksCipher" : - c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" : - c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" : - c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : - NULL; - assert(field); - - sd_json_variant **identity = - match_identity ?: - IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? - &arg_identity_extra_this_machine : &arg_identity_extra; - - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", field, optarg); - r = parse_string_field(identity, field, optarg); + case 'M': + r = parse_machine_argument(optarg, &arg_host, &arg_transport); if (r < 0) return r; break; - } - case ARG_LUKS_PBKDF_TIME_COST: - case ARG_RATE_LIMIT_INTERVAL: - case ARG_STOP_DELAY: { - const char *field = - c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" : - c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" : - c == ARG_STOP_DELAY ? "stopDelayUSec" : - NULL; - assert(field); + case 'I': + arg_identity = optarg; + break; - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + case 'c': + if (!isempty(optarg) && !valid_gecos(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid GECOS field '%s'.", optarg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", optarg); if (r < 0) return r; break; - } - case 'G': - r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); + case ARG_ALIAS: + r = parse_group_field(&arg_identity_extra, "aliases", optarg); if (r < 0) return r; break; - case ARG_TASKS_MAX: - r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); + case 'd': + r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); if (r < 0) return r; break; - case ARG_MEMORY_MAX: - case ARG_MEMORY_HIGH: - case ARG_LUKS_PBKDF_MEMORY_COST: { - const char *field = - c == ARG_MEMORY_MAX ? "memoryMax" : - c == ARG_MEMORY_HIGH ? "memoryHigh" : - c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : - NULL; - - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); + case ARG_REALM: + r = parse_realm_field(&arg_identity_extra, "realm", optarg); if (r < 0) return r; break; - } - case ARG_CPU_WEIGHT: - case ARG_IO_WEIGHT: { - const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" : - c == ARG_IO_WEIGHT ? "ioWeight" : - NULL; + case ARG_EMAIL_ADDRESS: + case ARG_LOCATION: + case ARG_ICON_NAME: + case ARG_CIFS_USER_NAME: + case ARG_CIFS_DOMAIN: + case ARG_CIFS_EXTRA_MOUNT_OPTIONS: + case ARG_LUKS_EXTRA_MOUNT_OPTIONS: + case ARG_SESSION_LAUNCHER: + case ARG_SESSION_TYPE: { + const char *field = + c == ARG_EMAIL_ADDRESS ? "emailAddress" : + c == ARG_LOCATION ? "location" : + c == ARG_ICON_NAME ? "iconName" : + c == ARG_CIFS_USER_NAME ? "cifsUserName" : + c == ARG_CIFS_DOMAIN ? "cifsDomain" : + c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" : + c == ARG_LUKS_EXTRA_MOUNT_OPTIONS ? "luksExtraMountOptions" : + c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : + c == ARG_SESSION_TYPE ? "preferredSessionType" : + NULL; + assert(field); - r = parse_weight_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; break; } - case ARG_PKCS11_TOKEN_URI: - r = parse_pkcs11_token_uri_field(optarg); - if (r <= 0) - return r; - 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); - 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); + r = parse_string_field(&arg_identity_extra, "birthDate", optarg); + if (r < 0) + return r; + } break; - case ARG_FIDO2_DEVICE: - r = parse_fido2_device_field(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); + if (r < 0) + return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); + } - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); 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); + case ARG_PASSWORD_HINT: + r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); if (r < 0) return r; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); + string_erase(optarg); break; - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + case ARG_NICE: + r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); if (r < 0) return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_RECOVERY_KEY: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); - arg_recovery_key = r; - - r = drop_from_identity("recoveryKey", "recoveryKeyType"); + case ARG_RLIMIT: + r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); if (r < 0) return r; break; - case ARG_AUTO_RESIZE_MODE: - r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, - "autoResizeMode", optarg); + case 'u': + r = parse_uid_field(&arg_identity_extra, "uid", optarg); if (r < 0) return r; break; - case ARG_REBALANCE_WEIGHT: - r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, - "rebalanceWeight", optarg); + case 'k': + case ARG_IMAGE_PATH: { + const char *field = c == 'k' ? "skeletonDirectory" : "imagePath"; + + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); if (r < 0) return r; break; + } - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; + case 's': + if (!isempty(optarg) && !valid_shell(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Shell '%s' not valid.", optarg); - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) + r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", optarg); + if (r < 0) return r; - - break; - - case 'E': - if (arg_export_format == EXPORT_FORMAT_FULL) - arg_export_format = EXPORT_FORMAT_STRIPPED; - else if (arg_export_format == EXPORT_FORMAT_STRIPPED) - arg_export_format = EXPORT_FORMAT_MINIMAL; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); - - arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; - if (arg_json_format_flags == 0) - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_EXPORT_FORMAT: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); - - arg_export_format = export_format_from_string(optarg); - if (arg_export_format < 0) - return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); - break; - case ARG_DROP_CACHES: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); + case ARG_SETENV: + r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", optarg); if (r < 0) return r; break; - case ARG_CAPABILITY_AMBIENT_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_ambient_set, - "capabilityAmbientSet", optarg); + case ARG_TIMEZONE: + if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Timezone '%s' is not valid.", optarg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", optarg); if (r < 0) return r; break; - case ARG_CAPABILITY_BOUNDING_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_bounding_set, - "capabilityBoundingSet", optarg); + case ARG_LANGUAGE: + r = parse_language_field(&arg_languages, optarg); if (r < 0) return r; break; - case ARG_PROMPT_NEW_USER: - arg_prompt_new_user = true; - break; - - case 'b': - case ARG_AVATAR: - case ARG_LOGIN_BACKGROUND: { - _cleanup_close_ int fd = -EBADF; - _cleanup_free_ char *path = NULL, *filename = NULL; - - if (c == 'b') { - char *eq; - - if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ - hashmap_clear(arg_blob_files); - arg_blob_dir = mfree(arg_blob_dir); - arg_blob_clear = true; - break; - } - - eq = strrchr(optarg, '='); - if (!eq) { /* --blob=/some/path replaces the blob dir */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); - if (r < 0) - return r; - break; - } - - /* --blob=filename=/some/path replaces the file "filename" with /some/path */ - filename = strndup(optarg, eq - optarg); - if (!filename) - return log_oom(); - - if (isempty(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); - if (!suitable_blob_filename(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); - - r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); - if (r < 0) - return r; - } else { - const char *well_known_filename = - c == ARG_AVATAR ? "avatar" : - c == ARG_LOGIN_BACKGROUND ? "login-background" : - NULL; - assert(well_known_filename); - - filename = strdup(well_known_filename); - if (!filename) - return log_oom(); + case ARG_NOSUID: + case ARG_NODEV: + case ARG_NOEXEC: + case ARG_LOCKED: + case ARG_KILL_PROCESSES: + case ARG_ENFORCE_PASSWORD_POLICY: + case ARG_AUTO_LOGIN: + case ARG_PASSWORD_CHANGE_NOW: { + const char *field = + c == ARG_LOCKED ? "locked" : + c == ARG_NOSUID ? "mountNoSuid" : + c == ARG_NODEV ? "mountNoDevices" : + c == ARG_NOEXEC ? "mountNoExecute" : + c == ARG_KILL_PROCESSES ? "killProcesses" : + c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" : + c == ARG_AUTO_LOGIN ? "autoLogin" : + c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" : + NULL; + assert(field); - r = parse_path_argument(optarg, /* suppress_root= */ false, &path); - if (r < 0) - return r; - } + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); + if (r < 0) + return r; + break; + } - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); + case 'P': + r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); + break; - if (fd_verify_regular(fd) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path); - } else - fd = -EBADF; /* Delete the file */ + case ARG_DISK_SIZE: + r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); + if (r < 0) + return r; + break; - r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd)); + case ARG_ACCESS_MODE: + r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); if (r < 0) - return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename); - TAKE_PTR(filename); /* hashmap takes ownership */ - TAKE_FD(fd); + return r; + break; + + case ARG_LUKS_DISCARD: + case ARG_LUKS_OFFLINE_DISCARD: { + const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); + if (r < 0) + return r; break; } - case ARG_TMP_LIMIT: - case ARG_DEV_SHM_LIMIT: { + case ARG_LUKS_VOLUME_KEY_SIZE: + case ARG_LUKS_PBKDF_FORCE_ITERATIONS: + case ARG_LUKS_PBKDF_PARALLEL_THREADS: + case ARG_RATE_LIMIT_BURST: { const char *field = - c == ARG_TMP_LIMIT ? "tmpLimit" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : - NULL; - const char *field_scale = - c == ARG_TMP_LIMIT ? "tmpLimitScale" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : - NULL; - + c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" : + c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" : + c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" : + c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : + NULL; assert(field); - assert(field_scale); - r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, - field, field_scale, optarg); + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; break; } - case ARG_DEFAULT_AREA: - r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + case ARG_LUKS_SECTOR_SIZE: + r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); if (r < 0) return r; break; - case ARG_KEY_NAME: - if (!isempty(optarg) && !filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for --key-name= not a valid filename: %s", optarg); - - r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + case ARG_UMASK: + r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); if (r < 0) return r; break; - case ARG_SEIZE: - r = parse_boolean_argument("--seize=", optarg, &arg_seize); + case ARG_SSH_AUTHORIZED_KEYS: + r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); if (r < 0) return r; - break; - case ARG_MATCH: - if (streq(optarg, "any")) - match_identity = &arg_identity_extra; - else if (streq(optarg, "this")) - match_identity = &arg_identity_extra_this_machine; - else if (streq(optarg, "other")) - match_identity = &arg_identity_extra_other_machines; - else if (streq(optarg, "auto")) - match_identity = NULL; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--machine= argument not understood. Refusing."); break; - case 'A': - match_identity = &arg_identity_extra; - break; - case 'T': - match_identity = &arg_identity_extra_this_machine; - break; - case 'N': - match_identity = &arg_identity_extra_other_machines; - break; + case ARG_NOT_BEFORE: + case ARG_NOT_AFTER: + case 'e': { + const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; - case ARG_PROMPT_SHELL: - r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; - break; + } - case ARG_PROMPT_GROUPS: - r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); + case ARG_PASSWORD_CHANGE_MIN: + case ARG_PASSWORD_CHANGE_MAX: + case ARG_PASSWORD_CHANGE_WARN: + case ARG_PASSWORD_CHANGE_INACTIVE: { + const char *field = + c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : + c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" : + c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" : + c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" : + NULL; + assert(field); + + r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; - break; + } - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + case ARG_STORAGE: + case ARG_FS_TYPE: + case ARG_LUKS_CIPHER: + case ARG_LUKS_CIPHER_MODE: + case ARG_LUKS_PBKDF_TYPE: + case ARG_LUKS_PBKDF_HASH_ALGORITHM: { + const char *field = + c == ARG_STORAGE ? "storage" : + c == ARG_FS_TYPE ? "fileSystemType" : + c == ARG_LUKS_CIPHER ? "luksCipher" : + c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" : + c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" : + c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : + NULL; + assert(field); + + sd_json_variant **identity = + match_identity ?: + IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? + &arg_identity_extra_this_machine : &arg_identity_extra; + + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", field, optarg); + + r = parse_string_field(identity, field, optarg); if (r < 0) return r; + break; + } + + case ARG_LUKS_PBKDF_TIME_COST: + case ARG_RATE_LIMIT_INTERVAL: + case ARG_STOP_DELAY: { + const char *field = + c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" : + c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" : + c == ARG_STOP_DELAY ? "stopDelayUSec" : + NULL; + assert(field); + r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + if (r < 0) + return r; break; + } - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + case 'G': + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); if (r < 0) return r; + break; + case ARG_TASKS_MAX: + r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); + if (r < 0) + return r; break; - case '?': - return -EINVAL; + case ARG_MEMORY_MAX: + case ARG_MEMORY_HIGH: + case ARG_LUKS_PBKDF_MEMORY_COST: { + const char *field = + c == ARG_MEMORY_MAX ? "memoryMax" : + c == ARG_MEMORY_HIGH ? "memoryHigh" : + c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : + NULL; - default: - assert_not_reached(); + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); + if (r < 0) + return r; + break; } - } - - if (!strv_isempty(arg_languages)) { - char **additional; - r = sd_json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", arg_languages[0]); - if (r < 0) - return log_error_errno(r, "Failed to update preferred language: %m"); + case ARG_CPU_WEIGHT: + case ARG_IO_WEIGHT: { + const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" : + c == ARG_IO_WEIGHT ? "ioWeight" : + NULL; - additional = strv_skip(arg_languages, 1); - if (!strv_isempty(additional)) { - r = sd_json_variant_set_field_strv(&arg_identity_extra, "additionalLanguages", additional); - if (r < 0) - return log_error_errno(r, "Failed to update additional language list: %m"); - } else { - r = drop_from_identity("additionalLanguages"); + r = parse_weight_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; + break; } - } - - return 1; -} - -static int redirect_bus_mgr(void) { - const char *suffix; - - /* Talk to a different service if that's requested. (The same env var is also understood by homed, so - * that it is relatively easily possible to invoke a second instance of homed for debug purposes and - * have homectl talk to it, without colliding with the host version. This is handy when operating - * from a homed-managed account.) */ - - suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX"); - if (suffix) { - static BusLocator locator = { - .path = "/org/freedesktop/home1", - .interface = "org.freedesktop.home1.Manager", - }; - /* Yes, we leak this memory, but there's little point to collect this, given that we only do - * this in a debug environment, do it only once, and the string shall live for out entire - * process runtime. */ + case ARG_PKCS11_TOKEN_URI: + r = parse_pkcs11_token_uri_field(optarg); + if (r <= 0) + return r; + break; - locator.destination = strjoin("org.freedesktop.home1.", suffix); - if (!locator.destination) - return log_oom(); + 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; - bus_mgr = &locator; - } else - bus_mgr = bus_home_mgr; + case ARG_FIDO2_DEVICE: + r = parse_fido2_device_field(optarg); + if (r <= 0) + return r; + break; - return 0; -} + case ARG_FIDO2_WITH_PIN: + r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + if (r < 0) + return r; -static bool is_fallback_shell(const char *p) { - const char *q; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); + break; - if (!p) - return false; + case ARG_FIDO2_WITH_UP: + r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); + if (r < 0) + return r; - if (p[0] == '-') { - /* Skip over login shell dash */ - p++; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); + break; - if (streq(p, "ystemd-home-fallback-shell")) /* maybe the dash was used to override the binary name? */ - return true; - } + case ARG_FIDO2_WITH_UV: + r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + if (r < 0) + return r; - q = strrchr(p, '/'); /* Skip over path */ - if (q) - p = q + 1; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); + break; - return streq(p, "systemd-home-fallback-shell"); -} + case ARG_RECOVERY_KEY: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); + arg_recovery_key = r; -static int fallback_shell(int argc, char *argv[]) { - _cleanup_(user_record_unrefp) UserRecord *secret = NULL, *hr = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_strv_free_ char **l = NULL; - _cleanup_free_ char *argv0 = NULL; - const char *json, *hd, *shell; - int r, incomplete; + r = drop_from_identity("recoveryKey", "recoveryKeyType"); + if (r < 0) + return r; + break; - /* So here's the deal: if users log into a system via ssh, and their homed-managed home directory - * wasn't activated yet, SSH will permit the access but the home directory isn't actually available - * yet. SSH doesn't allow us to ask authentication questions from the PAM session stack, and doesn't - * run the PAM authentication stack (because it authenticates via its own key management, after - * all). So here's our way to support this: homectl can be invoked as a multi-call binary under the - * name "systemd-home-fallback-shell". If so, it will chainload a login shell, but first try to - * unlock the home directory of the user it is invoked as. systemd-homed will then override the shell - * listed in user records whose home directory is not activated yet with this pseudo-shell. Net - * effect: one SSH auth succeeds this pseudo shell gets invoked, which will unlock the homedir - * (possibly asking for a passphrase) and then chainload the regular shell. Once the login is - * complete the user record will look like any other. */ + case ARG_AUTO_RESIZE_MODE: + r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, + "autoResizeMode", optarg); + if (r < 0) + return r; + break; - r = acquire_bus(&bus); - if (r < 0) - return r; + case ARG_REBALANCE_WEIGHT: + r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, + "rebalanceWeight", optarg); + if (r < 0) + return r; + break; - for (unsigned n_tries = 0;; n_tries++) { - _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_json_variant_unrefp) sd_json_variant *v = NULL; + case 'j': + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; - if (n_tries >= 5) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to activate home dir, even after %u tries.", n_tries); + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; - /* Let's start by checking if this all is even necessary, i.e. if the useFallback boolean field is actually set. */ - r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", NULL); /* empty user string means: our calling user */ - if (r < 0) - return log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r)); + break; - r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL); - if (r < 0) - return bus_log_parse_error(r); + case 'E': + if (arg_export_format == EXPORT_FORMAT_FULL) + arg_export_format = EXPORT_FORMAT_STRIPPED; + else if (arg_export_format == EXPORT_FORMAT_STRIPPED) + arg_export_format = EXPORT_FORMAT_MINIMAL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); - 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"); + arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; + if (arg_json_format_flags == 0) + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; - hr = user_record_new(); - if (!hr) - return log_oom(); + case ARG_EXPORT_FORMAT: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); - r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_LOG|USER_RECORD_PERMISSIVE); - if (r < 0) - return r; + arg_export_format = export_format_from_string(optarg); + if (arg_export_format < 0) + return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); - if (!hr->use_fallback) /* Nice! We are done, fallback logic not necessary */ break; - if (!secret) { - r = acquire_passed_secrets(hr->user_name, &secret); + case ARG_DROP_CACHES: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); if (r < 0) return r; - } - - for (;;) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHomeIfReferenced"); - if (r < 0) - return bus_log_create_error(r); + break; - r = sd_bus_message_append(m, "s", NULL); /* empty user string means: our calling user */ + case ARG_CAPABILITY_AMBIENT_SET: + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_ambient_set, + "capabilityAmbientSet", optarg); if (r < 0) - return bus_log_create_error(r); + return r; + break; - r = bus_message_append_secret(m, secret); + case ARG_CAPABILITY_BOUNDING_SET: + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_bounding_set, + "capabilityBoundingSet", optarg); if (r < 0) - return bus_log_create_error(r); + return r; + break; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_REFERENCED)) - return log_error_errno(r, "Called without reference on home taken, can't operate."); + case ARG_PROMPT_NEW_USER: + arg_prompt_new_user = true; + break; - r = handle_generic_user_record_error(hr->user_name, secret, &error, r, false); - if (r < 0) - return r; + case 'b': + case ARG_AVATAR: + case ARG_LOGIN_BACKGROUND: { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *path = NULL, *filename = NULL; - sd_bus_error_free(&error); - } else - break; - } + if (c == 'b') { + char *eq; - /* Try again */ - hr = user_record_unref(hr); - } + if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ + hashmap_clear(arg_blob_files); + arg_blob_dir = mfree(arg_blob_dir); + arg_blob_clear = true; + break; + } - incomplete = getenv_bool("XDG_SESSION_INCOMPLETE"); /* pam_systemd_home reports this state via an environment variable to us. */ - if (incomplete < 0 && incomplete != -ENXIO) - return log_error_errno(incomplete, "Failed to parse $XDG_SESSION_INCOMPLETE environment variable: %m"); - if (incomplete > 0) { - /* We are still in an "incomplete" session here. Now upgrade it to a full one. This will make logind - * start the user@.service instance for us. */ - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1/session/self", - "org.freedesktop.login1.Session", - "SetClass", - &error, - /* ret_reply= */ NULL, - "s", - "user"); - if (r < 0) - return log_error_errno(r, "Failed to upgrade session: %s", bus_error_message(&error, r)); + eq = strrchr(optarg, '='); + if (!eq) { /* --blob=/some/path replaces the blob dir */ + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); + if (r < 0) + return r; + break; + } - if (setenv("XDG_SESSION_CLASS", "user", /* overwrite= */ true) < 0) /* Update the XDG_SESSION_CLASS environment variable to match the above */ - return log_error_errno(errno, "Failed to set $XDG_SESSION_CLASS: %m"); + /* --blob=filename=/some/path replaces the file "filename" with /some/path */ + filename = strndup(optarg, eq - optarg); + if (!filename) + return log_oom(); - if (unsetenv("XDG_SESSION_INCOMPLETE") < 0) /* Unset the 'incomplete' env var */ - return log_error_errno(errno, "Failed to unset $XDG_SESSION_INCOMPLETE: %m"); - } + if (isempty(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); + if (!suitable_blob_filename(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); - /* We are going to invoke execv() soon. Let's be extra accurate and flush/close our bus connection - * first, just to make sure anything queued is flushed out (though there shouldn't be anything) */ - bus = sd_bus_flush_close_unref(bus); + r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); + if (r < 0) + return r; + } else { + const char *well_known_filename = + c == ARG_AVATAR ? "avatar" : + c == ARG_LOGIN_BACKGROUND ? "login-background" : + NULL; + assert(well_known_filename); - assert(!hr->use_fallback); - assert_se(shell = user_record_shell(hr)); - assert_se(hd = user_record_home_directory(hr)); + filename = strdup(well_known_filename); + if (!filename) + return log_oom(); - /* Extra protection: avoid loops */ - if (is_fallback_shell(shell)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Primary shell of '%s' is fallback shell, refusing loop.", hr->user_name); + r = parse_path_argument(optarg, /* suppress_root= */ false, &path); + if (r < 0) + return r; + } - if (chdir(hd) < 0) - return log_error_errno(errno, "Failed to change directory to home directory '%s': %m", hd); + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); - if (setenv("SHELL", shell, /* overwrite= */ true) < 0) - return log_error_errno(errno, "Failed to set $SHELL: %m"); + if (fd_verify_regular(fd) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path); + } else + fd = -EBADF; /* Delete the file */ - if (setenv("HOME", hd, /* overwrite= */ true) < 0) - return log_error_errno(errno, "Failed to set $HOME: %m"); + r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd)); + if (r < 0) + return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename); + TAKE_PTR(filename); /* hashmap takes ownership */ + TAKE_FD(fd); - /* Paranoia: in case the client passed some passwords to us to help us unlock, unlock things now */ - FOREACH_STRING(ue, "PASSWORD", "NEWPASSWORD", "PIN") - if (unsetenv(ue) < 0) - return log_error_errno(errno, "Failed to unset $%s: %m", ue); + break; + } - r = path_extract_filename(shell, &argv0); - if (r < 0) - return log_error_errno(r, "Unable to extract file name from '%s': %m", shell); - if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Shell '%s' is a path to a directory, refusing.", shell); + case ARG_TMP_LIMIT: + case ARG_DEV_SHM_LIMIT: { + const char *field = + c == ARG_TMP_LIMIT ? "tmpLimit" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : + NULL; + const char *field_scale = + c == ARG_TMP_LIMIT ? "tmpLimitScale" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : + NULL; - /* Invoke this as login shell, by setting argv[0][0] to '-' (unless we ourselves weren't called as login shell) */ - if (!argv || isempty(argv[0]) || argv[0][0] == '-') { - _cleanup_free_ char *prefixed = strjoin("-", argv0); - if (!prefixed) - return log_oom(); + assert(field); + assert(field_scale); - free_and_replace(argv0, prefixed); - } + r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, + field, field_scale, optarg); + if (r < 0) + return r; + break; + } - l = strv_new(argv0); - if (!l) - return log_oom(); + case ARG_DEFAULT_AREA: + r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + if (r < 0) + return r; + break; - if (strv_extend_strv(&l, strv_skip(argv, 1), /* filter_duplicates= */ false) < 0) - return log_oom(); + case ARG_KEY_NAME: + if (!isempty(optarg) && !filename_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for --key-name= not a valid filename: %s", optarg); - execv(shell, l); - return log_error_errno(errno, "Failed to execute shell '%s': %m", shell); -} + r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + if (r < 0) + return r; + break; -static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; + case ARG_SEIZE: + r = parse_boolean_argument("--seize=", optarg, &arg_seize); + if (r < 0) + return r; + break; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; + case ARG_MATCH: + if (streq(optarg, "any")) + match_identity = &arg_identity_extra; + else if (streq(optarg, "this")) + match_identity = &arg_identity_extra_this_machine; + else if (streq(optarg, "other")) + match_identity = &arg_identity_extra_other_machines; + else if (streq(optarg, "auto")) + match_identity = NULL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--machine= argument not understood. Refusing."); + break; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - r = bus_call_method(bus, bus_mgr, "ListSigningKeys", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list signing keys: %s", bus_error_message(&error, r)); + case 'A': + match_identity = &arg_identity_extra; + break; + case 'T': + match_identity = &arg_identity_extra_this_machine; + break; + case 'N': + match_identity = &arg_identity_extra_other_machines; + break; - _cleanup_(table_unrefp) Table *table = table_new("name", "key"); - if (!table) - return log_oom(); + case ARG_PROMPT_SHELL: + r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); + if (r < 0) + return r; - r = sd_bus_message_enter_container(reply, 'a', "(sst)"); - if (r < 0) - return bus_log_parse_error(r); + break; - for (;;) { - const char *name, *pem; + case ARG_PROMPT_GROUPS: + r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); + if (r < 0) + return r; - r = sd_bus_message_read(reply, "(sst)", &name, &pem, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) break; - _cleanup_free_ char *h = NULL; - if (!sd_json_format_enabled(arg_json_format_flags)) { - /* 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); + case ARG_CHROME: + r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); if (r < 0) return r; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *key = NULL; - r = openssl_pubkey_from_pem(pem, SIZE_MAX, &key); + break; + + case ARG_MUTE_CONSOLE: + r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); if (r < 0) - return log_error_errno(r, "Failed to parse PEM: %m"); + return r; - _cleanup_free_ void *der = NULL; - 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."); + break; - ssize_t m = base64mem(der, MIN(n, 64), &h); - if (m < 0) - return log_oom(); - if (n > 64) /* check if we truncated the original version */ - if (!strextend(&h, glyph(GLYPH_ELLIPSIS))) - return log_oom(); - } + case '?': + return -EINVAL; - r = table_add_many( - table, - TABLE_STRING, name, - TABLE_STRING, h ?: pem); - if (r < 0) - return table_log_add_error(r); + default: + assert_not_reached(); + } } - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); + if (!strv_isempty(arg_languages)) { + char **additional; - r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); + r = sd_json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", arg_languages[0]); if (r < 0) - return r; - } + return log_error_errno(r, "Failed to update preferred language: %m"); - if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { - if (table_isempty(table)) - printf("No signing keys.\n"); - else - printf("\n%zu signing keys listed.\n", table_get_rows(table) - 1); + additional = strv_skip(arg_languages, 1); + if (!strv_isempty(additional)) { + r = sd_json_variant_set_field_strv(&arg_identity_extra, "additionalLanguages", additional); + if (r < 0) + return log_error_errno(r, "Failed to update additional language list: %m"); + } else { + r = drop_from_identity("additionalLanguages"); + if (r < 0) + return r; + } } - return 0; + return 1; } -static int verb_get_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; +static int redirect_bus_mgr(void) { + const char *suffix; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; + /* Talk to a different service if that's requested. (The same env var is also understood by homed, so + * that it is relatively easily possible to invoke a second instance of homed for debug purposes and + * have homectl talk to it, without colliding with the host version. This is handy when operating + * from a homed-managed account.) */ - char **keys = argc >= 2 ? strv_skip(argv, 1) : STRV_MAKE("local.public"); - int ret = 0; - STRV_FOREACH(k, keys) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - r = bus_call_method(bus, bus_mgr, "GetSigningKey", &error, &reply, "s", *k); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to get signing key '%s': %s", *k, bus_error_message(&error, r))); - continue; - } + suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX"); + if (suffix) { + static BusLocator locator = { + .path = "/org/freedesktop/home1", + .interface = "org.freedesktop.home1.Manager", + }; - const char *pem; - r = sd_bus_message_read(reply, "st", &pem, NULL); - if (r < 0) { - RET_GATHER(ret, bus_log_parse_error(r)); - continue; - } + /* Yes, we leak this memory, but there's little point to collect this, given that we only do + * this in a debug environment, do it only once, and the string shall live for out entire + * process runtime. */ - fputs(pem, stdout); - if (!endswith(pem, "\n")) - fputc('\n', stdout); + locator.destination = strjoin("org.freedesktop.home1.", suffix); + if (!locator.destination) + return log_oom(); - fflush(stdout); - } + bus_mgr = &locator; + } else + bus_mgr = bus_home_mgr; - return ret; + return 0; } -static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { - int r; +static bool is_fallback_shell(const char *p) { + const char *q; - assert_se(bus); - assert_se(fn); - assert_se(key); + if (!p) + return false; - _cleanup_free_ char *pem = NULL; - r = read_full_stream(key, &pem, /* ret_size= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key '%s': %m", fn); + if (p[0] == '-') { + /* Skip over login shell dash */ + p++; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = bus_call_method(bus, bus_mgr, "AddSigningKey", &error, /* ret_reply= */ NULL, "sst", fn, pem, UINT64_C(0)); - if (r < 0) - return log_error_errno(r, "Failed to add signing key '%s': %s", fn, bus_error_message(&error, r)); + if (streq(p, "ystemd-home-fallback-shell")) /* maybe the dash was used to override the binary name? */ + return true; + } - return 0; -} + q = strrchr(p, '/'); /* Skip over path */ + if (q) + p = q + 1; -static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; + return streq(p, "systemd-home-fallback-shell"); +} +static int fallback_shell(int argc, char *argv[]) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL, *hr = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *argv0 = NULL; + const char *json, *hd, *shell; + int r, incomplete; + + /* So here's the deal: if users log into a system via ssh, and their homed-managed home directory + * wasn't activated yet, SSH will permit the access but the home directory isn't actually available + * yet. SSH doesn't allow us to ask authentication questions from the PAM session stack, and doesn't + * run the PAM authentication stack (because it authenticates via its own key management, after + * all). So here's our way to support this: homectl can be invoked as a multi-call binary under the + * name "systemd-home-fallback-shell". If so, it will chainload a login shell, but first try to + * unlock the home directory of the user it is invoked as. systemd-homed will then override the shell + * listed in user records whose home directory is not activated yet with this pseudo-shell. Net + * effect: one SSH auth succeeds this pseudo shell gets invoked, which will unlock the homedir + * (possibly asking for a passphrase) and then chainload the regular shell. Once the login is + * complete the user record will look like any other. */ + r = acquire_bus(&bus); if (r < 0) return r; - int ret = EXIT_SUCCESS; - if (argc < 2 || streq(argv[1], "-")) { - if (!arg_key_name) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key name must be specified via --key-name= when reading key from standard input, refusing."); + for (unsigned n_tries = 0;; n_tries++) { + _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_json_variant_unrefp) sd_json_variant *v = NULL; - RET_GATHER(ret, add_signing_key_one(bus, arg_key_name, stdin)); - } else { - /* Refuse if more han one key is specified in combination with --key-name= */ - if (argc >= 3 && arg_key_name) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--key-name= is not supported if multiple signing keys are specified, refusing."); + if (n_tries >= 5) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to activate home dir, even after %u tries.", n_tries); - STRV_FOREACH(k, strv_skip(argv, 1)) { + /* Let's start by checking if this all is even necessary, i.e. if the useFallback boolean field is actually set. */ + r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", NULL); /* empty user string means: our calling user */ + if (r < 0) + return log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r)); - if (streq(*k, "-")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing to read from standard input if multiple keys are specified."); + r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL); + if (r < 0) + return bus_log_parse_error(r); - _cleanup_free_ char *fn = NULL; - if (!arg_key_name) { - r = path_extract_filename(*k, &fn); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to extract filename from path '%s': %m", *k)); - continue; - } - } + 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"); - _cleanup_fclose_ FILE *f = fopen(*k, "re"); - if (!f) { - RET_GATHER(ret, log_error_errno(errno, "Failed to open '%s': %m", *k)); - continue; - } + hr = user_record_new(); + if (!hr) + return log_oom(); - RET_GATHER(ret, add_signing_key_one(bus, fn ?: arg_key_name, f)); - } - } + r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_LOG|USER_RECORD_PERMISSIVE); + if (r < 0) + return r; - return ret; -} + if (!hr->use_fallback) /* Nice! We are done, fallback logic not necessary */ + break; -static int add_signing_keys_from_credentials(void) { - int r; + if (!secret) { + r = acquire_passed_secrets(hr->user_name, &secret); + if (r < 0) + return r; + } - _cleanup_close_ int fd = open_credentials_dir(); - if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ - return 0; - if (fd < 0) - return log_error_errno(fd, "Failed to open credentials directory: %m"); + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ DirectoryEntries *des = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); - if (r < 0) - return log_error_errno(r, "Failed to enumerate credentials: %m"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHomeIfReferenced"); + if (r < 0) + return bus_log_create_error(r); - int ret = 0; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - FOREACH_ARRAY(i, des->entries, des->n_entries) { - struct dirent *de = *i; - if (de->d_type != DT_REG) - continue; + r = sd_bus_message_append(m, "s", NULL); /* empty user string means: our calling user */ + if (r < 0) + return bus_log_create_error(r); - const char *e = startswith(de->d_name, "home.add-signing-key."); - if (!e) - continue; + r = bus_message_append_secret(m, secret); + if (r < 0) + return bus_log_create_error(r); - if (!filename_is_valid(e)) - continue; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_REFERENCED)) + return log_error_errno(r, "Called without reference on home taken, can't operate."); - if (!bus) { - r = acquire_bus(&bus); - if (r < 0) - return r; - } + r = handle_generic_user_record_error(hr->user_name, secret, &error, r, false); + if (r < 0) + return r; - _cleanup_fclose_ FILE *f = NULL; - r = xfopenat(fd, de->d_name, "re", O_NOFOLLOW, &f); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to open credential '%s': %m", de->d_name)); - continue; + sd_bus_error_free(&error); + } else + break; } - RET_GATHER(ret, add_signing_key_one(bus, e, f)); + /* Try again */ + hr = user_record_unref(hr); } - return ret; -} + incomplete = getenv_bool("XDG_SESSION_INCOMPLETE"); /* pam_systemd_home reports this state via an environment variable to us. */ + if (incomplete < 0 && incomplete != -ENXIO) + return log_error_errno(incomplete, "Failed to parse $XDG_SESSION_INCOMPLETE environment variable: %m"); + if (incomplete > 0) { + /* We are still in an "incomplete" session here. Now upgrade it to a full one. This will make logind + * start the user@.service instance for us. */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1/session/self", + "org.freedesktop.login1.Session", + "SetClass", + &error, + /* ret_reply= */ NULL, + "s", + "user"); + if (r < 0) + return log_error_errno(r, "Failed to upgrade session: %s", bus_error_message(&error, r)); -static int remove_signing_key_one(sd_bus *bus, const char *fn) { - int r; + if (setenv("XDG_SESSION_CLASS", "user", /* overwrite= */ true) < 0) /* Update the XDG_SESSION_CLASS environment variable to match the above */ + return log_error_errno(errno, "Failed to set $XDG_SESSION_CLASS: %m"); - assert_se(bus); - assert_se(fn); + if (unsetenv("XDG_SESSION_INCOMPLETE") < 0) /* Unset the 'incomplete' env var */ + return log_error_errno(errno, "Failed to unset $XDG_SESSION_INCOMPLETE: %m"); + } - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = bus_call_method(bus, bus_mgr, "RemoveSigningKey", &error, /* ret_reply= */ NULL, "st", fn, UINT64_C(0)); - if (r < 0) - return log_error_errno(r, "Failed to remove signing key '%s': %s", fn, bus_error_message(&error, r)); + /* We are going to invoke execv() soon. Let's be extra accurate and flush/close our bus connection + * first, just to make sure anything queued is flushed out (though there shouldn't be anything) */ + bus = sd_bus_flush_close_unref(bus); - return 0; -} + assert(!hr->use_fallback); + assert_se(shell = user_record_shell(hr)); + assert_se(hd = user_record_home_directory(hr)); -static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; + /* Extra protection: avoid loops */ + if (is_fallback_shell(shell)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Primary shell of '%s' is fallback shell, refusing loop.", hr->user_name); - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); + if (chdir(hd) < 0) + return log_error_errno(errno, "Failed to change directory to home directory '%s': %m", hd); + + if (setenv("SHELL", shell, /* overwrite= */ true) < 0) + return log_error_errno(errno, "Failed to set $SHELL: %m"); + + if (setenv("HOME", hd, /* overwrite= */ true) < 0) + return log_error_errno(errno, "Failed to set $HOME: %m"); + + /* Paranoia: in case the client passed some passwords to us to help us unlock, unlock things now */ + FOREACH_STRING(ue, "PASSWORD", "NEWPASSWORD", "PIN") + if (unsetenv(ue) < 0) + return log_error_errno(errno, "Failed to unset $%s: %m", ue); + + r = path_extract_filename(shell, &argv0); if (r < 0) - return r; + return log_error_errno(r, "Unable to extract file name from '%s': %m", shell); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Shell '%s' is a path to a directory, refusing.", shell); - r = EXIT_SUCCESS; - STRV_FOREACH(k, strv_skip(argv, 1)) - RET_GATHER(r, remove_signing_key_one(bus, *k)); + /* Invoke this as login shell, by setting argv[0][0] to '-' (unless we ourselves weren't called as login shell) */ + if (!argv || isempty(argv[0]) || argv[0][0] == '-') { + _cleanup_free_ char *prefixed = strjoin("-", argv0); + if (!prefixed) + return log_oom(); - return r; + free_and_replace(argv0, prefixed); + } + + l = strv_new(argv0); + if (!l) + return log_oom(); + + if (strv_extend_strv(&l, strv_skip(argv, 1), /* filter_duplicates= */ false) < 0) + return log_oom(); + + execv(shell, l); + return log_error_errno(errno, "Failed to execute shell '%s': %m", shell); } static int run(int argc, char *argv[]) { From feb1078462ba177af046d0e08e4ac9c24ecc55c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 13:59:30 +0200 Subject: [PATCH 1753/2155] homectl: reorder cases in parse_argv() to match order in --help Display options --no-pager, --no-legend, --no-ask-password are placed at the end, after --mute-console=yes, rather than near the top. Co-developed-by: Claude Opus 4.7 (1M context) --- src/home/homectl.c | 822 +++++++++++++++++++++++++-------------------- 1 file changed, 459 insertions(+), 363 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 42d210d1c6c95..63e3b1f5320e9 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -4378,18 +4378,6 @@ 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 ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - case ARG_OFFLINE: arg_offline = true; break; @@ -4409,6 +4397,100 @@ static int parse_argv(int argc, char *argv[]) { arg_identity = optarg; break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + case 'j': + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + case ARG_EXPORT_FORMAT: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); + + arg_export_format = export_format_from_string(optarg); + if (arg_export_format < 0) + return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); + + break; + + case 'E': + if (arg_export_format == EXPORT_FORMAT_FULL) + arg_export_format = EXPORT_FORMAT_STRIPPED; + else if (arg_export_format == EXPORT_FORMAT_STRIPPED) + arg_export_format = EXPORT_FORMAT_MINIMAL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); + + arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; + if (arg_json_format_flags == 0) + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + case ARG_KEY_NAME: + if (!isempty(optarg) && !filename_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for --key-name= not a valid filename: %s", optarg); + + r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + if (r < 0) + return r; + break; + + case ARG_SEIZE: + r = parse_boolean_argument("--seize=", optarg, &arg_seize); + if (r < 0) + return r; + break; + + case ARG_PROMPT_NEW_USER: + arg_prompt_new_user = true; + break; + + case ARG_PROMPT_GROUPS: + r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); + if (r < 0) + return r; + + break; + + case ARG_PROMPT_SHELL: + r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); + if (r < 0) + return r; + + break; + + case ARG_CHROME: + r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + if (r < 0) + return r; + + break; + + case ARG_MUTE_CONSOLE: + r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + if (r < 0) + return r; + + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + case 'c': if (!isempty(optarg) && !valid_gecos(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -4419,51 +4501,29 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_ALIAS: - r = parse_group_field(&arg_identity_extra, "aliases", optarg); + case ARG_REALM: + r = parse_realm_field(&arg_identity_extra, "realm", optarg); if (r < 0) return r; break; - case 'd': - r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); + case ARG_ALIAS: + r = parse_group_field(&arg_identity_extra, "aliases", optarg); if (r < 0) return r; break; - case ARG_REALM: - r = parse_realm_field(&arg_identity_extra, "realm", optarg); + case ARG_EMAIL_ADDRESS: + r = parse_string_field(match_identity ?: &arg_identity_extra, "emailAddress", optarg); if (r < 0) return r; break; - case ARG_EMAIL_ADDRESS: case ARG_LOCATION: - case ARG_ICON_NAME: - case ARG_CIFS_USER_NAME: - case ARG_CIFS_DOMAIN: - case ARG_CIFS_EXTRA_MOUNT_OPTIONS: - case ARG_LUKS_EXTRA_MOUNT_OPTIONS: - case ARG_SESSION_LAUNCHER: - case ARG_SESSION_TYPE: { - const char *field = - c == ARG_EMAIL_ADDRESS ? "emailAddress" : - c == ARG_LOCATION ? "location" : - c == ARG_ICON_NAME ? "iconName" : - c == ARG_CIFS_USER_NAME ? "cifsUserName" : - c == ARG_CIFS_DOMAIN ? "cifsDomain" : - c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" : - c == ARG_LUKS_EXTRA_MOUNT_OPTIONS ? "luksExtraMountOptions" : - c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : - c == ARG_SESSION_TYPE ? "preferredSessionType" : - NULL; - assert(field); - - r = parse_string_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "location", optarg); if (r < 0) return r; break; - } case ARG_BIRTH_DATE: if (isempty(optarg)) { @@ -4481,53 +4541,63 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_CIFS_SERVICE: - if (!isempty(optarg)) { - r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); - } + case ARG_ICON_NAME: + r = parse_string_field(match_identity ?: &arg_identity_extra, "iconName", optarg); + if (r < 0) + return r; + break; - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); + case 'd': + r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); if (r < 0) return r; break; - case ARG_PASSWORD_HINT: - r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); + case 'u': + r = parse_uid_field(&arg_identity_extra, "uid", optarg); if (r < 0) return r; + break; - string_erase(optarg); + case 'G': + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); + if (r < 0) + return r; break; - case ARG_NICE: - r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); + case ARG_CAPABILITY_BOUNDING_SET: + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_bounding_set, + "capabilityBoundingSet", optarg); if (r < 0) return r; break; - case ARG_RLIMIT: - r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); + case ARG_CAPABILITY_AMBIENT_SET: + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_ambient_set, + "capabilityAmbientSet", optarg); if (r < 0) return r; break; - case 'u': - r = parse_uid_field(&arg_identity_extra, "uid", optarg); + case ARG_ACCESS_MODE: + r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); if (r < 0) return r; break; - case 'k': - case ARG_IMAGE_PATH: { - const char *field = c == 'k' ? "skeletonDirectory" : "imagePath"; + case ARG_UMASK: + r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); + if (r < 0) + return r; + break; - r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); + case 'k': + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "skeletonDirectory", optarg); if (r < 0) return r; break; - } case 's': if (!isempty(optarg) && !valid_shell(optarg)) @@ -4561,109 +4631,194 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_NOSUID: - case ARG_NODEV: - case ARG_NOEXEC: - case ARG_LOCKED: - case ARG_KILL_PROCESSES: - case ARG_ENFORCE_PASSWORD_POLICY: - case ARG_AUTO_LOGIN: - case ARG_PASSWORD_CHANGE_NOW: { - const char *field = - c == ARG_LOCKED ? "locked" : - c == ARG_NOSUID ? "mountNoSuid" : - c == ARG_NODEV ? "mountNoDevices" : - c == ARG_NOEXEC ? "mountNoExecute" : - c == ARG_KILL_PROCESSES ? "killProcesses" : - c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" : - c == ARG_AUTO_LOGIN ? "autoLogin" : - c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" : - NULL; - assert(field); - - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); + case ARG_DEFAULT_AREA: + r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); if (r < 0) return r; break; - } - - case 'P': - r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); - break; - case ARG_DISK_SIZE: - r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); + case ARG_SSH_AUTHORIZED_KEYS: + r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); if (r < 0) return r; - break; - case ARG_ACCESS_MODE: - r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); - if (r < 0) - return r; break; - case ARG_LUKS_DISCARD: - case ARG_LUKS_OFFLINE_DISCARD: { - const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; - - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) + case ARG_PKCS11_TOKEN_URI: + r = parse_pkcs11_token_uri_field(optarg); + if (r <= 0) return r; break; - } - - case ARG_LUKS_VOLUME_KEY_SIZE: - case ARG_LUKS_PBKDF_FORCE_ITERATIONS: - case ARG_LUKS_PBKDF_PARALLEL_THREADS: - case ARG_RATE_LIMIT_BURST: { - const char *field = - c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" : - c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" : - c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" : - c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : - NULL; - assert(field); - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) + case ARG_FIDO2_DEVICE: + r = parse_fido2_device_field(optarg); + if (r <= 0) return r; break; - } - case ARG_LUKS_SECTOR_SIZE: - r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); + 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_UMASK: - r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); + 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_SSH_AUTHORIZED_KEYS: - r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); + 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_NOT_BEFORE: - case ARG_NOT_AFTER: - case 'e': { - const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; + case ARG_RECOVERY_KEY: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); + arg_recovery_key = r; - r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); + r = drop_from_identity("recoveryKey", "recoveryKeyType"); if (r < 0) return r; break; - } - case ARG_PASSWORD_CHANGE_MIN: + case 'b': + case ARG_AVATAR: + case ARG_LOGIN_BACKGROUND: { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *path = NULL, *filename = NULL; + + if (c == 'b') { + char *eq; + + if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ + hashmap_clear(arg_blob_files); + arg_blob_dir = mfree(arg_blob_dir); + arg_blob_clear = true; + break; + } + + eq = strrchr(optarg, '='); + if (!eq) { /* --blob=/some/path replaces the blob dir */ + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); + if (r < 0) + return r; + break; + } + + /* --blob=filename=/some/path replaces the file "filename" with /some/path */ + filename = strndup(optarg, eq - optarg); + if (!filename) + return log_oom(); + + if (isempty(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); + if (!suitable_blob_filename(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); + + r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); + if (r < 0) + return r; + } else { + const char *well_known_filename = + c == ARG_AVATAR ? "avatar" : + c == ARG_LOGIN_BACKGROUND ? "login-background" : + NULL; + assert(well_known_filename); + + filename = strdup(well_known_filename); + if (!filename) + return log_oom(); + + r = parse_path_argument(optarg, /* suppress_root= */ false, &path); + if (r < 0) + return r; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + if (fd_verify_regular(fd) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path); + } else + fd = -EBADF; /* Delete the file */ + + r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd)); + if (r < 0) + return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename); + TAKE_PTR(filename); /* hashmap takes ownership */ + TAKE_FD(fd); + + break; + } + + case ARG_LOCKED: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "locked", optarg); + if (r < 0) + return r; + break; + + case ARG_NOT_BEFORE: + case ARG_NOT_AFTER: + case 'e': { + const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; + + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); + if (r < 0) + return r; + break; + } + + case ARG_RATE_LIMIT_INTERVAL: + r = parse_time_field(match_identity ?: &arg_identity_extra, "rateLimitIntervalUSec", optarg); + if (r < 0) + return r; + break; + + case ARG_RATE_LIMIT_BURST: + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "rateLimitBurst", optarg); + if (r < 0) + return r; + break; + + case ARG_PASSWORD_HINT: + r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); + if (r < 0) + return r; + + string_erase(optarg); + break; + + case ARG_ENFORCE_PASSWORD_POLICY: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "enforcePasswordPolicy", optarg); + if (r < 0) + return r; + break; + + case 'P': + r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); + break; + + case ARG_PASSWORD_CHANGE_NOW: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "passwordChangeNow", optarg); + if (r < 0) + return r; + break; + + case ARG_PASSWORD_CHANGE_MIN: case ARG_PASSWORD_CHANGE_MAX: case ARG_PASSWORD_CHANGE_WARN: case ARG_PASSWORD_CHANGE_INACTIVE: { @@ -4681,55 +4836,20 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_STORAGE: - case ARG_FS_TYPE: - case ARG_LUKS_CIPHER: - case ARG_LUKS_CIPHER_MODE: - case ARG_LUKS_PBKDF_TYPE: - case ARG_LUKS_PBKDF_HASH_ALGORITHM: { - const char *field = - c == ARG_STORAGE ? "storage" : - c == ARG_FS_TYPE ? "fileSystemType" : - c == ARG_LUKS_CIPHER ? "luksCipher" : - c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" : - c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" : - c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : - NULL; - assert(field); - - sd_json_variant **identity = - match_identity ?: - IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? - &arg_identity_extra_this_machine : &arg_identity_extra; - - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", field, optarg); - - r = parse_string_field(identity, field, optarg); + case ARG_DISK_SIZE: + r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); if (r < 0) return r; break; - } - - case ARG_LUKS_PBKDF_TIME_COST: - case ARG_RATE_LIMIT_INTERVAL: - case ARG_STOP_DELAY: { - const char *field = - c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" : - c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" : - c == ARG_STOP_DELAY ? "stopDelayUSec" : - NULL; - assert(field); - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + case ARG_NICE: + r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); if (r < 0) return r; break; - } - case 'G': - r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); + case ARG_RLIMIT: + r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); if (r < 0) return r; break; @@ -4740,20 +4860,17 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_MEMORY_MAX: case ARG_MEMORY_HIGH: - case ARG_LUKS_PBKDF_MEMORY_COST: { - const char *field = - c == ARG_MEMORY_MAX ? "memoryMax" : - c == ARG_MEMORY_HIGH ? "memoryHigh" : - c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : - NULL; + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryHigh", optarg); + if (r < 0) + return r; + break; - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); + case ARG_MEMORY_MAX: + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryMax", optarg); if (r < 0) return r; break; - } case ARG_CPU_WEIGHT: case ARG_IO_WEIGHT: { @@ -4767,248 +4884,255 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PKCS11_TOKEN_URI: - r = parse_pkcs11_token_uri_field(optarg); - if (r <= 0) + case ARG_TMP_LIMIT: + case ARG_DEV_SHM_LIMIT: { + const char *field = + c == ARG_TMP_LIMIT ? "tmpLimit" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : + NULL; + const char *field_scale = + c == ARG_TMP_LIMIT ? "tmpLimitScale" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : + NULL; + + assert(field); + assert(field_scale); + + r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, + field, field_scale, optarg); + if (r < 0) return r; break; + } - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); + case ARG_STORAGE: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "storage", optarg); + + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "storage", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); + return r; break; + } - case ARG_FIDO2_DEVICE: - r = parse_fido2_device_field(optarg); - if (r <= 0) + case ARG_IMAGE_PATH: + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "imagePath", optarg); + if (r < 0) return r; break; - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + case ARG_DROP_CACHES: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); 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); + case ARG_FS_TYPE: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "fileSystemType", optarg); + + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "fileSystemType", optarg); 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); + case ARG_LUKS_DISCARD: + case ARG_LUKS_OFFLINE_DISCARD: { + const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; + + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; + } - case ARG_RECOVERY_KEY: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); - arg_recovery_key = r; + case ARG_LUKS_CIPHER: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksCipher", optarg); - r = drop_from_identity("recoveryKey", "recoveryKeyType"); + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipher", optarg); if (r < 0) return r; break; + } - case ARG_AUTO_RESIZE_MODE: - r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, - "autoResizeMode", optarg); + case ARG_LUKS_CIPHER_MODE: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksCipherMode", optarg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipherMode", optarg); if (r < 0) return r; break; + } - case ARG_REBALANCE_WEIGHT: - r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, - "rebalanceWeight", optarg); + case ARG_LUKS_VOLUME_KEY_SIZE: + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksVolumeKeySize", optarg); if (r < 0) return r; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; + case ARG_LUKS_PBKDF_TYPE: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksPbkdfType", optarg); - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfType", optarg); + if (r < 0) return r; - break; + } - case 'E': - if (arg_export_format == EXPORT_FORMAT_FULL) - arg_export_format = EXPORT_FORMAT_STRIPPED; - else if (arg_export_format == EXPORT_FORMAT_STRIPPED) - arg_export_format = EXPORT_FORMAT_MINIMAL; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); + case ARG_LUKS_PBKDF_HASH_ALGORITHM: { + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksPbkdfHashAlgorithm", optarg); - arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; - if (arg_json_format_flags == 0) - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfHashAlgorithm", optarg); + if (r < 0) + return r; break; + } - case ARG_EXPORT_FORMAT: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); - - arg_export_format = export_format_from_string(optarg); - if (arg_export_format < 0) - return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); - + case ARG_LUKS_PBKDF_TIME_COST: + r = parse_time_field(match_identity ?: &arg_identity_extra, "luksPbkdfTimeCostUSec", optarg); + if (r < 0) + return r; break; - case ARG_DROP_CACHES: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); + case ARG_LUKS_PBKDF_MEMORY_COST: + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "luksPbkdfMemoryCost", optarg); if (r < 0) return r; break; - case ARG_CAPABILITY_AMBIENT_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_ambient_set, - "capabilityAmbientSet", optarg); + case ARG_LUKS_PBKDF_PARALLEL_THREADS: + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfParallelThreads", optarg); if (r < 0) return r; break; - case ARG_CAPABILITY_BOUNDING_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_bounding_set, - "capabilityBoundingSet", optarg); + case ARG_LUKS_SECTOR_SIZE: + r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); if (r < 0) return r; break; - case ARG_PROMPT_NEW_USER: - arg_prompt_new_user = true; + case ARG_LUKS_EXTRA_MOUNT_OPTIONS: + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksExtraMountOptions", optarg); + if (r < 0) + return r; break; - case 'b': - case ARG_AVATAR: - case ARG_LOGIN_BACKGROUND: { - _cleanup_close_ int fd = -EBADF; - _cleanup_free_ char *path = NULL, *filename = NULL; + case ARG_LUKS_PBKDF_FORCE_ITERATIONS: + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfForceIterations", optarg); + if (r < 0) + return r; + break; - if (c == 'b') { - char *eq; + case ARG_AUTO_RESIZE_MODE: + r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, + "autoResizeMode", optarg); + if (r < 0) + return r; + break; - if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ - hashmap_clear(arg_blob_files); - arg_blob_dir = mfree(arg_blob_dir); - arg_blob_clear = true; - break; - } + case ARG_REBALANCE_WEIGHT: + r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, + "rebalanceWeight", optarg); + if (r < 0) + return r; + break; - eq = strrchr(optarg, '='); - if (!eq) { /* --blob=/some/path replaces the blob dir */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); - if (r < 0) - return r; - break; - } + case ARG_NOSUID: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoSuid", optarg); + if (r < 0) + return r; + break; - /* --blob=filename=/some/path replaces the file "filename" with /some/path */ - filename = strndup(optarg, eq - optarg); - if (!filename) - return log_oom(); + case ARG_NODEV: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoDevices", optarg); + if (r < 0) + return r; + break; - if (isempty(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); - if (!suitable_blob_filename(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); + case ARG_NOEXEC: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoExecute", optarg); + if (r < 0) + return r; + break; - r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); - if (r < 0) - return r; - } else { - const char *well_known_filename = - c == ARG_AVATAR ? "avatar" : - c == ARG_LOGIN_BACKGROUND ? "login-background" : - NULL; - assert(well_known_filename); + case ARG_CIFS_DOMAIN: + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsDomain", optarg); + if (r < 0) + return r; + break; - filename = strdup(well_known_filename); - if (!filename) - return log_oom(); + case ARG_CIFS_USER_NAME: + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsUserName", optarg); + if (r < 0) + return r; + break; - r = parse_path_argument(optarg, /* suppress_root= */ false, &path); + case ARG_CIFS_SERVICE: + if (!isempty(optarg)) { + r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); if (r < 0) - return r; + return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); } - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - - if (fd_verify_regular(fd) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path); - } else - fd = -EBADF; /* Delete the file */ - - r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd)); + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); if (r < 0) - return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename); - TAKE_PTR(filename); /* hashmap takes ownership */ - TAKE_FD(fd); - + return r; break; - } - - case ARG_TMP_LIMIT: - case ARG_DEV_SHM_LIMIT: { - const char *field = - c == ARG_TMP_LIMIT ? "tmpLimit" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : - NULL; - const char *field_scale = - c == ARG_TMP_LIMIT ? "tmpLimitScale" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : - NULL; - assert(field); - assert(field_scale); + case ARG_CIFS_EXTRA_MOUNT_OPTIONS: + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsExtraMountOptions", optarg); + if (r < 0) + return r; + break; - r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, - field, field_scale, optarg); + case ARG_STOP_DELAY: + r = parse_time_field(match_identity ?: &arg_identity_extra, "stopDelayUSec", optarg); if (r < 0) return r; break; - } - case ARG_DEFAULT_AREA: - r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + case ARG_KILL_PROCESSES: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "killProcesses", optarg); if (r < 0) return r; break; - case ARG_KEY_NAME: - if (!isempty(optarg) && !filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for --key-name= not a valid filename: %s", optarg); + case ARG_AUTO_LOGIN: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "autoLogin", optarg); + if (r < 0) + return r; + break; - r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + case ARG_SESSION_LAUNCHER: + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionLauncher", optarg); if (r < 0) return r; break; - case ARG_SEIZE: - r = parse_boolean_argument("--seize=", optarg, &arg_seize); + case ARG_SESSION_TYPE: + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionType", optarg); if (r < 0) return r; 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_MATCH: if (streq(optarg, "any")) match_identity = &arg_identity_extra; @@ -5032,34 +5156,6 @@ static int parse_argv(int argc, char *argv[]) { match_identity = &arg_identity_extra_other_machines; break; - case ARG_PROMPT_SHELL: - r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); - if (r < 0) - return r; - - break; - - case ARG_PROMPT_GROUPS: - r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); - if (r < 0) - return r; - - break; - - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); - if (r < 0) - return r; - - break; - - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); - if (r < 0) - return r; - - break; - case '?': return -EINVAL; From 82b87471ad3f6f1b3faf6bebc26dced9df242a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 17:51:41 +0200 Subject: [PATCH 1754/2155] shared/options: fix display of OPTION_HELP_ENTRY_VERBATIM So far it was used only once, in bootctl. It should not be indented: -R --print-root-device -RR ^ --- 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 ac36fe07653ec..1a24106fc5403 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -510,7 +510,8 @@ int _option_parser_get_help_table_full( /* 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. */ - const char *prefix = opt->short_code != 0 ? " " : " "; + bool shift_left = opt->short_code != 0 || FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM); + const char *prefix = shift_left ? " " : " "; _cleanup_free_ char *t = strjoin(prefix, s); if (!t) return log_oom(); From 9a7b2d0d87079485046be34827f8d9f59e38cfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 15:01:09 +0200 Subject: [PATCH 1755/2155] homectl: convert parse_argv to OPTION macros Rewrite help() with help-util.h primitives + option_parser_get_help_table_group for each User Record Properties section. The verbs[] table stays unchanged for now; run() switches from dispatch_verb() (which depended on the global optind) to _dispatch_verb_with_args() fed by option_parser_get_args(). Explanations are improved for --birth-date[=DATE] (correct placement of '['), --skel=, --shell= (short options listed). Some minor rewordings for other options. The explanation for -E and -EE is split. (OPTION_HELP_ENTRY_VERBATIM is used for -EE.) Co-developed-by: Claude Opus 4.7 (1M context) --- src/home/homectl.c | 1085 ++++++++++++++++---------------------------- 1 file changed, 388 insertions(+), 697 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 63e3b1f5320e9..f635981a06940 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -30,6 +29,7 @@ #include "fs-util.h" #include "glyph-util.h" #include "hashmap.h" +#include "help-util.h" #include "hexdecoct.h" #include "home-util.h" #include "homectl-fido2.h" @@ -40,6 +40,7 @@ #include "libfido2-util.h" #include "locale-util.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -50,7 +51,6 @@ #include "pkcs11-util.h" #include "plymouth-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "prompt-util.h" @@ -3917,218 +3917,90 @@ static int parse_fido2_device_field(const char *arg) { } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char* const groups[] = { + NULL, + "General User Record Properties", + "Authentication User Record Properties", + "Blob Directory User Record Properties", + "Account Management User Record Properties", + "Password Policy User Record Properties", + "Resource Management User Record Properties", + "Storage User Record Properties", + "LUKS Storage User Record Properties", + "Mounting User Record Properties", + "CIFS User Record Properties", + "Login Behaviour User Record Properties", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; + 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; + } + + assert_cc(ELEMENTSOF(tables) == 12); + (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]); + pager_open(arg_pager_flags); - r = terminal_urlify_man("homectl", "1", &link); - if (r < 0) - return log_oom(); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Create, manipulate or inspect home directories."); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sCreate, manipulate or inspect home directories.%3$s\n" - "\n%4$sBasic User Manipulation Commands:%5$s\n" - " list List home areas\n" + help_section("Basic User Manipulation Commands"); + printf(" list List home areas\n" " inspect USER… Inspect a home area\n" " create USER Create a home area\n" " update USER Update a home area\n" " passwd USER Change password of a home area\n" " resize USER SIZE Resize a home area\n" - " remove USER… Remove a home area\n" - "\n%4$sAdvanced User Manipulation Commands:%5$s\n" - " activate USER… Activate a home area\n" + " remove USER… Remove a home area\n"); + + help_section("Advanced User Manipulation Commands"); + printf(" activate USER… Activate a home area\n" " deactivate USER… Deactivate a home area\n" " deactivate-all Deactivate all active home areas\n" " with USER [COMMAND…] Run shell or command with access to a home area\n" - " authenticate USER… Authenticate a home area\n" - "\n%4$sUser Migration Commands:%5$s\n" - " adopt PATH… Add an existing home area on this system\n" + " authenticate USER… Authenticate a home area\n"); + + help_section("User Migration Commands"); + printf(" adopt PATH… Add an existing home area on this system\n" " register PATH… Register a user record locally\n" - " unregister USER… Unregister a user record locally\n" - "\n%4$sSigning Keys Commands:%5$s\n" - " list-signing-keys List home signing keys\n" + " unregister USER… Unregister a user record locally\n"); + + help_section("Signing Keys Commands"); + printf(" list-signing-keys List home signing keys\n" " get-signing-key [NAME…] Get a named home signing key\n" " add-signing-key FILE… Add home signing key\n" - " remove-signing-key NAME… Remove home signing key\n" - "\n%4$sLock/Unlock Commands:%5$s\n" - " lock USER… Temporarily lock an active home area\n" + " remove-signing-key NAME… Remove home signing key\n"); + + help_section("Lock/Unlock Commands"); + printf(" lock USER… Temporarily lock an active home area\n" " unlock USER… Unlock a temporarily locked home area\n" - " lock-all Lock all suitable home areas\n" - "\n%4$sOther Commands:%5$s\n" - " rebalance Rebalance free space between home areas\n" - " firstboot Run first-boot home area creation wizard\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" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " --offline Don't update record embedded in home directory\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --identity=PATH Read JSON identity from file\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" - " --export-format= Strip JSON inspection data (full, stripped,\n" - " minimal)\n" - " -E When specified once equals -j --export-format=\n" - " stripped, when specified twice equals\n" - " -j --export-format=minimal\n" - " --key-name=NAME Key name when adding a signing key\n" - " --seize=no Do not strip existing signatures of user record\n" - " when creating\n" - " --prompt-new-user firstboot: Query user interactively for user\n" - " to create\n" - " --prompt-groups=no In first-boot mode, don't prompt for auxiliary\n" - " group memberships\n" - " --prompt-shell=no In first-boot mode, don't prompt for shells\n" - " --chrome=no In first-boot mode, don't show colour bar at top\n" - " and bottom of terminal\n" - " --mute-console=yes In first-boot mode, tell kernel/PID 1 to not\n" - " write to the console while running\n" - "\n%4$sGeneral User Record Properties:%5$s\n" - " -c --real-name=REALNAME Real name for user\n" - " --realm=REALM Realm to create user in\n" - " --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" - " -G --member-of=GROUP Add user to group\n" - " --capability-bounding-set=CAPS\n" - " Bounding POSIX capability set\n" - " --capability-ambient-set=CAPS\n" - " Ambient POSIX capability set\n" - " --access-mode=MODE User home directory access mode\n" - " --umask=MODE Umask for user when logging in\n" - " --skel=PATH Skeleton directory to use\n" - " --shell=PATH Shell for account\n" - " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" - " --timezone=TIMEZONE Set a time-zone\n" - " --language=LOCALE Set preferred languages\n" - " --default-area=AREA Select default area\n" - "\n%4$sAuthentication User Record Properties:%5$s\n" - " --ssh-authorized-keys=KEYS\n" - " Specify SSH public keys\n" - " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" - " private key and matching X.509 certificate\n" - " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" - " extension\n" - " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the\n" - " account\n" - " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the\n" - " account\n" - " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock\n" - " the account\n" - " --recovery-key=BOOL Add a recovery key\n" - "\n%4$sBlob Directory User Record Properties:%5$s\n" - " -b --blob=[FILENAME=]PATH\n" - " Path to a replacement blob directory, or replace\n" - " an individual files in the blob directory.\n" - " --avatar=PATH Path to user avatar picture\n" - " --login-background=PATH Path to user login background picture\n" - "\n%4$sAccount Management User Record Properties:%5$s\n" - " --locked=BOOL Set locked account state\n" - " --not-before=TIMESTAMP Do not allow logins before\n" - " --not-after=TIMESTAMP Do not allow logins after\n" - " --rate-limit-interval=SECS\n" - " Login rate-limit interval in seconds\n" - " --rate-limit-burst=NUMBER\n" - " Login rate-limit attempts per interval\n" - "\n%4$sPassword Policy User Record Properties:%5$s\n" - " --password-hint=HINT Set Password hint\n" - " --enforce-password-policy=BOOL\n" - " Control whether to enforce system's password\n" - " policy for this user\n" - " -P Same as --enforce-password-policy=no\n" - " --password-change-now=BOOL\n" - " Require the password to be changed on next login\n" - " --password-change-min=TIME\n" - " Require minimum time between password changes\n" - " --password-change-max=TIME\n" - " Require maximum time between password changes\n" - " --password-change-warn=TIME\n" - " How much time to warn before password expiry\n" - " --password-change-inactive=TIME\n" - " How much time to block password after expiry\n" - "\n%4$sResource Management User Record Properties:%5$s\n" - " --disk-size=BYTES Size to assign the user on disk\n" - " --nice=NICE Nice level for user\n" - " --rlimit=LIMIT=VALUE[:VALUE]\n" - " Set resource limits\n" - " --tasks-max=MAX Set maximum number of per-user tasks\n" - " --memory-high=BYTES Set high memory threshold in bytes\n" - " --memory-max=BYTES Set maximum memory limit\n" - " --cpu-weight=WEIGHT Set CPU weight\n" - " --io-weight=WEIGHT Set IO weight\n" - " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" - " --dev-shm-limit=BYTES|PERCENT\n" - " Set limit on /dev/shm/\n" - "\n%4$sStorage User Record Properties:%5$s\n" - " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" - " subvolume, cifs)\n" - " --image-path=PATH Path to image file/directory\n" - " --drop-caches=BOOL Whether to automatically drop caches on logout\n" - "\n%4$sLUKS Storage User Record Properties:%5$s\n" - " --fs-type=TYPE File system type to use in case of luks\n" - " storage (btrfs, ext4, xfs)\n" - " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" - " when activated (mounted)\n" - " --luks-offline-discard=BOOL\n" - " Whether to trim file on logout\n" - " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" - " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" - " --luks-volume-key-size=BITS\n" - " Volume key size to use for LUKS encryption\n" - " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" - " --luks-pbkdf-hash-algorithm=ALGORITHM\n" - " PBKDF hash algorithm to use\n" - " --luks-pbkdf-time-cost=SECS\n" - " Time cost for PBKDF in seconds\n" - " --luks-pbkdf-memory-cost=BYTES\n" - " Memory cost for PBKDF in bytes\n" - " --luks-pbkdf-parallel-threads=NUMBER\n" - " Number of parallel threads for PKBDF\n" - " --luks-sector-size=BYTES\n" - " Sector size for LUKS encryption in bytes\n" - " --luks-extra-mount-options=OPTIONS\n" - " LUKS extra mount options\n" - " --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n" - " --rebalance-weight=WEIGHT Weight while rebalancing\n" - "\n%4$sMounting User Record Properties:%5$s\n" - " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" - " --nodev=BOOL Control the 'nodev' flag of the home mount\n" - " --noexec=BOOL Control the 'noexec' flag of the home mount\n" - "\n%4$sCIFS User Record Properties:%5$s\n" - " --cifs-domain=DOMAIN CIFS (Windows) domain\n" - " --cifs-user-name=USER CIFS (Windows) user name\n" - " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" - " --cifs-extra-mount-options=OPTIONS\n" - " CIFS (Windows) extra mount options\n" - "\n%4$sLogin Behaviour User Record Properties:%5$s\n" - " --stop-delay=SECS How long to leave user services running after\n" - " logout\n" - " --kill-processes=BOOL Whether to kill user processes when sessions\n" - " terminate\n" - " --auto-login=BOOL Try to log this user in automatically\n" - " --session-launcher=LAUNCHER\n" - " Preferred session launcher file\n" - " --session-type=TYPE Preferred session type\n" - "\nSee the %6$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + " lock-all Lock all suitable home areas\n"); + + help_section("Other Commands"); + printf(" rebalance Rebalance free space between home areas\n" + " firstboot Run first-boot home area creation wizard\n"); + + help_section("Options"); + r = table_print_or_warn(tables[0]); + if (r < 0) + return r; + + for (size_t i = 1; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("homectl", "1"); return 0; } @@ -4137,214 +4009,8 @@ static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } -static int parse_argv(int argc, char *argv[]) { +static int parse_argv(int argc, char *argv[], char ***remaining_args) { _cleanup_strv_free_ char **arg_languages = NULL; - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_OFFLINE, - ARG_REALM, - ARG_ALIAS, - ARG_EMAIL_ADDRESS, - ARG_DISK_SIZE, - ARG_ACCESS_MODE, - ARG_STORAGE, - ARG_FS_TYPE, - ARG_IMAGE_PATH, - ARG_UMASK, - ARG_LUKS_DISCARD, - ARG_LUKS_OFFLINE_DISCARD, - ARG_JSON, - ARG_SETENV, - ARG_TIMEZONE, - ARG_LANGUAGE, - ARG_LOCKED, - ARG_SSH_AUTHORIZED_KEYS, - ARG_LOCATION, - ARG_BIRTH_DATE, - ARG_ICON_NAME, - ARG_PASSWORD_HINT, - ARG_NICE, - ARG_RLIMIT, - ARG_NOT_BEFORE, - ARG_NOT_AFTER, - ARG_LUKS_CIPHER, - ARG_LUKS_CIPHER_MODE, - ARG_LUKS_VOLUME_KEY_SIZE, - ARG_NOSUID, - ARG_NODEV, - ARG_NOEXEC, - ARG_CIFS_DOMAIN, - ARG_CIFS_USER_NAME, - ARG_CIFS_SERVICE, - ARG_CIFS_EXTRA_MOUNT_OPTIONS, - ARG_TASKS_MAX, - ARG_MEMORY_HIGH, - ARG_MEMORY_MAX, - ARG_CPU_WEIGHT, - ARG_IO_WEIGHT, - ARG_LUKS_PBKDF_TYPE, - ARG_LUKS_PBKDF_HASH_ALGORITHM, - ARG_LUKS_PBKDF_FORCE_ITERATIONS, - ARG_LUKS_PBKDF_TIME_COST, - ARG_LUKS_PBKDF_MEMORY_COST, - ARG_LUKS_PBKDF_PARALLEL_THREADS, - ARG_LUKS_SECTOR_SIZE, - ARG_RATE_LIMIT_INTERVAL, - ARG_RATE_LIMIT_BURST, - ARG_STOP_DELAY, - ARG_KILL_PROCESSES, - ARG_ENFORCE_PASSWORD_POLICY, - ARG_PASSWORD_CHANGE_NOW, - ARG_PASSWORD_CHANGE_MIN, - ARG_PASSWORD_CHANGE_MAX, - ARG_PASSWORD_CHANGE_WARN, - ARG_PASSWORD_CHANGE_INACTIVE, - ARG_EXPORT_FORMAT, - ARG_AUTO_LOGIN, - ARG_SESSION_LAUNCHER, - ARG_SESSION_TYPE, - ARG_PKCS11_TOKEN_URI, - ARG_FIDO2_DEVICE, - ARG_FIDO2_WITH_PIN, - ARG_FIDO2_WITH_UP, - ARG_FIDO2_WITH_UV, - ARG_RECOVERY_KEY, - ARG_DROP_CACHES, - ARG_LUKS_EXTRA_MOUNT_OPTIONS, - ARG_AUTO_RESIZE_MODE, - ARG_REBALANCE_WEIGHT, - ARG_FIDO2_CRED_ALG, - ARG_CAPABILITY_BOUNDING_SET, - ARG_CAPABILITY_AMBIENT_SET, - ARG_PROMPT_NEW_USER, - ARG_AVATAR, - ARG_LOGIN_BACKGROUND, - ARG_TMP_LIMIT, - ARG_DEV_SHM_LIMIT, - ARG_DEFAULT_AREA, - ARG_KEY_NAME, - ARG_SEIZE, - ARG_MATCH, - ARG_PROMPT_SHELL, - ARG_PROMPT_GROUPS, - ARG_CHROME, - ARG_MUTE_CONSOLE, - }; - - 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 }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "identity", required_argument, NULL, 'I' }, - { "real-name", required_argument, NULL, 'c' }, - { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "realm", required_argument, NULL, ARG_REALM }, - { "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) */ - { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ - { "member-of", required_argument, NULL, 'G' }, - { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ - { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "language", required_argument, NULL, ARG_LANGUAGE }, - { "locked", required_argument, NULL, ARG_LOCKED }, - { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, - { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, - { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, - { "umask", required_argument, NULL, ARG_UMASK }, - { "nice", required_argument, NULL, ARG_NICE }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, - { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, - { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, - { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, - { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, - { "storage", required_argument, NULL, ARG_STORAGE }, - { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, - { "fs-type", required_argument, NULL, ARG_FS_TYPE }, - { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, - { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, - { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, - { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, - { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, - { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, - { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, - { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, - { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, - { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, - { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, - { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, - { "nosuid", required_argument, NULL, ARG_NOSUID }, - { "nodev", required_argument, NULL, ARG_NODEV }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, - { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, - { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, - { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, - { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, - { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, - { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, - { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, - { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, - { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, - { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, - { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, - { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, - { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, - { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, - { "session-launcher", required_argument, NULL, ARG_SESSION_LAUNCHER, }, - { "session-type", required_argument, NULL, ARG_SESSION_TYPE, }, - { "json", required_argument, NULL, ARG_JSON }, - { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, - { "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-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 }, - { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, - { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, - { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, - { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, - { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, - { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, - { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, - { "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER }, - { "blob", required_argument, NULL, 'b' }, - { "avatar", required_argument, NULL, ARG_AVATAR }, - { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, - { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, - { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, - { "default-area", required_argument, NULL, ARG_DEFAULT_AREA }, - { "key-name", required_argument, NULL, ARG_KEY_NAME }, - { "seize", required_argument, NULL, ARG_SEIZE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "prompt-shell", required_argument, NULL, ARG_PROMPT_SHELL }, - { "prompt-groups", required_argument, NULL, ARG_PROMPT_GROUPS }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; - int r; /* This points to one of arg_identity_extra, arg_identity_extra_this_machine, @@ -4354,6 +4020,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); + assert(remaining_args); /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not * hooked up everywhere let's make it an environment variable only. */ @@ -4363,62 +4030,59 @@ static int parse_argv(int argc, char *argv[]) { else if (r != -ENXIO) log_debug_errno(r, "Unable to parse $SYSTEMD_HOME_DRY_RUN, ignoring: %m"); - for (;;) { - int c; - - c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPENAT", options, NULL); - if (c < 0) - break; + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_OFFLINE: + OPTION_LONG("offline", NULL, "Don't update record embedded in home directory"): arg_offline = true; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'I': - arg_identity = optarg; + OPTION('I', "identity", "PATH", "Read JSON identity from file"): + arg_identity = opts.arg; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.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 ARG_EXPORT_FORMAT: - if (streq(optarg, "help")) + OPTION_LONG("export-format", "FORMAT", + "Strip JSON inspection data (full, stripped, minimal)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); - arg_export_format = export_format_from_string(optarg); + arg_export_format = export_format_from_string(opts.arg); if (arg_export_format < 0) - return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); + return log_error_errno(arg_export_format, "Invalid export format: %s", opts.arg); break; - case 'E': + OPTION_SHORT('E', NULL, "Same as -j --export-format=stripped"): {} + OPTION_HELP_VERBATIM("-EE", "Same as -j --export-format=minimal"): if (arg_export_format == EXPORT_FORMAT_FULL) arg_export_format = EXPORT_FORMAT_STRIPPED; else if (arg_export_format == EXPORT_FORMAT_STRIPPED) @@ -4431,259 +4095,272 @@ static int parse_argv(int argc, char *argv[]) { arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case ARG_KEY_NAME: - if (!isempty(optarg) && !filename_is_valid(optarg)) + OPTION_LONG("key-name", "NAME", "Key name when adding a signing key"): + if (!isempty(opts.arg) && !filename_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for --key-name= not a valid filename: %s", optarg); + "Parameter for --key-name= not a valid filename: %s", opts.arg); - r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + r = free_and_strdup_warn(&arg_key_name, empty_to_null(opts.arg)); if (r < 0) return r; break; - case ARG_SEIZE: - r = parse_boolean_argument("--seize=", optarg, &arg_seize); + OPTION_LONG("seize", "BOOL", + "Whether to strip existing signatures of user record when creating"): + r = parse_boolean_argument("--seize=", opts.arg, &arg_seize); if (r < 0) return r; break; - case ARG_PROMPT_NEW_USER: + OPTION_LONG("prompt-new-user", NULL, + "firstboot: Query user interactively for user to create"): arg_prompt_new_user = true; break; - case ARG_PROMPT_GROUPS: - r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); + OPTION_LONG("prompt-groups", "BOOL", + "In first-boot mode, don't prompt for auxiliary group memberships"): + r = parse_boolean_argument("--prompt-groups=", opts.arg, &arg_prompt_groups); if (r < 0) return r; - break; - case ARG_PROMPT_SHELL: - r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); + OPTION_LONG("prompt-shell", "BOOL", + "In first-boot mode, don't prompt for shells"): + r = parse_boolean_argument("--prompt-shell=", opts.arg, &arg_prompt_shell); if (r < 0) return r; - break; - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + OPTION_LONG("chrome", "BOOL", + "In first-boot mode, don't show colour bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", opts.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", + "In first-boot mode, tell kernel/PID 1 to not write to the console while running"): + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); if (r < 0) return r; - 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_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'c': - if (!isempty(optarg) && !valid_gecos(optarg)) + OPTION_GROUP("General User Record Properties"): {} + + OPTION('c', "real-name", "REALNAME", "Real name for user"): {} + OPTION_LONG("comment", "REALNAME", /* help= */ NULL): /* Compat alias to keep things in sync with useradd(8) */ + if (!isempty(opts.arg) && !valid_gecos(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid GECOS field '%s'.", optarg); + "Invalid GECOS field '%s'.", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", opts.arg); if (r < 0) return r; break; - case ARG_REALM: - r = parse_realm_field(&arg_identity_extra, "realm", optarg); + OPTION_LONG("realm", "REALM", "Realm to create user in"): + r = parse_realm_field(&arg_identity_extra, "realm", opts.arg); if (r < 0) return r; break; - case ARG_ALIAS: - r = parse_group_field(&arg_identity_extra, "aliases", optarg); + OPTION_LONG("alias", "ALIAS", "Define alias usernames for this account"): + r = parse_group_field(&arg_identity_extra, "aliases", opts.arg); if (r < 0) return r; break; - case ARG_EMAIL_ADDRESS: - r = parse_string_field(match_identity ?: &arg_identity_extra, "emailAddress", optarg); + OPTION_LONG("email-address", "EMAIL", "Email address for user"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "emailAddress", opts.arg); if (r < 0) return r; break; - case ARG_LOCATION: - r = parse_string_field(match_identity ?: &arg_identity_extra, "location", optarg); + OPTION_LONG("location", "LOCATION", "Set location of user on earth"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "location", opts.arg); if (r < 0) return r; break; - case ARG_BIRTH_DATE: - if (isempty(optarg)) { + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "birth-date", "DATE", + "Set user birth date (YYYY-MM-DD)"): + if (isempty(opts.arg)) { r = drop_from_identity("birthDate"); if (r < 0) return r; } else { - r = parse_birth_date(optarg, /* ret= */ NULL); + r = parse_birth_date(opts.arg, /* ret= */ NULL); if (r < 0) - return log_error_errno(r, "Invalid birth date (expected YYYY-MM-DD): %s", optarg); + return log_error_errno(r, "Invalid birth date (expected YYYY-MM-DD): %s", opts.arg); - r = parse_string_field(&arg_identity_extra, "birthDate", optarg); + r = parse_string_field(&arg_identity_extra, "birthDate", opts.arg); if (r < 0) return r; } break; - case ARG_ICON_NAME: - r = parse_string_field(match_identity ?: &arg_identity_extra, "iconName", optarg); + OPTION_LONG("icon-name", "NAME", "Icon name for user"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "iconName", opts.arg); if (r < 0) return r; break; - case 'd': - r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); + OPTION('d', "home-dir", "PATH", "Home directory"): /* Compatible with useradd(8) */ + r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", opts.arg); if (r < 0) return r; break; - case 'u': - r = parse_uid_field(&arg_identity_extra, "uid", optarg); + OPTION('u', "uid", "UID", "Numeric UID for user"): /* Compatible with useradd(8) */ + r = parse_uid_field(&arg_identity_extra, "uid", opts.arg); if (r < 0) return r; break; - case 'G': - r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); + OPTION('G', "member-of", "GROUP", "Add user to group"): {} + OPTION_LONG("groups", "GROUP", /* help= */ NULL): /* Compat alias to keep things in sync with useradd(8) */ + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", opts.arg); if (r < 0) return r; break; - case ARG_CAPABILITY_BOUNDING_SET: + OPTION_LONG("capability-bounding-set", "CAPS", "Bounding POSIX capability set"): r = parse_capability_set_field(match_identity ?: &arg_identity_extra, &arg_capability_bounding_set, - "capabilityBoundingSet", optarg); + "capabilityBoundingSet", opts.arg); if (r < 0) return r; break; - case ARG_CAPABILITY_AMBIENT_SET: + OPTION_LONG("capability-ambient-set", "CAPS", "Ambient POSIX capability set"): r = parse_capability_set_field(match_identity ?: &arg_identity_extra, &arg_capability_ambient_set, - "capabilityAmbientSet", optarg); + "capabilityAmbientSet", opts.arg); if (r < 0) return r; break; - case ARG_ACCESS_MODE: - r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); + OPTION_LONG("access-mode", "MODE", "User home directory access mode"): + r = parse_mode_field(&arg_identity_extra, "accessMode", opts.arg); if (r < 0) return r; break; - case ARG_UMASK: - r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); + OPTION_LONG("umask", "MODE", "Umask for user when logging in"): + r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", opts.arg); if (r < 0) return r; break; - case 'k': - r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "skeletonDirectory", optarg); + OPTION('k', "skel", "PATH", "Skeleton directory to use"): /* Compatible with useradd(8) */ + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "skeletonDirectory", opts.arg); if (r < 0) return r; break; - case 's': - if (!isempty(optarg) && !valid_shell(optarg)) + OPTION('s', "shell", "PATH", "Shell for account"): /* Compatible with useradd(8) */ + if (!isempty(opts.arg) && !valid_shell(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Shell '%s' not valid.", optarg); + "Shell '%s' not valid.", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", opts.arg); if (r < 0) return r; break; - case ARG_SETENV: - r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", optarg); + OPTION_LONG("setenv", "VARIABLE[=VALUE]", "Set an environment variable at log-in"): + r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", opts.arg); if (r < 0) return r; break; - case ARG_TIMEZONE: - if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) + OPTION_LONG("timezone", "TIMEZONE", "Set a time-zone"): + if (!isempty(opts.arg) && !timezone_is_valid(opts.arg, LOG_DEBUG)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone '%s' is not valid.", optarg); + "Timezone '%s' is not valid.", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", opts.arg); if (r < 0) return r; break; - case ARG_LANGUAGE: - r = parse_language_field(&arg_languages, optarg); + OPTION_LONG("language", "LOCALE", "Set preferred languages"): + r = parse_language_field(&arg_languages, opts.arg); if (r < 0) return r; break; - case ARG_DEFAULT_AREA: - r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + OPTION_LONG("default-area", "AREA", "Select default area"): + r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", opts.arg); if (r < 0) return r; break; - case ARG_SSH_AUTHORIZED_KEYS: - r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); + OPTION_GROUP("Authentication User Record Properties"): {} + + OPTION_LONG("ssh-authorized-keys", "KEYS", "Specify SSH public keys"): + r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", opts.arg); if (r < 0) return r; - break; - case ARG_PKCS11_TOKEN_URI: - r = parse_pkcs11_token_uri_field(optarg); + OPTION_LONG("pkcs11-token-uri", "URI", + "URI to PKCS#11 security token containing private key and matching X.509 certificate"): + r = parse_pkcs11_token_uri_field(opts.arg); if (r <= 0) return r; break; - case ARG_FIDO2_DEVICE: - r = parse_fido2_device_field(optarg); + OPTION_LONG("fido2-device", "PATH", + "Path to FIDO2 hidraw device with hmac-secret extension"): + r = parse_fido2_device_field(opts.arg); if (r <= 0) return r; 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 account"): + r = parse_boolean_argument("--fido2-with-client-pin=", opts.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 account"): + r = parse_boolean_argument("--fido2-with-user-presence=", opts.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 account"): + r = parse_boolean_argument("--fido2-with-user-verification=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_RECOVERY_KEY: - r = parse_boolean(optarg); + OPTION_LONG("recovery-key", "BOOL", "Add a recovery key"): + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", opts.arg); arg_recovery_key = r; r = drop_from_identity("recoveryKey", "recoveryKeyType"); @@ -4691,37 +4368,41 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case 'b': - case ARG_AVATAR: - case ARG_LOGIN_BACKGROUND: { + OPTION_GROUP("Blob Directory User Record Properties"): {} + + OPTION('b', "blob", "[FILENAME=]PATH", + "Path to a replacement blob directory, or replace an individual files in the blob directory"): {} + OPTION_LONG("avatar", "PATH", "Path to user avatar picture"): {} + OPTION_LONG("login-background", "PATH", "Path to user login background picture"): { _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *path = NULL, *filename = NULL; + const char *long_code = opts.opt->long_code; - if (c == 'b') { - char *eq; + if (streq(long_code, "blob")) { + const char *eq; - if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ + if (isempty(opts.arg)) { /* --blob= deletes everything, including existing blob dirs */ hashmap_clear(arg_blob_files); arg_blob_dir = mfree(arg_blob_dir); arg_blob_clear = true; break; } - eq = strrchr(optarg, '='); + eq = strrchr(opts.arg, '='); if (!eq) { /* --blob=/some/path replaces the blob dir */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_blob_dir); if (r < 0) return r; break; } /* --blob=filename=/some/path replaces the file "filename" with /some/path */ - filename = strndup(optarg, eq - optarg); + filename = strndup(opts.arg, eq - opts.arg); if (!filename) return log_oom(); if (isempty(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", opts.arg); if (!suitable_blob_filename(filename)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); @@ -4729,17 +4410,11 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; } else { - const char *well_known_filename = - c == ARG_AVATAR ? "avatar" : - c == ARG_LOGIN_BACKGROUND ? "login-background" : - NULL; - assert(well_known_filename); - - filename = strdup(well_known_filename); + filename = strdup(long_code); if (!filename) return log_oom(); - r = parse_path_argument(optarg, /* suppress_root= */ false, &path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &path); if (r < 0) return r; } @@ -4763,406 +4438,420 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LOCKED: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "locked", optarg); + OPTION_GROUP("Account Management User Record Properties"): {} + + OPTION_LONG("locked", "BOOL", "Set locked account state"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "locked", opts.arg); if (r < 0) return r; break; - case ARG_NOT_BEFORE: - case ARG_NOT_AFTER: - case 'e': { - const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; + OPTION_LONG("not-before", "TIMESTAMP", "Do not allow logins before"): {} + OPTION_LONG("not-after", "TIMESTAMP", "Do not allow logins after"): {} + OPTION_LONG("expiredate", "TIMESTAMP", /* help= */ NULL): /* Compat alias for -e to keep things in sync with useradd(8) */ { + const char *field = streq(opts.opt->long_code, "not-before") ? "notBeforeUSec" : "notAfterUSec"; - r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, opts.arg); if (r < 0) return r; break; } - case ARG_RATE_LIMIT_INTERVAL: - r = parse_time_field(match_identity ?: &arg_identity_extra, "rateLimitIntervalUSec", optarg); + OPTION_SHORT('e', "TIMESTAMP", /* help= */ NULL): /* -e alias for --expiredate */ + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, "notAfterUSec", opts.arg); if (r < 0) return r; break; - case ARG_RATE_LIMIT_BURST: - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "rateLimitBurst", optarg); + OPTION_LONG("rate-limit-interval", "SECS", "Login rate-limit interval in seconds"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "rateLimitIntervalUSec", opts.arg); if (r < 0) return r; break; - case ARG_PASSWORD_HINT: - r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); + OPTION_LONG("rate-limit-burst", "NUMBER", "Login rate-limit attempts per interval"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "rateLimitBurst", opts.arg); if (r < 0) return r; + break; - string_erase(optarg); + OPTION_GROUP("Password Policy User Record Properties"): {} + + OPTION_LONG("password-hint", "HINT", "Set Password hint"): + r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", opts.arg); + if (r < 0) + return r; + + string_erase((char *) opts.arg); break; - case ARG_ENFORCE_PASSWORD_POLICY: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "enforcePasswordPolicy", optarg); + OPTION_LONG("enforce-password-policy", "BOOL", + "Control whether to enforce system's password policy for this user"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "enforcePasswordPolicy", opts.arg); if (r < 0) return r; break; - case 'P': + OPTION_SHORT('P', NULL, "Same as --enforce-password-policy=no"): r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); break; - case ARG_PASSWORD_CHANGE_NOW: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "passwordChangeNow", optarg); + OPTION_LONG("password-change-now", "BOOL", + "Require the password to be changed on next login"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "passwordChangeNow", opts.arg); if (r < 0) return r; break; - case ARG_PASSWORD_CHANGE_MIN: - case ARG_PASSWORD_CHANGE_MAX: - case ARG_PASSWORD_CHANGE_WARN: - case ARG_PASSWORD_CHANGE_INACTIVE: { + OPTION_LONG("password-change-min", "TIME", "Require minimum time between password changes"): {} + OPTION_LONG("password-change-max", "TIME", "Require maximum time between password changes"): {} + OPTION_LONG("password-change-warn", "TIME", "How much time to warn before password expiry"): {} + OPTION_LONG("password-change-inactive", "TIME", "How much time to block password after expiry"): { + const char *lc = opts.opt->long_code; const char *field = - c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : - c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" : - c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" : - c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" : - NULL; + streq(lc, "password-change-min") ? "passwordChangeMinUSec" : + streq(lc, "password-change-max") ? "passwordChangeMaxUSec" : + streq(lc, "password-change-warn") ? "passwordChangeWarnUSec" : + streq(lc, "password-change-inactive") ? "passwordChangeInactiveUSec" : + NULL; assert(field); - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_time_field(match_identity ?: &arg_identity_extra, field, opts.arg); if (r < 0) return r; break; } - case ARG_DISK_SIZE: - r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); + OPTION_GROUP("Resource Management User Record Properties"): {} + + OPTION_LONG("disk-size", "BYTES", "Size to assign the user on disk"): + r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, opts.arg); if (r < 0) return r; break; - case ARG_NICE: - r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); + OPTION_LONG("nice", "NICE", "Nice level for user"): + r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", opts.arg); if (r < 0) return r; break; - case ARG_RLIMIT: - r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); + OPTION_LONG("rlimit", "LIMIT=VALUE[:VALUE]", "Set resource limits"): + r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", opts.arg); if (r < 0) return r; break; - case ARG_TASKS_MAX: - r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); + OPTION_LONG("tasks-max", "MAX", "Set maximum number of per-user tasks"): + r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", opts.arg); if (r < 0) return r; break; - case ARG_MEMORY_HIGH: - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryHigh", optarg); + OPTION_LONG("memory-high", "BYTES", "Set high memory threshold in bytes"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryHigh", opts.arg); if (r < 0) return r; break; - case ARG_MEMORY_MAX: - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryMax", optarg); + OPTION_LONG("memory-max", "BYTES", "Set maximum memory limit"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryMax", opts.arg); if (r < 0) return r; break; - case ARG_CPU_WEIGHT: - case ARG_IO_WEIGHT: { - const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" : - c == ARG_IO_WEIGHT ? "ioWeight" : - NULL; + OPTION_LONG("cpu-weight", "WEIGHT", "Set CPU weight"): {} + OPTION_LONG("io-weight", "WEIGHT", "Set IO weight"): { + const char *field = streq(opts.opt->long_code, "cpu-weight") ? "cpuWeight" : "ioWeight"; - r = parse_weight_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_weight_field(match_identity ?: &arg_identity_extra, field, opts.arg); if (r < 0) return r; break; } - case ARG_TMP_LIMIT: - case ARG_DEV_SHM_LIMIT: { - const char *field = - c == ARG_TMP_LIMIT ? "tmpLimit" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : - NULL; - const char *field_scale = - c == ARG_TMP_LIMIT ? "tmpLimitScale" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : - NULL; - - assert(field); - assert(field_scale); + OPTION_LONG("tmp-limit", "BYTES|PERCENT", "Set limit on /tmp/"): {} + OPTION_LONG("dev-shm-limit", "BYTES|PERCENT", "Set limit on /dev/shm/"): { + bool is_tmp = streq(opts.opt->long_code, "tmp-limit"); + const char *field = is_tmp ? "tmpLimit" : "devShmLimit"; + const char *field_scale = is_tmp ? "tmpLimitScale" : "devShmLimitScale"; r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, - field, field_scale, optarg); + field, field_scale, opts.arg); if (r < 0) return r; break; } - case ARG_STORAGE: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_GROUP("Storage User Record Properties"): {} + + OPTION_LONG("storage", "STORAGE", + "Storage type to use (luks, fscrypt, directory, subvolume, cifs)"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "storage", optarg); + "Parameter for field %s not valid: %s", "storage", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "storage", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "storage", opts.arg); if (r < 0) return r; break; - } - case ARG_IMAGE_PATH: - r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "imagePath", optarg); + OPTION_LONG("image-path", "PATH", "Path to image file/directory"): + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "imagePath", opts.arg); if (r < 0) return r; break; - case ARG_DROP_CACHES: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); + OPTION_LONG("drop-caches", "BOOL", "Whether to automatically drop caches on logout"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", opts.arg); if (r < 0) return r; break; - case ARG_FS_TYPE: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_GROUP("LUKS Storage User Record Properties"): {} + + OPTION_LONG("fs-type", "TYPE", + "File system type to use in case of luks storage (btrfs, ext4, xfs)"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "fileSystemType", optarg); + "Parameter for field %s not valid: %s", "fileSystemType", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "fileSystemType", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "fileSystemType", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_DISCARD: - case ARG_LUKS_OFFLINE_DISCARD: { - const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; + OPTION_LONG("luks-discard", "BOOL", + "Whether to use 'discard' feature of file system when activated (mounted)"): {} + OPTION_LONG("luks-offline-discard", "BOOL", "Whether to trim file on logout"): { + const char *field = streq(opts.opt->long_code, "luks-discard") ? "luksDiscard" : "luksOfflineDiscard"; - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, opts.arg); if (r < 0) return r; break; } - case ARG_LUKS_CIPHER: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_LONG("luks-cipher", "CIPHER", "Cipher to use for LUKS encryption"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "luksCipher", optarg); + "Parameter for field %s not valid: %s", "luksCipher", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipher", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipher", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_CIPHER_MODE: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_LONG("luks-cipher-mode", "MODE", "Cipher mode to use for LUKS encryption"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "luksCipherMode", optarg); + "Parameter for field %s not valid: %s", "luksCipherMode", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipherMode", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipherMode", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_VOLUME_KEY_SIZE: - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksVolumeKeySize", optarg); + OPTION_LONG("luks-volume-key-size", "BITS", "Volume key size to use for LUKS encryption"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksVolumeKeySize", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_PBKDF_TYPE: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_LONG("luks-pbkdf-type", "TYPE", "Password-based Key Derivation Function to use"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "luksPbkdfType", optarg); + "Parameter for field %s not valid: %s", "luksPbkdfType", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfType", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfType", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_PBKDF_HASH_ALGORITHM: { - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) + OPTION_LONG("luks-pbkdf-hash-algorithm", "ALG", "PBKDF hash algorithm to use"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", "luksPbkdfHashAlgorithm", optarg); + "Parameter for field %s not valid: %s", "luksPbkdfHashAlgorithm", opts.arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfHashAlgorithm", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfHashAlgorithm", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_PBKDF_TIME_COST: - r = parse_time_field(match_identity ?: &arg_identity_extra, "luksPbkdfTimeCostUSec", optarg); + OPTION_LONG("luks-pbkdf-time-cost", "SECS", "Time cost for PBKDF in seconds"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "luksPbkdfTimeCostUSec", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_PBKDF_MEMORY_COST: - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "luksPbkdfMemoryCost", optarg); + OPTION_LONG("luks-pbkdf-memory-cost", "BYTES", "Memory cost for PBKDF in bytes"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "luksPbkdfMemoryCost", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_PBKDF_PARALLEL_THREADS: - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfParallelThreads", optarg); + OPTION_LONG("luks-pbkdf-parallel-threads", "N", "Number of parallel threads for PKBDF"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfParallelThreads", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_SECTOR_SIZE: - r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); + OPTION_LONG("luks-sector-size", "BYTES", "Sector size for LUKS encryption in bytes"): + r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_EXTRA_MOUNT_OPTIONS: - r = parse_string_field(match_identity ?: &arg_identity_extra, "luksExtraMountOptions", optarg); + OPTION_LONG("luks-extra-mount-options", "…", "LUKS extra mount options"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksExtraMountOptions", opts.arg); if (r < 0) return r; break; - case ARG_LUKS_PBKDF_FORCE_ITERATIONS: - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfForceIterations", optarg); + OPTION_LONG("luks-pbkdf-force-iterations", "NUMBER", /* help= */ NULL): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfForceIterations", opts.arg); if (r < 0) return r; break; - case ARG_AUTO_RESIZE_MODE: + OPTION_LONG("auto-resize-mode", "MODE", + "Automatically grow/shrink home on login/logout"): r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, - "autoResizeMode", optarg); + "autoResizeMode", opts.arg); if (r < 0) return r; break; - case ARG_REBALANCE_WEIGHT: + OPTION_LONG("rebalance-weight", "WEIGHT", "Weight while rebalancing"): r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, - "rebalanceWeight", optarg); + "rebalanceWeight", opts.arg); if (r < 0) return r; break; - case ARG_NOSUID: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoSuid", optarg); + OPTION_GROUP("Mounting User Record Properties"): {} + + OPTION_LONG("nosuid", "BOOL", "Control the 'nosuid' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoSuid", opts.arg); if (r < 0) return r; break; - case ARG_NODEV: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoDevices", optarg); + OPTION_LONG("nodev", "BOOL", "Control the 'nodev' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoDevices", opts.arg); if (r < 0) return r; break; - case ARG_NOEXEC: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoExecute", optarg); + OPTION_LONG("noexec", "BOOL", "Control the 'noexec' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoExecute", opts.arg); if (r < 0) return r; break; - case ARG_CIFS_DOMAIN: - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsDomain", optarg); + OPTION_GROUP("CIFS User Record Properties"): {} + + OPTION_LONG("cifs-domain", "DOMAIN", "CIFS (Windows) domain"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsDomain", opts.arg); if (r < 0) return r; break; - case ARG_CIFS_USER_NAME: - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsUserName", optarg); + OPTION_LONG("cifs-user-name", "USER", "CIFS (Windows) user name"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsUserName", opts.arg); 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); + OPTION_LONG("cifs-service", "SERVICE", + "CIFS (Windows) service to mount as home area"): + if (!isempty(opts.arg)) { + r = parse_cifs_service(opts.arg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); + return log_error_errno(r, "Failed to validate CIFS service name: %s", opts.arg); } - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", opts.arg); if (r < 0) return r; break; - case ARG_CIFS_EXTRA_MOUNT_OPTIONS: - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsExtraMountOptions", optarg); + OPTION_LONG("cifs-extra-mount-options", "…", + "CIFS (Windows) extra mount options"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsExtraMountOptions", opts.arg); if (r < 0) return r; break; - case ARG_STOP_DELAY: - r = parse_time_field(match_identity ?: &arg_identity_extra, "stopDelayUSec", optarg); + OPTION_GROUP("Login Behaviour User Record Properties"): {} + + OPTION_LONG("stop-delay", "SECS", + "How long to leave user services running after logout"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "stopDelayUSec", opts.arg); if (r < 0) return r; break; - case ARG_KILL_PROCESSES: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "killProcesses", optarg); + OPTION_LONG("kill-processes", "BOOL", + "Whether to kill user processes when sessions terminate"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "killProcesses", opts.arg); if (r < 0) return r; break; - case ARG_AUTO_LOGIN: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "autoLogin", optarg); + OPTION_LONG("auto-login", "BOOL", "Try to log this user in automatically"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "autoLogin", opts.arg); if (r < 0) return r; break; - case ARG_SESSION_LAUNCHER: - r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionLauncher", optarg); + OPTION_LONG("session-launcher", "LAUNCHER", "Preferred session launcher file"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionLauncher", opts.arg); if (r < 0) return r; break; - case ARG_SESSION_TYPE: - r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionType", optarg); + OPTION_LONG("session-type", "TYPE", "Preferred session type"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionType", opts.arg); if (r < 0) return r; break; - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); + /* Hidden options below */ + + OPTION_LONG("fido2-credential-algorithm", "ALG", /* help= */ NULL): + r = parse_fido2_algorithm(opts.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", opts.arg); break; - case ARG_MATCH: - if (streq(optarg, "any")) + OPTION_LONG("match", "any|this|other|auto", /* help= */ NULL): + if (streq(opts.arg, "any")) match_identity = &arg_identity_extra; - else if (streq(optarg, "this")) + else if (streq(opts.arg, "this")) match_identity = &arg_identity_extra_this_machine; - else if (streq(optarg, "other")) + else if (streq(opts.arg, "other")) match_identity = &arg_identity_extra_other_machines; - else if (streq(optarg, "auto")) + else if (streq(opts.arg, "auto")) match_identity = NULL; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--machine= argument not understood. Refusing."); break; - case 'A': + OPTION_SHORT('A', NULL, /* help= */ NULL): match_identity = &arg_identity_extra; break; - case 'T': + + OPTION_SHORT('T', NULL, /* help= */ NULL): match_identity = &arg_identity_extra_this_machine; break; - case 'N': + + OPTION_SHORT('N', NULL, /* help= */ NULL): match_identity = &arg_identity_extra_other_machines; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (!strv_isempty(arg_languages)) { char **additional; @@ -5183,6 +4872,7 @@ static int parse_argv(int argc, char *argv[]) { } } + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -5442,6 +5132,7 @@ static int run(int argc, char *argv[]) { {} }; + char **args = NULL; int r; log_setup(); @@ -5453,11 +5144,11 @@ static int run(int argc, char *argv[]) { if (is_fallback_shell(argv[0])) return fallback_shell(argc, argv); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 6cadf7b108d73d8680fb6264bcf1b6355faed876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 17:23:45 +0200 Subject: [PATCH 1756/2155] homectl: convert verbs to VERB macros --help looks pretty much the same. "[list]" is shown in parentheses because it's the default. Co-developed-by: Claude Opus 4.7 (1M context) --- src/home/homectl.c | 179 +++++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 81 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index f635981a06940..42b6359fc6986 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -178,6 +178,9 @@ static int acquire_bus(sd_bus **bus) { return 0; } +VERB_GROUP("Basic User Manipulation Commands"); +VERB(verb_list_homes, "list", /* argspec= */ NULL, VERB_ANY, 1, VERB_DEFAULT, + "List home areas"); 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; @@ -696,6 +699,8 @@ static int inspect_home(sd_bus *bus, const char *name) { return 0; } +VERB(verb_inspect_homes, "inspect", "USER…", VERB_ANY, VERB_ANY, 0, + "Inspect a home area"); 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; @@ -1419,6 +1424,8 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password return 0; } +VERB(verb_create_home, "create", "USER", VERB_ANY, 2, 0, + "Create a home area"); static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -1595,6 +1602,8 @@ static int home_record_reset_human_interaction_permission(UserRecord *hr) { return 0; } +VERB(verb_update_home, "update", "USER", VERB_ANY, 2, 0, + "Update a home area"); 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; @@ -1772,6 +1781,8 @@ static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_passwd_home, "passwd", "USER", VERB_ANY, 2, 0, + "Change password of a home area"); 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; @@ -1889,6 +1900,8 @@ static int parse_disk_size(const char *t, uint64_t *ret) { return 0; } +VERB(verb_resize_home, "resize", "USER SIZE", 2, 3, 0, + "Resize a home area"); 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; @@ -1951,6 +1964,8 @@ static int verb_resize_home(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_remove_home, "remove", "USER…", 2, VERB_ANY, 0, + "Remove a home area"); 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; @@ -1984,6 +1999,9 @@ static int verb_remove_home(int argc, char *argv[], uintptr_t _data, void *userd return ret; } +VERB_GROUP("Advanced User Manipulation Commands"); +VERB(verb_activate_home, "activate", "USER…", 2, VERB_ANY, 0, + "Activate a home area"); 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; @@ -2032,6 +2050,8 @@ static int verb_activate_home(int argc, char *argv[], uintptr_t _data, void *use return ret; } +VERB(verb_deactivate_home, "deactivate", "USER…", 2, VERB_ANY, 0, + "Deactivate a home area"); 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; @@ -2063,6 +2083,8 @@ static int verb_deactivate_home(int argc, char *argv[], uintptr_t _data, void *u return ret; } +VERB_NOARG(verb_deactivate_all_homes, "deactivate-all", + "Deactivate all active home areas"); 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; @@ -2084,6 +2106,8 @@ static int verb_deactivate_all_homes(int argc, char *argv[], uintptr_t _data, vo return 0; } +VERB(verb_with_home, "with", "USER [COMMAND…]", 2, VERB_ANY, 0, + "Run shell or command with access to a home area"); 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; @@ -2241,6 +2265,8 @@ static int authenticate_home(sd_bus *bus, const char *name) { } } +VERB(verb_authenticate_homes, "authenticate", "USER…", VERB_ANY, VERB_ANY, 0, + "Authenticate a home area"); 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; @@ -2266,6 +2292,9 @@ static int verb_authenticate_homes(int argc, char *argv[], uintptr_t _data, void } } +VERB_GROUP("User Migration Commands"); +VERB(verb_adopt_home, "adopt", "PATH…", VERB_ANY, VERB_ANY, 0, + "Add an existing home area on this system"); static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; @@ -2348,6 +2377,8 @@ static int register_home_one(sd_bus *bus, FILE *f, const char *path) { return register_home_common(bus, v); } +VERB(verb_register_home, "register", "PATH…", VERB_ANY, VERB_ANY, 0, + "Register a user record locally"); static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2379,6 +2410,8 @@ static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *use return r; } +VERB(verb_unregister_home, "unregister", "USER…", 2, VERB_ANY, 0, + "Unregister a user record locally"); static int verb_unregister_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2409,6 +2442,9 @@ static int verb_unregister_home(int argc, char *argv[], uintptr_t _data, void *u return ret; } +VERB_GROUP("Signing Keys Commands"); +VERB_NOARG(verb_list_signing_keys, "list-signing-keys", + "List home signing keys"); static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2499,6 +2535,8 @@ static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB(verb_get_signing_key, "get-signing-key", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Get a named home signing key"); static int verb_get_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2555,6 +2593,8 @@ static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { return 0; } +VERB(verb_add_signing_key, "add-signing-key", "FILE…", VERB_ANY, VERB_ANY, 0, + "Add home signing key"); static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2662,6 +2702,8 @@ static int remove_signing_key_one(sd_bus *bus, const char *fn) { return 0; } +VERB(verb_remove_signing_key, "remove-signing-key", "NAME…", 2, VERB_ANY, 0, + "Remove home signing key"); static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2677,6 +2719,9 @@ static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void return r; } +VERB_GROUP("Lock/Unlock Commands"); +VERB(verb_lock_home, "lock", "USER…", 2, VERB_ANY, 0, + "Temporarily lock an active home area"); 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; @@ -2708,6 +2753,8 @@ static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdat return ret; } +VERB(verb_unlock_home, "unlock", "USER…", 2, VERB_ANY, 0, + "Unlock a temporarily locked home area"); 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; @@ -2756,6 +2803,8 @@ static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userd return ret; } +VERB_NOARG(verb_lock_all_homes, "lock-all", + "Lock all suitable home areas"); 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; @@ -2777,6 +2826,9 @@ static int verb_lock_all_homes(int argc, char *argv[], uintptr_t _data, void *us return 0; } +VERB_GROUP("Other Commands"); +VERB_NOARG(verb_rebalance, "rebalance", + "Rebalance free space between home areas"); 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; @@ -3032,6 +3084,8 @@ static int create_interactively(void) { return 0; } +VERB_NOARG(verb_firstboot, "firstboot", + "Run first-boot home area creation wizard"); static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -3917,7 +3971,16 @@ static int parse_fido2_device_field(const char *arg) { } static int help(void) { - static const char* const groups[] = { + static const char* const vgroups[] = { + "Basic User Manipulation Commands", + "Advanced User Manipulation Commands", + "User Migration Commands", + "Signing Keys Commands", + "Lock/Unlock Commands", + "Other Commands", + }; + + static const char* const ogroups[] = { NULL, "General User Record Properties", "Authentication User Record Properties", @@ -3932,70 +3995,55 @@ static int help(void) { "Login Behaviour User Record Properties", }; - Table *tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + Table *otables[ELEMENTSOF(ogroups)] = {}; + CLEANUP_ELEMENTS(otables, table_unref_array_clear); int r; - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_group(groups[i], &tables[i]); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); if (r < 0) return r; } - assert_cc(ELEMENTSOF(tables) == 12); - (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]); + for (size_t i = 0; i < ELEMENTSOF(ogroups); i++) { + r = option_parser_get_help_table_group(ogroups[i], &otables[i]); + if (r < 0) + return r; + } + + /* The two groups are not synchronized because the option table is very wide. */ + + assert_cc(ELEMENTSOF(vtables) == 6); + (void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2], + vtables[3], vtables[4], vtables[5]); + + assert_cc(ELEMENTSOF(otables) == 12); + (void) table_sync_column_widths(0, otables[0], otables[1], otables[2], otables[3], + otables[4], otables[5], otables[6], otables[7], + otables[8], otables[9], otables[10], otables[11]); pager_open(arg_pager_flags); help_cmdline("[OPTIONS…] COMMAND …"); help_abstract("Create, manipulate or inspect home directories."); - help_section("Basic User Manipulation Commands"); - printf(" list List home areas\n" - " inspect USER… Inspect a home area\n" - " create USER Create a home area\n" - " update USER Update a home area\n" - " passwd USER Change password of a home area\n" - " resize USER SIZE Resize a home area\n" - " remove USER… Remove a home area\n"); - - help_section("Advanced User Manipulation Commands"); - printf(" activate USER… Activate a home area\n" - " deactivate USER… Deactivate a home area\n" - " deactivate-all Deactivate all active home areas\n" - " with USER [COMMAND…] Run shell or command with access to a home area\n" - " authenticate USER… Authenticate a home area\n"); - - help_section("User Migration Commands"); - printf(" adopt PATH… Add an existing home area on this system\n" - " register PATH… Register a user record locally\n" - " unregister USER… Unregister a user record locally\n"); - - help_section("Signing Keys Commands"); - printf(" list-signing-keys List home signing keys\n" - " get-signing-key [NAME…] Get a named home signing key\n" - " add-signing-key FILE… Add home signing key\n" - " remove-signing-key NAME… Remove home signing key\n"); - - help_section("Lock/Unlock Commands"); - printf(" lock USER… Temporarily lock an active home area\n" - " unlock USER… Unlock a temporarily locked home area\n" - " lock-all Lock all suitable home areas\n"); - - help_section("Other Commands"); - printf(" rebalance Rebalance free space between home areas\n" - " firstboot Run first-boot home area creation wizard\n"); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } help_section("Options"); - r = table_print_or_warn(tables[0]); + r = table_print_or_warn(otables[0]); if (r < 0) return r; - for (size_t i = 1; i < ELEMENTSOF(groups); i++) { - help_section(groups[i]); - r = table_print_or_warn(tables[i]); + for (size_t i = 1; i < ELEMENTSOF(ogroups); i++) { + help_section(ogroups[i]); + r = table_print_or_warn(otables[i]); if (r < 0) return r; } @@ -4005,9 +4053,7 @@ static int help(void) { 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[], char ***remaining_args) { _cleanup_strv_free_ char **arg_languages = NULL; @@ -5103,35 +5149,6 @@ static int fallback_shell(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "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 }, - {} - }; - char **args = NULL; int r; @@ -5148,7 +5165,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 3b5495c0416c21679f0cca1fbaabb22e093c6c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 18:45:01 +0200 Subject: [PATCH 1757/2155] homectl: fix unlocking of multiple homes Fixes 4aa0a8ac3e548219d380a0c566242338eab185da. --- src/home/homectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 42b6359fc6986..75339d93c8302 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2788,7 +2788,7 @@ static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userd r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - r = handle_generic_user_record_error(argv[1], secret, &error, r, false); + r = handle_generic_user_record_error(*i, secret, &error, r, false); if (r < 0) { if (ret == 0) ret = r; From 23f22047189a291b9ba19185a24264771ab8c467 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 12:54:02 +0200 Subject: [PATCH 1758/2155] nsresourced: detect and clean up registry entries for dead user namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BPF kprobe that fires on user namespace destruction is the only thing that triggers registry cleanup, so any time it doesn't run — ring buffer overflow, kprobe missing, fdstore entry dropped outside our cleanup path — a registry entry is left behind forever. Stamp each registry entry with the kernel's unique namespace identifier (NS_GET_ID, kernel ≥ 6.13) at allocation time. At manager startup, after the existing fdstore→registry sweep, walk the registry and ask the kernel to look each namespace up by id via open_by_handle_at() on nsfs; if the lookup returns -ESTALE the namespace is gone and we release the entry. Old entries written before this change carry no identifier and are left alone. Add a namespace_open_by_id() helper for the lookup. The kernel restricts open_by_handle_at() on nsfs to processes in the initial user namespace, collapsing both permission denials and dead namespaces onto -ESTALE; the helper refuses early with -EHOSTDOWN outside the initial user namespace so callers can tell the two apart. --- src/basic/namespace-util.c | 65 +++++++++++++++++++++++++++ src/basic/namespace-util.h | 2 + src/include/override/fcntl.h | 4 ++ src/include/override/linux/nsfs.h | 5 +++ src/nsresourced/nsresourced-manager.c | 46 +++++++++++++++++++ src/nsresourced/nsresourcework.c | 4 ++ src/nsresourced/userns-registry.c | 2 + src/nsresourced/userns-registry.h | 1 + src/test/test-namespace.c | 53 ++++++++++++++++++++++ 9 files changed, 182 insertions(+) diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 3f355e082f759..fff518ef96957 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -23,6 +23,7 @@ #include "stat-util.h" #include "stdio-util.h" #include "uid-range.h" +#include "unaligned.h" #include "user-util.h" const struct namespace_info namespace_info[_NAMESPACE_TYPE_MAX + 1] = { @@ -860,6 +861,70 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { } } +int namespace_open_by_id(uint64_t ns_id) { + int r; + + /* Looks up a namespace by its unique boot-stable identifier and returns an O_PATH fd to it. + * Requires kernel ≥ 6.13. + * + * Returns -ESTALE if the namespace no longer exists, or if the kernel refuses the lookup + * for permission reasons. The latter happens outside the initial user namespace: the + * kernel only permits open_by_handle_at() on nsfs when the caller is in the initial user + * and pid namespaces with CAP_SYS_ADMIN, with a narrow exception for lookups of the + * caller's own user namespace and its ancestors. To avoid conflating "namespace is dead" + * with "kernel refused us", we refuse early with -EPERM when we aren't in the initial + * user/pid namespace or missing CAP_SYS_ADMIN and let the caller skip the check. */ + + if (ns_id == 0) + return -EINVAL; + + r = namespace_is_init(NAMESPACE_USER); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + r = namespace_is_init(NAMESPACE_PID); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + r = have_effective_cap(CAP_SYS_ADMIN); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + /* The natural way to write this would be a compound designated initializer: + * + * union { ... } fh = { + * .file_handle.handle_bytes = sizeof(struct nsfs_file_handle), + * .file_handle.handle_type = FILEID_NSFS, + * }; + * + * but that only zero-initializes the named struct members of struct file_handle. + * struct file_handle ends with a flexible array (`unsigned char f_handle[]`), whose + * storage comes from the overlapping `space[]` member of the union. Bytes in that storage + * are not covered by the partial struct initializer and end up as stack garbage. Zero the + * entire union first, then fill in the fields explicitly. */ + + union { + struct file_handle file_handle; + uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(struct nsfs_file_handle)]; + } fh = {}; + fh.file_handle.handle_bytes = sizeof(struct nsfs_file_handle); + fh.file_handle.handle_type = FILEID_NSFS; + + /* The first 8 bytes of struct nsfs_file_handle (see , uapi since kernel v6.18) + * are __u64 ns_id; the remaining ns_type/ns_inum fields stay zero so the kernel looks up by + * id alone. The kernel made lookup-by-id-only an explicit ABI guarantee in v6.19 via commit + * 04173501a69e ("nstree: allow lookup solely based on inode"). */ + unaligned_write_ne64(fh.file_handle.f_handle, ns_id); + + return RET_NERRNO(open_by_handle_at(FD_NSFS_ROOT, &fh.file_handle, O_PATH|O_CLOEXEC)); +} + int is_idmapping_supported(const char *path) { _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF; int r; diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index cd2ea786927c3..3bfa34371c0d2 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -88,6 +88,8 @@ bool userns_supported(void); int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid); +int namespace_open_by_id(uint64_t ns_id); + int process_is_owned_by_uid(const PidRef *pidref, uid_t uid); int is_idmapping_supported(const char *path); diff --git a/src/include/override/fcntl.h b/src/include/override/fcntl.h index 875f112b009d1..bf42009022db0 100644 --- a/src/include/override/fcntl.h +++ b/src/include/override/fcntl.h @@ -25,6 +25,10 @@ #define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */ #endif +#ifndef FD_NSFS_ROOT +#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ +#endif + /* Defined since glibc-2.42. * Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */ int openat2_shim(int dfd, const char *filename, const struct open_how *how, size_t usize); diff --git a/src/include/override/linux/nsfs.h b/src/include/override/linux/nsfs.h index a256df1c6f9fa..163333d362843 100644 --- a/src/include/override/linux/nsfs.h +++ b/src/include/override/linux/nsfs.h @@ -12,3 +12,8 @@ #define PROC_PID_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFC)) #define PROC_CGROUP_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFB)) #define PROC_TIME_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFA)) + +/* From kernel-internal include/linux/exportfs.h, not part of uapi. */ +#ifndef FILEID_NSFS +#define FILEID_NSFS 0xf1 +#endif diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index cceaa9c378e74..406db72e7d72a 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -19,6 +19,7 @@ #include "fs-util.h" #include "log.h" #include "mkdir.h" +#include "namespace-util.h" #include "nsresourced-manager.h" #include "parse-util.h" #include "pidfd-util.h" @@ -648,6 +649,51 @@ int manager_startup(Manager *m) { manager_release_userns_by_inode(m, inode); } + /* Look for registry entries whose user namespace has died without us getting a BPF + * notification — e.g. because the BPF ring buffer overflowed, the kprobe is missing, or + * something else dropped the fd store entry without going through our cleanup path. Each + * registry entry stores the kernel's unique namespace identifier; ask the kernel to open + * the namespace by that identifier and release the entry if the lookup fails. Entries + * written by older versions don't carry the identifier, and old kernels (or running + * outside the initial user namespace) don't support lookup by it — in those cases we leave + * the entry alone. */ + + SET_FOREACH(p, registry_inodes) { + uint64_t inode = PTR_TO_UINT32(p); + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + r = userns_registry_load_by_userns_inode(m->registry_fd, inode, &userns_info); + if (r < 0) { + log_debug_errno(r, "Failed to load registry entry for user namespace %" PRIu64 ", ignoring: %m", inode); + continue; + } + + if (userns_info->userns_id == 0) + continue; /* Entry predates ns_id tracking, can't probe authoritatively */ + + _cleanup_close_ int probe_fd = namespace_open_by_id(userns_info->userns_id); + if (probe_fd >= 0) + continue; /* User namespace is still alive */ + /* EPERM/EACCES means we're not in the initial user/pid namespace or missing + * CAP_SYS_ADMIN; ENOTSUP/ENOSYS means the kernel is too old for + * open_by_handle_at() on nsfs. Either way the sweep can't proceed for any + * entry, so bail out rather than logging once per entry. */ + if (ERRNO_IS_NEG_PRIVILEGE(probe_fd) || ERRNO_IS_NEG_NOT_SUPPORTED(probe_fd)) { + log_debug_errno(probe_fd, "Cannot detect stale registry entries, skipping: %m"); + break; + } + /* Anything else except ESTALE is unexpected — log it but skip just this one. */ + if (probe_fd != -ESTALE) { + log_debug_errno(probe_fd, "Failed to probe liveness of user namespace %" PRIu64 " (id %" PRIu64 "), ignoring: %m", + inode, userns_info->userns_id); + continue; + } + + log_debug("Registry entry for user namespace %" PRIu64 " (id %" PRIu64 ") refers to a dead namespace, removing.", + inode, userns_info->userns_id); + manager_release_userns_by_inode(m, inode); + } + r = manager_make_listen_socket(m); if (r < 0) return r; diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index 91b3645809a92..a366e9421ab93 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -1295,6 +1295,8 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para userns_info->owner = peer_uid; userns_info->userns_inode = userns_st.st_ino; + if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0) + log_debug_errno(errno, "Failed to query userns ID, ignoring: %m"); userns_info->size = p.size; userns_info->target_uid = p.target; userns_info->target_gid = (gid_t) p.target; @@ -1575,6 +1577,8 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant * userns_info->owner = peer_uid; userns_info->userns_inode = userns_st.st_ino; + if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0) + log_debug_errno(errno, "Failed to query userns ID, ignoring: %m"); r = userns_registry_store(registry_dir_fd, userns_info); if (r < 0) diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index a9e3f82e59c22..3a0dace7ca3da 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -239,6 +239,7 @@ static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo ** { "owner", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), SD_JSON_MANDATORY }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserNamespaceInfo, name), SD_JSON_MANDATORY }, { "userns", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), SD_JSON_MANDATORY }, + { "usernsId", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_id), 0 }, { "size", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 }, { "start", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_uid), 0 }, { "target", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_uid), 0 }, @@ -565,6 +566,7 @@ int userns_registry_store(int dir_fd, UserNamespaceInfo *info) { SD_JSON_BUILD_PAIR_UNSIGNED("owner", info->owner), SD_JSON_BUILD_PAIR_STRING("name", info->name), SD_JSON_BUILD_PAIR_UNSIGNED("userns", info->userns_inode), + SD_JSON_BUILD_PAIR_CONDITION(info->userns_id != 0, "usernsId", SD_JSON_BUILD_UNSIGNED(info->userns_id)), SD_JSON_BUILD_PAIR_CONDITION(info->size > 0, "size", SD_JSON_BUILD_UNSIGNED(info->size)), SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start_uid), "start", SD_JSON_BUILD_UNSIGNED(info->start_uid)), SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->target_uid), "target", SD_JSON_BUILD_UNSIGNED(info->target_uid)), diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h index 77ff2d6d20760..028d57e48ccca 100644 --- a/src/nsresourced/userns-registry.h +++ b/src/nsresourced/userns-registry.h @@ -29,6 +29,7 @@ typedef struct UserNamespaceInfo { uid_t owner; char *name; uint64_t userns_inode; + uint64_t userns_id; /* Unique namespace identifier from NS_GET_ID, 0 if unavailable */ uint32_t size; uid_t start_uid; uid_t target_uid; diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 7497596cc56fc..96272bc7623d4 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include +#include #include #include #include @@ -282,6 +284,57 @@ TEST(userns_get_base_uid) { ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, &base_gid), ENOMSG); } +TEST(namespace_open_by_id) { + /* Try our own user namespace first to see if the kernel exposes ns_id at all. */ + _cleanup_close_ int userns_fd = ASSERT_OK_ERRNO(open("/proc/self/ns/user", O_RDONLY|O_CLOEXEC)); + + uint64_t ns_id; + int r = RET_NERRNO(ioctl(userns_fd, NS_GET_ID, &ns_id)); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return (void) log_tests_skipped("NS_GET_ID is not supported by this kernel"); + ASSERT_OK(r); + + /* namespace_open_by_id() refuses with -EPERM outside the initial user/pid namespace, since + * the kernel restricts open_by_handle_at() on nsfs to the initial userns and pidns and to + * CAP_SYS_ADMIN. */ + _cleanup_close_ int opened = namespace_open_by_id(ns_id); + if (opened == -EPERM) + return (void) log_tests_skipped("not in initial user namespace or missing CAP_SYS_ADMIN"); + if (IN_SET(opened, -EOPNOTSUPP, -EINVAL)) + return (void) log_tests_skipped("nsfs lookup by ns_id is not supported by this kernel"); + ASSERT_OK(opened); + + struct stat orig_st, opened_st; + ASSERT_OK_ERRNO(fstat(userns_fd, &orig_st)); + ASSERT_OK_ERRNO(fstat(opened, &opened_st)); + ASSERT_EQ(orig_st.st_ino, opened_st.st_ino); + + opened = safe_close(opened); + + ASSERT_ERROR(namespace_open_by_id(0), EINVAL); + + _cleanup_close_ int transient_fd = userns_acquire_empty(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(transient_fd) || ERRNO_IS_NEG_PRIVILEGE(transient_fd)) + return (void) log_tests_skipped("cannot acquire userns for transient lookup test"); + ASSERT_OK(transient_fd); + + uint64_t transient_id; + ASSERT_OK_ERRNO(ioctl(transient_fd, NS_GET_ID, &transient_id)); + ASSERT_NE(transient_id, ns_id); + + opened = ASSERT_OK(namespace_open_by_id(transient_id)); + + struct stat transient_st, transient_opened_st; + ASSERT_OK_ERRNO(fstat(transient_fd, &transient_st)); + ASSERT_OK_ERRNO(fstat(opened, &transient_opened_st)); + ASSERT_EQ(transient_st.st_ino, transient_opened_st.st_ino); + opened = safe_close(opened); + + /* Close the only reference. The namespace is now dead — lookup must fail. */ + transient_fd = safe_close(transient_fd); + ASSERT_ERROR(namespace_open_by_id(transient_id), ESTALE); +} + TEST(process_is_owned_by_uid) { int r; From 7f9dd1366e755ff90796acef478630a7b7e54a9c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 15 May 2026 17:40:28 +0100 Subject: [PATCH 1759/2155] shell-completion: add --when to systemctl Fixes https://github.com/systemd/systemd/issues/41672 --- shell-completion/bash/systemctl.in | 5 ++++- shell-completion/zsh/_systemctl.in | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index 9f52115b7328a..748126ea14a74 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 --kernel-cmdline --drop-in' + --image --boot-loader-menu --boot-loader-entry --reboot-argument --kernel-cmdline --drop-in --when' ) if __contains_word "--user" ${COMP_WORDS[*]}; then @@ -202,6 +202,9 @@ _systemctl () { --boot-loader-entry) comps=$(systemctl --boot-loader-entry=help 2>/dev/null) ;; + --when) + comps='show cancel auto' + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index 828e74c2af15d..ca8b218c2cecc 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -542,4 +542,5 @@ _arguments -s \ '--plain[When used with list-dependencies, print output as a list]' \ '--failed[Show failed units]' \ '--timestamp=[Change format of printed timestamps]:style:_systemctl_timestamp' \ + '--when=[Schedule halt/power-off/reboot/kexec action after a certain timestamp]:timestamp:(show cancel auto)' \ '*::systemctl command:_systemctl_commands' From 3f96c89a520de7cb8857bac7b0d3d17252bf5ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 18:29:29 +0200 Subject: [PATCH 1760/2155] machinectl: reorder verb functions to match --help The net diff is negative because some spurious whitespace and forward declarations were dropped. One new forward declaration was added. (For verb_poweroff_machine. The func could be moved, but I think it's better to keep it adjacent to verb_reboot_machine which is very similar.) --- src/machine/machinectl.c | 1764 +++++++++++++++++++------------------- 1 file changed, 880 insertions(+), 884 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 3d7f782a8c775..1e704e4d96214 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -361,66 +361,6 @@ static int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *use return show_table(table, "machines"); } -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 *table = NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - pager_open(arg_pager_flags); - - r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r)); - - table = table_new("name", "type", "ro", "usage", "created", "modified"); - if (!table) - return log_oom(); - - if (arg_full) - table_set_width(table, 0); - - (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - uint64_t crtime, mtime, size; - const char *name, *type; - int ro_int; - - r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - if (name[0] == '.' && !arg_all) - continue; - - r = table_add_many(table, - TABLE_STRING, name, - TABLE_STRING, type, - TABLE_BOOLEAN, ro_int, - TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, - TABLE_SIZE, size, - TABLE_TIMESTAMP, crtime, - TABLE_TIMESTAMP, mtime); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return show_table(table, "images"); -} - static int show_unit_cgroup( sd_bus *bus, const char *unit, @@ -683,7 +623,6 @@ static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_ } static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { - static const struct bus_properties_map map[] = { { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, @@ -792,396 +731,441 @@ static int verb_show_machine(int argc, char *argv[], uintptr_t _data, void *user return r; } -static int print_image_hostname(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *hn; +static int make_service_name(const char *name, char **ret) { int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name); - if (r < 0) - return r; + assert(name); + assert(ret); + assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX); - r = sd_bus_message_read(reply, "s", &hn); - if (r < 0) - return r; + if (!hostname_is_valid(name, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name %s.", name); - if (!isempty(hn)) - printf("\tHostname: %s\n", hn); + r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); return 0; } -static int print_image_machine_id(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_id128_t id; +static int image_exists(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name); - if (r < 0) - return r; + assert(bus); + assert(name); - r = bus_message_read_id128(reply, &id); - if (r < 0) - return r; + r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE)) + return 0; - if (!sd_id128_is_null(id)) - printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r)); + } - return 0; + return 1; } -static int print_image_machine_info(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; +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); int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name); - if (r < 0) - return r; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + ask_password_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + r = bus_wait_for_jobs_new(bus, &w); if (r < 0) - return r; + return log_error_errno(r, "Could not watch jobs: %m"); - for (;;) { - const char *p, *q; + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; - r = sd_bus_message_read(reply, "{ss}", &p, &q); + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = image_exists(bus, argv[i]); if (r < 0) return r; if (r == 0) - break; + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "Machine image '%s' does not exist.", + argv[i]); - if (streq(p, "DEPLOYMENT")) - printf(" Deployment: %s\n", q); + r = bus_call_method( + bus, + bus_systemd_mgr, + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) + return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); } - r = sd_bus_message_exit_container(reply); + r = bus_wait_for_jobs(w, arg_quiet, NULL); if (r < 0) return r; return 0; } -typedef struct ImageStatusInfo { - const char *name; - const char *path; - const char *type; - bool read_only; - usec_t crtime; - usec_t mtime; - uint64_t usage; - uint64_t limit; - uint64_t usage_exclusive; - uint64_t limit_exclusive; -} ImageStatusInfo; - -static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - assert(bus); - assert(i); +static int parse_machine_uid(const char *spec, const char **machine, char **uid) { + /* + * Whatever is specified in the spec takes priority over global arguments. + */ + char *_uid = NULL; + const char *_machine = NULL; - if (i->name) { - fputs(i->name, stdout); - putchar('\n'); - } + assert(uid); + assert(machine); - if (i->type) - printf("\t Type: %s\n", i->type); + if (spec) { + const char *at; - if (i->path) - printf("\t Path: %s\n", i->path); + at = strchr(spec, '@'); + if (at) { + if (at == spec) + /* Do the same as ssh and refuse "@host". */ + return -EINVAL; - (void) print_image_hostname(bus, i->name); - (void) print_image_machine_id(bus, i->name); - (void) print_image_machine_info(bus, i->name); + _machine = at + 1; + _uid = strndup(spec, at - spec); + if (!_uid) + return -ENOMEM; + } else + _machine = spec; + }; - print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: "); + if (arg_uid && !_uid) { + _uid = strdup(arg_uid); + if (!_uid) + return -ENOMEM; + } - printf("\t RO: %s%s%s\n", - i->read_only ? ansi_highlight_red() : "", - i->read_only ? "read-only" : "writable", - i->read_only ? ansi_normal() : ""); + *uid = _uid; + *machine = isempty(_machine) ? ".host" : _machine; + return 0; +} - if (timestamp_is_set(i->crtime)) - printf("\t Created: %s; %s\n", - FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime)); +static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) { + int r; - if (timestamp_is_set(i->mtime)) - printf("\tModified: %s; %s\n", - FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime)); + assert(event); + assert(machine_removed_slot); + assert(master >= 0); + assert(name); - if (i->usage != UINT64_MAX) { - if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX) - printf("\t Usage: %s (exclusive: %s)\n", - FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive)); + if (!arg_quiet) { + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); else - printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); } - if (i->limit != UINT64_MAX) { - if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX) - printf("\t Limit: %s (exclusive: %s)\n", - FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive)); - else - printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); + _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; + if (!terminal_is_dumb()) { + r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id); + if (r < 0) + return r; } -} - -static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, - { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, - { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, - { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, - { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, - { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, - { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, - { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, - { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, - { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, - {} - }; + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m"); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - ImageStatusInfo info = {}; - int r; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + r = pty_forward_new(event, master, flags, &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); - assert(bus); - assert(path); - assert(new_line); + /* No userdata should not set previously. */ + assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward)); - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - BUS_MAP_BOOLEAN_AS_BOOL, - &error, - &m, - &info); + r = sd_event_loop(event); if (r < 0) - return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to run event loop: %m"); - if (*new_line) - printf("\n"); - *new_line = true; + bool machine_died = + FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) && + !pty_forward_vhangup_honored(forward); - print_image_status_info(bus, &info); + if (!arg_quiet) { + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + } - return r; + return 0; } -typedef struct PoolStatusInfo { - const char *path; - uint64_t usage; - uint64_t limit; -} PoolStatusInfo; +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward *forward = ASSERT_PTR(userdata); + int r; -static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - if (i->path) - printf("\t Path: %s\n", i->path); + assert(m); - if (i->usage != UINT64_MAX) - printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued + * and exit then. */ - if (i->limit != UINT64_MAX) - printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); -} + r = pty_forward_honor_vhangup(forward); + if (r < 0) { + /* On error, quit immediately. */ + log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m"); + (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + } -static int show_pool_info(sd_bus *bus) { + return 0; +} - static const struct bus_properties_map map[] = { - { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, - { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, - { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, - {} - }; +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; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = ASSERT_PTR(userdata); + const char *match, *machine; - PoolStatusInfo info = { - .usage = UINT64_MAX, - .limit = UINT64_MAX, - }; + if (!strv_isempty(arg_setenv) || arg_uid) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; + if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Login only supported on local machines."); - assert(bus); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - map, - 0, - &error, - &m, - &info); + r = sd_event_default(&event); if (r < 0) - return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - - print_pool_status_info(bus, &info); + return log_error_errno(r, "Failed to get event loop: %m"); - return 0; -} + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); -static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; - assert(bus); - assert(path); - assert(new_line); + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); - if (*new_line) - printf("\n"); + r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request machine removal match: %m"); - *new_line = true; + r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine); + if (r < 0) + return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r)); - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL); + r = sd_bus_message_read(reply, "hs", &master, NULL); if (r < 0) - log_error_errno(r, "Could not get properties: %m"); + return bus_log_parse_error(r); - return r; + return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } -static int verb_show_image(int argc, char *argv[], uintptr_t _data, 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; - bool properties, new_line = false; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; sd_bus *bus = ASSERT_PTR(userdata); - int r = 0; + const char *match, *machine, *path; + _cleanup_free_ char *uid = NULL; - properties = !strstr(argv[0], "status"); + if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Shell only supported on local machines."); - pager_open(arg_pager_flags); + if (terminal_is_dumb()) { + /* Set TERM=dumb if we are running on a dumb terminal or with a pipe. + * Otherwise, we will get unwanted OSC sequences. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) + if (strv_extend(&arg_setenv, "TERM=dumb") < 0) + return log_oom(); + } else + /* Pass $TERM & Co. to shell session, if not explicitly specified. */ + FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { + if (strv_find_prefix(arg_setenv, v)) + continue; - if (argc <= 1) { + const char *t = strv_find_prefix(environ, v); + if (!t) + continue; - /* If no argument is specified, inspect the manager - * itself */ + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } - if (properties) - r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); - else - r = show_pool_info(bus); - if (r < 0) - return r; - } + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - for (int i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path = NULL; + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); - r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]); - if (r < 0) - return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r)); + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); + r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse machine specification: %m"); - if (properties) - r = show_image_properties(bus, path, &new_line); - else - r = show_image_info(bus, path, &new_line); - } + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); - return r; -} + r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request machine removal match: %m"); -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; + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; - if (!arg_kill_whom) - arg_kill_whom = "all"; + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); - for (int i = 1; i < argc; i++) { - r = bus_call_method( - bus, - bus_machine_mgr, - "KillMachine", - &error, - NULL, - "ssi", argv[i], arg_kill_whom, arg_signal); - if (r < 0) - return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); - } + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); - return 0; + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "hs", &master, NULL); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, slot, master, /* flags= */ 0, machine); } -static int verb_machine_control_one(const char *machine_name, const char *method); +static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata); -static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, 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; sd_bus *bus = ASSERT_PTR(userdata); int r; + bool enable; (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + enable = streq(argv[0], "enable"); + method = enable ? "EnableUnitFiles" : "DisableUnitFiles"; + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + if (enable) { + r = sd_bus_message_append(m, "s", "machines.target"); + if (r < 0) + return bus_log_create_error(r); + } + 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) + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) return r; - /* 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); + r = image_exists(bus, argv[i]); if (r < 0) - return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r)); - } + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "Machine image '%s' does not exist.", + argv[i]); - return 0; -} + r = sd_bus_message_append(m, "s", unit); + if (r < 0) + return bus_log_create_error(r); + } -static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - int r; + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + if (enable) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); - 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; + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, 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 (enable) { + r = sd_bus_message_read(reply, "b", NULL); if (r < 0) - return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); + return bus_log_parse_error(r); } - return 0; -} + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet); + if (r < 0) + return r; -static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - int r; + r = bus_service_manager_reload(bus); + if (r < 0) + return r; - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + if (arg_now) { + _cleanup_strv_free_ char **new_args = NULL; - 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; + new_args = strv_new(enable ? "start" : "poweroff"); + if (!new_args) + return log_oom(); - /* 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]); + r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false); if (r < 0) - return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r)); + return log_oom(); + + if (enable) + return verb_start_machine(strv_length(new_args), new_args, data, userdata); + + return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata); } return 0; @@ -1258,6 +1242,55 @@ static int verb_machine_control_one(const char *machine_name, const char *method return 0; } +static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { + 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 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_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) { + 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; + + /* 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 0; +} + static int verb_vm_control(int argc, char *argv[], const char *method) { int r; @@ -1280,7 +1313,56 @@ 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) { +static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { + 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)); + } + + return 0; +} + +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; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + if (!arg_kill_whom) + arg_kill_whom = "all"; + + for (int i = 1; i < argc; i++) { + r = bus_call_method( + bus, + bus_machine_mgr, + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_whom, arg_signal); + if (r < 0) + return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static const char* select_copy_method(bool copy_from, bool force) { if (force) return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags"; else @@ -1342,393 +1424,381 @@ static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userda return 0; } -static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, 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; - if (arg_transport != BUS_TRANSPORT_LOCAL) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "bind-volume is only supported on the local transport."); - - _cleanup_(bind_volume_freep) BindVolume *bv = NULL; - r = bind_volume_parse(argv[2], &bv); - if (r < 0) - return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - /* Locate and connect to the target machine before acquiring storage, so a missing - * machine doesn't trigger 'create=new' side effects on the StorageProvider. */ - _cleanup_free_ char *address = NULL; - r = machine_get_control_address(argv[1], &address); - if (r == -EOPNOTSUPP) - return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); - if (r < 0) - return r; - - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - r = sd_varlink_connect_address(&vl, address); + r = bus_call_method( + bus, + bus_machine_mgr, + "BindMountMachine", + &error, + NULL, + "sssbb", + argv[1], + argv[2], + argv[3], + arg_read_only, + arg_mkdir); if (r < 0) - return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address); + return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r)); - r = sd_varlink_set_allow_fd_passing_output(vl, true); - if (r < 0) - return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m"); + return 0; +} - _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; - _cleanup_free_ char *acquire_error_id = NULL; - r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply); - if (r < 0) { - if (acquire_error_id) - return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s", - bv->provider, bv->volume, acquire_error_id); - return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m", - bv->provider, bv->volume); - } +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 *table = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; - int fd_index = sd_varlink_push_fd(vl, reply.fd); - if (fd_index < 0) - return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m"); - TAKE_FD(reply.fd); + pager_open(arg_pager_flags); - _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume); - if (!name) + r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r)); + + table = table_new("name", "type", "ro", "usage", "created", "modified"); + if (!table) return log_oom(); - sd_json_variant *vl_reply = NULL; - const char *error_id = NULL; - r = sd_varlink_callbo( - vl, - "io.systemd.MachineInstance.AddStorage", - &vl_reply, &error_id, - SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index), - SD_JSON_BUILD_PAIR_STRING("name", name), - JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config)); + if (arg_full) + table_set_width(table, 0); + + (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); if (r < 0) - return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m"); - if (error_id) - return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply), - "AddStorage failed for '%s': %s", name, error_id); + return bus_log_parse_error(r); - return 0; -} + for (;;) { + uint64_t crtime, mtime, size; + const char *name, *type; + int ro_int; -static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; + r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; - if (arg_transport != BUS_TRANSPORT_LOCAL) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "unbind-volume is only supported on the local transport."); + if (name[0] == '.' && !arg_all) + continue; - r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL); - if (r == -ENOMEM) - return log_oom(); + r = table_add_many(table, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_BOOLEAN, ro_int, + TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, + TABLE_SIZE, size, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid unbind-volume name '%s', expected ':'.", argv[2]); + return bus_log_parse_error(r); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + return show_table(table, "images"); +} - _cleanup_free_ char *address = NULL; - r = machine_get_control_address(argv[1], &address); - if (r == -EOPNOTSUPP) - return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); +static int print_image_hostname(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *hn; + int r; + + r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name); if (r < 0) return r; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - r = sd_varlink_connect_address(&vl, address); + r = sd_bus_message_read(reply, "s", &hn); if (r < 0) - return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address); + return r; - sd_json_variant *reply = NULL; - const char *error_id = NULL; - r = sd_varlink_callbo( - vl, - "io.systemd.MachineInstance.RemoveStorage", - &reply, &error_id, - SD_JSON_BUILD_PAIR_STRING("name", argv[2])); - if (r < 0) - return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m"); - if (error_id) - return log_error_errno(sd_varlink_error_to_errno(error_id, reply), - "RemoveStorage failed for '%s': %s", argv[2], error_id); + if (!isempty(hn)) + printf("\tHostname: %s\n", hn); return 0; } -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); +static int print_image_machine_id(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_id128_t id; int r; - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name); + if (r < 0) + return r; - r = bus_call_method( - bus, - bus_machine_mgr, - "BindMountMachine", - &error, - NULL, - "sssbb", - argv[1], - argv[2], - argv[3], - arg_read_only, - arg_mkdir); + r = bus_message_read_id128(reply, &id); if (r < 0) - return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r)); + return r; + + if (!sd_id128_is_null(id)) + printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); return 0; } -static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - PTYForward *forward = ASSERT_PTR(userdata); +static int print_image_machine_info(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; int r; - assert(m); + r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name); + if (r < 0) + return r; - /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued - * and exit then. */ + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return r; - r = pty_forward_honor_vhangup(forward); - if (r < 0) { - /* On error, quit immediately. */ - log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m"); - (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + for (;;) { + const char *p, *q; + + r = sd_bus_message_read(reply, "{ss}", &p, &q); + if (r < 0) + return r; + if (r == 0) + break; + + if (streq(p, "DEPLOYMENT")) + printf(" Deployment: %s\n", q); } + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + return 0; } -static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) { - int r; +typedef struct ImageStatusInfo { + const char *name; + const char *path; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; - assert(event); - assert(machine_removed_slot); - assert(master >= 0); - assert(name); +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + assert(bus); + assert(i); - if (!arg_quiet) { - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); } - _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; - if (!terminal_is_dumb()) { - r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id); - if (r < 0) - return r; - } + if (i->type) + printf("\t Type: %s\n", i->type); - r = sd_event_set_signal_exit(event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m"); + if (i->path) + printf("\t Path: %s\n", i->path); - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - r = pty_forward_new(event, master, flags, &forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); + (void) print_image_hostname(bus, i->name); + (void) print_image_machine_id(bus, i->name); + (void) print_image_machine_info(bus, i->name); - /* No userdata should not set previously. */ - assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward)); + print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: "); - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); - bool machine_died = - FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) && - !pty_forward_vhangup_honored(forward); + if (timestamp_is_set(i->crtime)) + printf("\t Created: %s; %s\n", + FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime)); - if (!arg_quiet) { - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); + if (timestamp_is_set(i->mtime)) + printf("\tModified: %s; %s\n", + FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime)); + + if (i->usage != UINT64_MAX) { + if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX) + printf("\t Usage: %s (exclusive: %s)\n", + FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive)); else - log_info("Connection to machine %s terminated.", name); + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); } - return 0; + if (i->limit != UINT64_MAX) { + if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX) + printf("\t Limit: %s (exclusive: %s)\n", + FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive)); + else + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); + } } -static int parse_machine_uid(const char *spec, const char **machine, char **uid) { - /* - * Whatever is specified in the spec takes priority over global arguments. - */ - char *_uid = NULL; - const char *_machine = NULL; +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; - assert(uid); - assert(machine); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + ImageStatusInfo info = {}; + int r; - if (spec) { - const char *at; + assert(bus); + assert(path); + assert(new_line); - at = strchr(spec, '@'); - if (at) { - if (at == spec) - /* Do the same as ssh and refuse "@host". */ - return -EINVAL; + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + BUS_MAP_BOOLEAN_AS_BOOL, + &error, + &m, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - _machine = at + 1; - _uid = strndup(spec, at - spec); - if (!_uid) - return -ENOMEM; - } else - _machine = spec; - }; + if (*new_line) + printf("\n"); + *new_line = true; - if (arg_uid && !_uid) { - _uid = strdup(arg_uid); - if (!_uid) - return -ENOMEM; - } + print_image_status_info(bus, &info); - *uid = _uid; - *machine = isempty(_machine) ? ".host" : _machine; - return 0; + return r; } -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; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = ASSERT_PTR(userdata); - const char *match, *machine; - - if (!strv_isempty(arg_setenv) || arg_uid) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); +typedef struct PoolStatusInfo { + const char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; - if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Login only supported on local machines."); +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + if (i->path) + printf("\t Path: %s\n", i->path); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + if (i->usage != UINT64_MAX) + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + if (i->limit != UINT64_MAX) + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); +} - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); +static int show_pool_info(sd_bus *bus) { + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; - machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + PoolStatusInfo info = { + .usage = UINT64_MAX, + .limit = UINT64_MAX, + }; - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; - r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to request machine removal match: %m"); + assert(bus); - r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine); + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + 0, + &error, + &m, + &info); if (r < 0) - return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - r = sd_bus_message_read(reply, "hs", &master, NULL); - if (r < 0) - return bus_log_parse_error(r); + print_pool_status_info(bus, &info); - return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); + return 0; } -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; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = ASSERT_PTR(userdata); - const char *match, *machine, *path; - _cleanup_free_ char *uid = NULL; - - if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Shell only supported on local machines."); - - if (terminal_is_dumb()) { - /* Set TERM=dumb if we are running on a dumb terminal or with a pipe. - * Otherwise, we will get unwanted OSC sequences. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) - if (strv_extend(&arg_setenv, "TERM=dumb") < 0) - return log_oom(); - } else - /* Pass $TERM & Co. to shell session, if not explicitly specified. */ - FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { - if (strv_find_prefix(arg_setenv, v)) - continue; +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; - const char *t = strv_find_prefix(environ, v); - if (!t) - continue; + assert(bus); + assert(path); + assert(new_line); - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } + if (*new_line) + printf("\n"); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + *new_line = true; - r = sd_event_default(&event); + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL); if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + log_error_errno(r, "Could not get properties: %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"); + return r; +} - r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid); - if (r < 0) - return log_error_errno(r, "Failed to parse machine specification: %m"); +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); + int r = 0; - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); + properties = !strstr(argv[0], "status"); - r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to request machine removal match: %m"); + pager_open(arg_pager_flags); - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell"); - if (r < 0) - return bus_log_create_error(r); + if (argc <= 1) { - path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + /* If no argument is specified, inspect the manager + * itself */ - r = sd_bus_message_append(m, "sss", machine, uid, path); - if (r < 0) - return bus_log_create_error(r); + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } - r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); - if (r < 0) - return bus_log_create_error(r); + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path = NULL; - r = sd_bus_message_append_strv(m, arg_setenv); - if (r < 0) - return bus_log_create_error(r); + r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]); + if (r < 0) + return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r)); - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r)); + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); - r = sd_bus_message_read(reply, "hs", &master, NULL); - if (r < 0) - return bus_log_parse_error(r); + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } - return process_forward(event, slot, master, /* flags= */ 0, machine); + return r; } static int normalize_nspawn_filename(const char *name, char **ret_file) { @@ -1873,301 +1943,113 @@ static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *user if (q < 0) return r < 0 ? r : q; - q = get_settings_path(file, &path); - if (q == -ENOENT) { - log_error_errno(q, "No settings file found for machine '%s'.", *name); - r = r < 0 ? r : q; - continue; - } - if (q < 0) { - log_error_errno(q, "Failed to get the path of the settings file: %m"); - return r < 0 ? r : q; - } - - q = cat_files(path, /* dropins= */ NULL, /* flags= */ CAT_FORMAT_HAS_SECTIONS); - if (q < 0) - return r < 0 ? r : q; - } - - return r; -} - -static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { - 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++) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", argv[i]); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); - } - - return 0; -} - -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; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method( - bus, - bus_machine_mgr, - "RenameImage", - &error, - NULL, - "ss", argv[1], argv[2]); - if (r < 0) - return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r)); - - return 0; -} - -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); - int r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); - - return 0; -} - -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; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse boolean argument: %s", - argv[2]); - } - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); - if (r < 0) - return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - - return 0; -} - -static int image_exists(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(name); - - r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE)) - return 0; - - return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r)); - } - - return 1; -} - -static int make_service_name(const char *name, char **ret) { - int r; - - assert(name); - assert(ret); - assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX); - - if (!hostname_is_valid(name, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name %s.", name); - - r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret); - if (r < 0) - return log_error_errno(r, "Failed to build unit name: %m"); - - return 0; -} - -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); - int r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - ask_password_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - for (int i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *object; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = image_exists(bus, argv[i]); - if (r < 0) - return r; - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "Machine image '%s' does not exist.", - argv[i]); - - r = bus_call_method( - bus, - bus_systemd_mgr, - "StartUnit", - &error, - &reply, - "ss", unit, "fail"); - if (r < 0) - return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_add(w, object); - if (r < 0) - return log_oom(); - } + q = get_settings_path(file, &path); + if (q == -ENOENT) { + log_error_errno(q, "No settings file found for machine '%s'.", *name); + r = r < 0 ? r : q; + continue; + } + if (q < 0) { + log_error_errno(q, "Failed to get the path of the settings file: %m"); + return r < 0 ? r : q; + } - r = bus_wait_for_jobs(w, arg_quiet, NULL); - if (r < 0) - return r; + q = cat_files(path, /* dropins= */ NULL, /* flags= */ CAT_FORMAT_HAS_SECTIONS); + if (q < 0) + return r < 0 ? r : q; + } - return 0; + return r; } -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; +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; - const char *method; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; - bool enable; (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - enable = streq(argv[0], "enable"); - method = enable ? "EnableUnitFiles" : "DisableUnitFiles"; - - r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method); + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage"); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_open_container(m, 'a', "s"); + r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); if (r < 0) return bus_log_create_error(r); - if (enable) { - r = sd_bus_message_append(m, "s", "machines.target"); - if (r < 0) - return bus_log_create_error(r); - } - - for (int i = 1; i < argc; i++) { - _cleanup_free_ char *unit = NULL; + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; + return 0; +} - r = image_exists(bus, argv[i]); - if (r < 0) - return r; - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "Machine image '%s' does not exist.", - argv[i]); +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; - r = sd_bus_message_append(m, "s", unit); - if (r < 0) - return bus_log_create_error(r); - } + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_message_close_container(m); + r = bus_call_method( + bus, + bus_machine_mgr, + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r)); - if (enable) - r = sd_bus_message_append(m, "bb", false, false); - else - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return bus_log_create_error(r); + return 0; +} - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r)); +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; - if (enable) { - r = sd_bus_message_read(reply, "b", NULL); - if (r < 0) - return bus_log_parse_error(r); + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse boolean argument: %s", + argv[2]); } - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet); - if (r < 0) - return r; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = bus_service_manager_reload(bus); + r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); if (r < 0) - return r; + return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - if (arg_now) { - _cleanup_strv_free_ char **new_args = NULL; + return 0; +} - new_args = strv_new(enable ? "start" : "poweroff"); - if (!new_args) - return log_oom(); +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + int r; - r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage"); if (r < 0) - return log_oom(); + return bus_log_create_error(r); - if (enable) - return verb_start_machine(strv_length(new_args), new_args, data, userdata); + r = sd_bus_message_append(m, "s", argv[i]); + if (r < 0) + return bus_log_create_error(r); - return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata); + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); } return 0; @@ -2257,6 +2139,120 @@ static int verb_clean_images(int argc, char *argv[], uintptr_t _data, void *user return 0; } +static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bind-volume is only supported on the local transport."); + + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + r = bind_volume_parse(argv[2], &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + /* Locate and connect to the target machine before acquiring storage, so a missing + * machine doesn't trigger 'create=new' side effects on the StorageProvider. */ + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m"); + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0) { + if (acquire_error_id) + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s", + bv->provider, bv->volume, acquire_error_id); + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m", + bv->provider, bv->volume); + } + + int fd_index = sd_varlink_push_fd(vl, reply.fd); + if (fd_index < 0) + return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m"); + TAKE_FD(reply.fd); + + _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume); + if (!name) + return log_oom(); + + sd_json_variant *vl_reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.AddStorage", + &vl_reply, &error_id, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index), + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config)); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply), + "AddStorage failed for '%s': %s", name, error_id); + + return 0; +} + +static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "unbind-volume is only supported on the local transport."); + + r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid unbind-volume name '%s', expected ':'.", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.RemoveStorage", + &reply, &error_id, + SD_JSON_BUILD_PAIR_STRING("name", argv[2])); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "RemoveStorage failed for '%s': %s", argv[2], error_id); + + return 0; +} + static int chainload_importctl(int argc, char *argv[]) { int r; From 6b8c3af359265ac958b9e42237d6068dcc8b0264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 18:34:03 +0200 Subject: [PATCH 1761/2155] machinectl: reorder cases in parse_argv() to match order in --help --no-pager, --no-legend, --no-ask-password, --quiet are shifted to the end. Co-developed-by: Claude Opus 4.7 (1M context) --- src/machine/machinectl.c | 136 +++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 1e704e4d96214..657c69e2a17d6 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2520,6 +2520,24 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case 'p': case 'P': r = strv_extend(&arg_property, optarg); @@ -2546,33 +2564,6 @@ static int parse_argv(int argc, char *argv[]) { arg_full = true; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); - break; - - case 'o': - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - - r = output_mode_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Unknown output '%s'.", optarg); - arg_output = r; - - if (OUTPUT_MODE_IS_JSON(arg_output)) - arg_legend = false; - break; - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - case ARG_KILL_WHOM: arg_kill_whom = optarg; break; @@ -2583,18 +2574,14 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + case ARG_UID: + arg_uid = optarg; break; - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; + case 'E': + r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); break; case ARG_READ_ONLY: @@ -2605,22 +2592,39 @@ static int parse_argv(int argc, char *argv[]) { arg_mkdir = true; break; - case 'q': - arg_quiet = true; + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", optarg); break; - case ARG_VERIFY: + case ARG_MAX_ADDRESSES: + if (streq(optarg, "all")) + arg_max_addresses = UINT_MAX; + else if (safe_atou(optarg, &arg_max_addresses) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid number of addresses: %s", optarg); + break; + + case 'o': if (streq(optarg, "help")) - return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - r = import_verify_from_string(optarg); + r = output_mode_from_string(optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); - arg_verify = r; + return log_error_errno(r, "Unknown output '%s'.", optarg); + arg_output = r; + + if (OUTPUT_MODE_IS_JSON(arg_output)) + arg_legend = false; break; - case 'V': - arg_runner = RUNNER_VMSPAWN; + case ARG_FORCE: + arg_force = true; + break; + + case ARG_NOW: + arg_now = true; break; case ARG_RUNNER: @@ -2631,12 +2635,18 @@ static int parse_argv(int argc, char *argv[]) { arg_runner = r; break; - case ARG_NOW: - arg_now = true; + case 'V': + arg_runner = RUNNER_VMSPAWN; break; - case ARG_FORCE: - arg_force = true; + 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; break; case ARG_FORMAT: @@ -2647,30 +2657,20 @@ static int parse_argv(int argc, char *argv[]) { arg_format = optarg; break; - case ARG_UID: - arg_uid = optarg; - break; - - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); - if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_MAX_ADDRESSES: - if (streq(optarg, "all")) - arg_max_addresses = UINT_MAX; - else if (safe_atou(optarg, &arg_max_addresses) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid number of addresses: %s", optarg); + case ARG_NO_LEGEND: + arg_legend = false; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + case 'q': + arg_quiet = true; break; case '?': From 0bd979025ae2c5c7326f26e7f9fd960c61017a27 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 18:51:30 +0000 Subject: [PATCH 1762/2155] meson: drop vestigial libgpg-error dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libgpg-error was added in 2017 (commit 76c8741060, Michael Biebl) to gate HAVE_GCRYPT on its presence because src/resolve referenced libgpg-error directly at the time. That usage is long gone — no source file references any gpg-error API today — so the dependency only served to fail HAVE_GCRYPT detection when gpg-error-dev wasn't installed. libgcrypt's pkg-config Requires already pulls in the gpg-error headers (via the transitive #include in ), so dropping the dep doesn't break compilation. --- meson.build | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/meson.build b/meson.build index fc1885ce83715..ebef4e0de80e2 100644 --- a/meson.build +++ b/meson.build @@ -1184,22 +1184,14 @@ libqrencode = dependency('libqrencode', libqrencode_cflags = libqrencode.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_QRENCODE', libqrencode.found()) -feature = get_option('gcrypt') libgcrypt = dependency('libgcrypt', - required : feature) -libgpg_error = dependency('gpg-error', - required : feature) - -have = libgcrypt.found() and libgpg_error.found() -if not have - # link to neither of the libs if one is not found - libgcrypt = [] - libgpg_error = [] - libgcrypt_cflags = [] -else + required : get_option('gcrypt')) +conf.set10('HAVE_GCRYPT', libgcrypt.found()) +if libgcrypt.found() libgcrypt_cflags = libgcrypt.partial_dependency(includes: true, compile_args: true) +else + libgcrypt_cflags = [] endif -conf.set10('HAVE_GCRYPT', have) libgnutls = dependency('gnutls', version : '>= 3.1.4', From 047036bf5431b3a42d073b86ef21046cab58a5d0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 13:10:25 +0000 Subject: [PATCH 1763/2155] meson: Drop two unnecessary dependencies --- meson.build | 3 +-- src/journal/meson.build | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/meson.build b/meson.build index ebef4e0de80e2..c69b96e1c6d50 100644 --- a/meson.build +++ b/meson.build @@ -1836,8 +1836,7 @@ if static_libudev != 'false' install_tag: 'libudev', install_dir : libdir, link_depends : libudev_sym, - dependencies : [libmount, - libshared_deps, + dependencies : [libshared_deps, userspace], c_args : static_libudev_pic ? [] : ['-fno-PIC'], pic : static_libudev_pic) diff --git a/src/journal/meson.build b/src/journal/meson.build index 142d2246c1fe0..1f40e9b43b1a9 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -80,7 +80,6 @@ executables += [ 'public' : true, 'conditions' : ['HAVE_QRENCODE'], 'sources' : files('bsod.c'), - 'dependencies' : libqrencode, }, executable_template + { 'name' : 'systemd-cat', From 446b0393ff06bf5065af48601eb28f6d56616c19 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 15 May 2026 18:19:41 +0100 Subject: [PATCH 1764/2155] test-network: retry networkctl status in wait_operstate() networkctl status may transiently fail right after start_networkd() because networkd has not yet picked up the freshly-created link from the kernel. The retry loop in wait_operstate() did not catch the resulting subprocess.CalledProcessError, so the test aborted on the first attempt instead of retrying for the configured timeout. Observed in TEST-85-NETWORK-NetworkdBridgeTests, subtest test_bridge_configure_without_carrier[no-slave]: [ 19.600156] systemd-networkd-tests.py[526]: Failed to issue io.systemd.Network.Link.Describe() varlink call: Invalid argument [ 53.124982] systemd[1]: systemd-networkd.service: Changed start -> running [ 53.336167] systemd-networkd-tests.py[526]: ERROR: test_bridge_configure_without_carrier (__main__.NetworkdBridgeTests.test_bridge_configure_without_carrier) (test='no-slave') [ 53.336167] systemd-networkd-tests.py[526]: self.wait_operstate('bridge99', operstate=r'(no-carrier|routable)', setup_state=None, setup_timeout=30) [ 53.336167] systemd-networkd-tests.py[526]: subprocess.CalledProcessError: Command '['/usr/bin/networkctl', '-n', '0', 'status', 'bridge99']' returned non-zero exit status 1. Co-developed-by: Claude Opus 4.7 --- test/test-network/systemd-networkd-tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 6917c2f697b72..a6d4ff033c26b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1277,7 +1277,13 @@ def wait_operstate(self, link, operstate='degraded', setup_state='configured', s if not link_exists(link): time.sleep(0.5) continue - output = networkctl_status(link) + try: + output = networkctl_status(link) + except subprocess.CalledProcessError: + # networkctl status may transiently fail e.g. when networkd has not + # yet picked up the link from the kernel. Retry until the timeout. + time.sleep(0.5) + continue if re.search(rf'(?m)^\s*State:\s+{operstate}\s+\({setup_state}\)\s*$', output): return True time.sleep(0.5) From f39c071f31bbdcba0baaebb967d86311534cb953 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 19 Mar 2026 01:01:50 +0900 Subject: [PATCH 1765/2155] dhcp-client-send: introduce dhcp_client_send_message() This internally uses sd_dhcp_message object, and replaces functions for creating and sending DHCP messages. By using sd_dhcp_message internally, now we can correctly send long (> 255 bytes) option data that cannot be fit in a single DHCP option TLV. This also fixes the value in DHCP option 57 (Maximum Message Size). Previously the IP and UDP header size is subtracted from the interface MTU, but it should not. Except for the above, this should not change any effective behaviors. --- src/libsystemd-network/dhcp-client-send.c | 391 +++++++++++++- src/libsystemd-network/dhcp-client-send.h | 14 +- src/libsystemd-network/sd-dhcp-client.c | 595 +--------------------- src/libsystemd-network/test-dhcp-client.c | 6 +- 4 files changed, 387 insertions(+), 619 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index d1f20fef46e69..e8b91a3e7c0bd 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -1,15 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "dhcp-client-internal.h" #include "dhcp-client-send.h" #include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-message.h" #include "dhcp-network.h" -#include "dhcp-packet.h" #include "fd-util.h" -#include "iovec-util.h" #include "iovec-wrapper.h" +#include "ip-util.h" #include "socket-util.h" static int client_get_socket(sd_dhcp_client *client, int domain) { @@ -70,17 +72,16 @@ static int client_setup_io_event( return 0; } -int dhcp_client_send_raw( +static int client_send_raw( sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message, + bool expect_reply) { _cleanup_close_ int fd_close = -EBADF; int r, fd; assert(client); - assert(packet); + assert(message); fd = client_get_socket(client, AF_PACKET); if (fd < 0) { @@ -100,24 +101,39 @@ int dhcp_client_send_raw( fd_close = fd; } - r = dhcp_packet_append_ip_headers( - packet, + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + struct iphdr ip; + struct udphdr udp; + r = udp_packet_build( INADDR_ANY, client->port, INADDR_BROADCAST, client->server_port, - sizeof(DHCPPacket) + optoffset, - client->ip_service_type); + client->ip_service_type, + &payload, + &ip, + &udp); if (r < 0) return r; - r = dhcp_network_send_raw_socket( - fd, - &client->link, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(packet, sizeof(DHCPPacket) + optoffset), - .count = 1, - }); + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); + if (r < 0) + return r; + + r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); + if (r < 0) + return r; + + r = iovw_put_iovw(&iovw, &payload); + if (r < 0) + return r; + + r = dhcp_network_send_raw_socket(fd, &client->link, &iovw); if (r < 0) return r; @@ -138,17 +154,16 @@ int dhcp_client_send_raw( return 0; } -int dhcp_client_send_udp( +static int client_send_udp( sd_dhcp_client *client, - bool expect_reply, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message, + bool expect_reply) { _cleanup_close_ int fd_close = -EBADF; int r, fd; assert(client); - assert(packet); + assert(message); if (!client->lease || client->lease->address == 0) return -EADDRNOTAVAIL; @@ -166,14 +181,16 @@ int dhcp_client_send_udp( fd_close = fd; } + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + r = dhcp_network_send_udp_socket( fd, client->lease->server_address, client->server_port, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(&packet->dhcp, sizeof(DHCPMessage) + optoffset), - .count = 1, - }); + &payload); if (r < 0) return r; @@ -193,3 +210,323 @@ int dhcp_client_send_udp( TAKE_FD(fd_close); return 0; } + +static int client_new_message(sd_dhcp_client *client, uint8_t type, sd_dhcp_message **ret) { + int r; + + assert(client); + assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREQUEST, + client->xid, + client->arp_type, + &client->hw_addr); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers refuse to issue a DHCP lease + * if 'secs' is set to zero. */ + usec_t time_now; + r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* Seconds between sending first and last DISCOVER must always be strictly positive to deal with + * broken servers. */ + message->header.secs = usec_to_be16_sec(usec_sub_unsigned(time_now, client->start_time) ?: 1 * USEC_PER_SEC); + + /* RFC2131 section 4.1 + * A client that cannot receive unicast IP datagrams until its protocol software has been configured + * with an IP address SHOULD set the BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + * DHCPREQUEST messages that client sends. The BROADCAST bit will provide a hint to the DHCP server + * and BOOTP relay agent to broadcast any messages to the client on the client's subnet. + * + * Note: some interfaces needs this to be enabled, but some networks need this to be disabled as + * broadcasts are filtered, so this needs to be configurable. */ + dhcp_message_set_broadcast_flag(message, client->request_broadcast || client->arp_type != ARPHRD_ETHER); + + /* We append no vendor options on BOOTP mode. */ + if (client->bootp) { + *ret = TAKE_PTR(message); + return 0; + } + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; + + /* Server Identifier (54): mandatory in DHCPREQUEST on REQUESTING state. It is also mandatory when + * DHCPRELEASE and DHCPDECLINE. */ + if ((type == DHCP_REQUEST && client->state == DHCP_STATE_REQUESTING) || + IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + ASSERT_PTR(client->lease)->server_address); + if (r < 0) + return r; + } + + /* Client Identifier (61): Not mandatory, but some DHCP servers will reject messages without client + * identifier option. Hence, we always set it. */ + r = dhcp_message_append_option_client_id(message, &client->client_id); + if (r < 0) + return r; + + /* Requested IP Address option (50) or ciaddr + * + * See RFC2131 section 4.3.2 (note that there is a typo in the RFC, SELECTING should be REQUESTING). */ + be32_t addr = INADDR_ANY; + switch (type) { + case DHCP_DISCOVER: + /* the client may suggest values for the network address and lease time in the DHCPDISCOVER + * message. The client may include the ’requested IP address’ option to suggest that a + * particular IP address be assigned, and may include the ’IP address lease time’ option to + * suggest the lease time it would like. + * + * RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + if (!client->anonymize) + addr = client->last_addr; + break; + + case DHCP_REQUEST: + switch (client->state) { + + case DHCP_STATE_REQUESTING: + /* ’ciaddr’ MUST be zero, ’requested IP address’ MUST be filled in with the + * yiaddr value from the chosen DHCPOFFER. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_STATE_REBOOTING: + /* ’requested IP address’ option MUST be filled in with client’s notion of its + * previously assigned address. ’ciaddr’ MUST be zero. */ + addr = client->last_addr; + break; + + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + /* ’requested IP address’ option MUST NOT be filled in, ’ciaddr’ MUST be filled + * in with client’s IP address. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + break; + + case DHCP_RELEASE: + /* The acquired address must be set in ciaddr. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_DECLINE: + /* The acquired address must be set in Requested IP Address option. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + + if (addr != INADDR_ANY) { + r = dhcp_message_append_option_be32(message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr); + if (r < 0) + return r; + } + + /* DHCPRELEASE and DHCPDECLINE MUST NOT contain any other options. */ + if (IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + *ret = TAKE_PTR(message); + return 0; + } + + /* Parameter Request List (55) + * + * RFC2131 section 3.5: + * in its initial DHCPDISCOVER or DHCPREQUEST message, a client may provide the server with a list of + * specific parameters the client is interested in. If the client includes a list of parameters in a + * DHCPDISCOVER message, it MUST include that list in any subsequent DHCPREQUEST messages. + * + * RFC7844 section 3: + * MAY contain the Parameter Request List option. + * + * RFC7844 section 3.6: + * The client intending to protect its privacy SHOULD only request a minimal number of options in the + * PRL and SHOULD also randomly shuffle the ordering of option codes in the PRL. If this random + * ordering cannot be implemented, the client MAY order the option codes in the PRL by option code + * number (lowest to highest). + * + * NOTE: using PRL options that Windows 10 RFC7844 implementation uses. */ + if (client->anonymize) { + static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ + }; + + r = dhcp_message_append_option( + message, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + ELEMENTSOF(default_req_opts_anonymize), + default_req_opts_anonymize); + if (r < 0) + return r; + + /* RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + *ret = TAKE_PTR(message); + return 0; + } + + /* When not on anonymized mode, use the default + user requested options. */ + r = dhcp_message_append_option_parameter_request_list(message, client->req_opts); + if (r < 0) + return r; + + /* Maximum Message Size (57) + * + * RFC2131 section 3.5: + * The client SHOULD include the ’maximum DHCP message size’ option to let the server know how + * large the server may make its DHCP messages. + * + * Note (from ConnMan): Some DHCP servers will send bigger DHCP packets than the defined default size + * unless the Maximum Message Size option is explicitly set. + * + * RFC3442 "Requirements to Avoid Sizing Constraints": + * Because a full routing table can be quite large, the standard 576 octet maximum size for a DHCP + * message may be too short to contain some legitimate Classless Static Route options. Because of + * this, clients implementing the Classless Static Route option SHOULD send a Maximum DHCP Message + * Size [4] option if the DHCP client's TCP/IP stack is capable of receiving larger IP datagrams. + * In this case, the client SHOULD set the value of this option to at least the MTU of the interface + * that the client is configuring. The client MAY set the value of this option higher, up to the size + * of the largest UDP packet it is prepared to accept. (Note that the value specified in the Maximum + * DHCP Message Size option is the total maximum packet size, including IP and UDP headers.) */ + r = dhcp_message_append_option_u16( + message, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + CLAMP(client->mtu, (uint32_t) IPV4_MIN_REASSEMBLY_SIZE, (uint32_t) UINT16_MAX)); + if (r < 0) + return r; + + /* Hostname (12) or FQDN (81) + * + * Note, it is unclear from RFC 2131 if client should send hostname in DHCPDISCOVER but dhclient does + * and so we do as well. */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_S, /* Request server to perform A RR DNS updates */ + /* is_client= */ true, + client->hostname); + if (r < 0) + return r; + + /* Vendor Specific (43) */ + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + client->vendor_options); + if (r < 0) + return r; + + /* Vendor Class Identifier (60) */ + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + client->vendor_class_identifier); + if (r < 0) + return r; + + /* User Class (77) */ + r = dhcp_message_append_option_length_prefixed_data( + message, + SD_DHCP_OPTION_USER_CLASS, + /* length_size= */ 1, + &client->user_class); + if (r < 0) + return r; + + /* Rapid Commit (80): only for DHCPDISCOVER */ + if (client->rapid_commit && type == DHCP_DISCOVER) { + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return r; + } + + /* MUD URL (161) */ + r = dhcp_message_append_option_string(message, SD_DHCP_OPTION_MUD_URL, client->mudurl); + if (r < 0) + return r; + + r = dhcp_message_append_option_tlv(message, client->extra_options); + if (r < 0) + return r; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type) { + int r; + + assert(client); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = client_new_message(client, type, &message); + if (r < 0) + return r; + + switch (type) { + case DHCP_DISCOVER: + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_REQUEST: + if (client->state == DHCP_STATE_RENEWING) + r = client_send_udp(client, message, /* expect_reply= */ true); + else + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_RELEASE: + r = client_send_udp(client, message, /* expect_reply= */ false); + break; + case DHCP_DECLINE: + r = client_send_raw(client, message, /* expect_reply= */ false); + break; + default: + r = -EINVAL; + } + if (r < 0) + return r; + + if (client->bootp) + log_dhcp_client(client, "BOOTREQUEST"); + else if (type == DHCP_REQUEST) + log_dhcp_client(client, "%s (%s)", + dhcp_message_type_to_string(type), + dhcp_state_to_string(client->state)); + else + log_dhcp_client(client, "%s", dhcp_message_type_to_string(type)); + return 0; +} diff --git a/src/libsystemd-network/dhcp-client-send.h b/src/libsystemd-network/dhcp-client-send.h index 2dbb6a0878dcc..d5d65a31cce50 100644 --- a/src/libsystemd-network/dhcp-client-send.h +++ b/src/libsystemd-network/dhcp-client-send.h @@ -3,16 +3,4 @@ #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); +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 463f95ea29bf1..e2513567a2f58 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -24,7 +24,6 @@ #include "network-common.h" #include "random-util.h" #include "set.h" -#include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" @@ -715,495 +714,6 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir return MAX(usec_sub_unsigned(expire, now_usec) / 2, 60 * USEC_PER_SEC); } -static int cmp_uint8(const uint8_t *a, const uint8_t *b) { - assert(a); - assert(b); - - return CMP(*a, *b); -} - -static int client_message_init( - sd_dhcp_client *client, - uint8_t type, - DHCPPacket **ret_packet, - size_t *ret_optlen, - size_t *ret_optoffset) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optlen, optoffset, size; - usec_t time_now; - uint16_t secs; - int r; - - assert(client); - assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); - assert(ret_packet); - assert(ret_optlen); - assert(ret_optoffset); - - optlen = DHCP_MIN_OPTIONS_SIZE; - size = sizeof(DHCPPacket) + optlen; - - packet = malloc0(size); - if (!packet) - return -ENOMEM; - if (client->bootp) { - /* BOOTP supports options, but only DHCP_OPTION_END is used. The rest of the 64-byte buffer - * is set to zero, per RFC1542. Allow for this by initialaizing optoffset to 0. */ - optoffset = 0; - r = bootp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes); - } else - r = dhcp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes, - type, optlen, &optoffset); - if (r < 0) - return r; - - /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers - refuse to issue an DHCP lease if 'secs' is set to zero */ - r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); - if (r < 0) - return r; - assert(time_now >= client->start_time); - - /* seconds between sending first and last DISCOVER - * must always be strictly positive to deal with broken servers */ - secs = ((time_now - client->start_time) / USEC_PER_SEC) ?: 1; - packet->dhcp.secs = htobe16(secs); - - /* RFC2131 section 4.1 - A client that cannot receive unicast IP datagrams until its protocol - software has been configured with an IP address SHOULD set the - BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or - DHCPREQUEST messages that client sends. The BROADCAST bit will - provide a hint to the DHCP server and BOOTP relay agent to broadcast - any messages to the client on the client's subnet. - - Note: some interfaces needs this to be enabled, but some networks - needs this to be disabled as broadcasts are filteretd, so this - needs to be configurable */ - if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) - packet->dhcp.flags = htobe16(0x8000); - - if (client->bootp) { - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); - return 0; - } - - /* Some DHCP servers will refuse to issue an DHCP lease if the Client - Identifier option is not set */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_CLIENT_IDENTIFIER, - client->client_id.size, - client->client_id.raw); - if (r < 0) - return r; - - /* RFC2131 section 3.5: - in its initial DHCPDISCOVER or DHCPREQUEST message, a - client may provide the server with a list of specific - parameters the client is interested in. If the client - includes a list of parameters in a DHCPDISCOVER message, - it MUST include that list in any subsequent DHCPREQUEST - messages. - */ - - /* RFC7844 section 3: - 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) && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { - _cleanup_free_ uint8_t *opts = NULL; - size_t n_opts, i = 0; - void *val; - - n_opts = set_size(client->req_opts); - opts = new(uint8_t, n_opts); - if (!opts) - return -ENOMEM; - - SET_FOREACH(val, client->req_opts) - opts[i++] = PTR_TO_UINT8(val); - assert(i == n_opts); - - /* For anonymizing the request, let's sort the options. */ - typesafe_qsort(opts, n_opts, cmp_uint8); - - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, - n_opts, opts); - if (r < 0) - return r; - } - - /* RFC2131 section 3.5: - The client SHOULD include the ’maximum DHCP message size’ option to - let the server know how large the server may make its DHCP messages. - - Note (from ConnMan): Some DHCP servers will send bigger DHCP packets - than the defined default size unless the Maximum Message Size option - is explicitly set - - RFC3442 "Requirements to Avoid Sizing Constraints": - Because a full routing table can be quite large, the standard 576 - octet maximum size for a DHCP message may be too short to contain - some legitimate Classless Static Route options. Because of this, - clients implementing the Classless Static Route option SHOULD send a - Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP - stack is capable of receiving larger IP datagrams. In this case, the - client SHOULD set the value of this option to at least the MTU of the - interface that the client is configuring. The client MAY set the - value of this option higher, up to the size of the largest UDP packet - it is prepared to accept. (Note that the value specified in the - Maximum DHCP Message Size option is the total maximum packet size, - including IP and UDP headers.) - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { - be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX)); - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, - 2, &max_size); - if (r < 0) - return r; - } - - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); - - return 0; -} - -static int client_append_fqdn_option( - DHCPMessage *message, - size_t optlen, - size_t *optoffset, - const char *fqdn) { - - uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; - int r; - - buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ - DHCP_FQDN_FLAG_E; /* Canonical wire format */ - buffer[1] = 0; /* RCODE1 (deprecated) */ - buffer[2] = 0; /* RCODE2 (deprecated) */ - - r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); - if (r > 0) - r = dhcp_option_append(message, optlen, optoffset, 0, - SD_DHCP_OPTION_FQDN, 3 + r, buffer); - - return r; -} - -static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { - int r; - - assert(client); - - if (client->hostname) { - /* According to RFC 4702 "clients that send the Client FQDN option in - their messages MUST NOT also send the Host Name option". Just send - one of the two depending on the hostname type. - */ - if (dns_name_is_single_label(client->hostname)) { - /* it is unclear from RFC 2131 if client should send hostname in - DHCPDISCOVER but dhclient does and so we do as well - */ - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(client->hostname), client->hostname); - } else - r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset, - client->hostname); - if (r < 0) - return r; - } - - if (client->vendor_class_identifier) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, - strlen(client->vendor_class_identifier), - client->vendor_class_identifier); - if (r < 0) - return r; - } - - if (client->mudurl) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_MUD_URL, - strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - - if (!iovw_isempty(&client->user_class)) { - size_t sz = iovw_size(&client->user_class) + client->user_class.count; - if (sz <= UINT8_MAX) { - _cleanup_free_ uint8_t *buf = new(uint8_t, sz); - if (!buf) - return -ENOMEM; - - uint8_t *p = buf; - FOREACH_ARRAY(iovec, client->user_class.iovec, client->user_class.count) { - assert(iovec->iov_len > 0 && iovec->iov_len <= UINT8_MAX); - *p++ = iovec->iov_len; - p = mempcpy(p, iovec->iov_base, iovec->iov_len); - } - - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_USER_CLASS, - sz, buf); - if (r < 0) - return r; - } - } - - if (client->extra_options) { - void *key; - struct iovec_wrapper *iovw; - HASHMAP_FOREACH_KEY(iovw, key, client->extra_options->entries) { - uint32_t tag = PTR_TO_UINT32(key); - - FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - tag, iov->iov_len, iov->iov_base); - if (r < 0) - return r; - } - } - } - - if (!tlv_isempty(client->vendor_options)) { - _cleanup_(iovec_done) struct iovec iov = {}; - r = tlv_build(client->vendor_options, &iov); - if (r < 0) - return r; - - r = dhcp_option_append( - &packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - iov.iov_len, iov.iov_base); - if (r < 0) - return r; - } - - return 0; -} - -static int client_send_dhcp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - - assert(client); - - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - if (r < 0) - return r; - - /* the client may suggest values for the network address - and lease time in the DHCPDISCOVER message. The client may include - the ’requested IP address’ option to suggest that a particular IP - address be assigned, and may include the ’IP address lease time’ - option to suggest the lease time it would like. - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && client->last_addr != INADDR_ANY) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - if (r < 0) - return r; - } - - if (client->rapid_commit) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL); - if (r < 0) - return r; - } - - r = client_append_common_discover_request_options(client, discover, &optoffset, optlen); - if (r < 0) - return r; - - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); - if (r < 0) - return r; - - log_dhcp_client(client, "DISCOVER"); - return 0; -} - -static int client_send_bootp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - - assert(client); - - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - if (r < 0) - return r; - - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - /* RFC1542 section 3.5: - * if the client has no information to communicate to the server, the octet immediately following the - * magic cookie SHOULD be set to the "End" tag (255) and the remaining octets of the 'vend' field - * SHOULD be set to zero. - * - * Use this RFC, along with the fact that some BOOTP servers require a 64-byte vend field, to suggest - * that we always zero and send 64 bytes in the options field. The first four bites are the "magic" - * field, so this only needs to add 60 bytes. */ - if (optoffset < 60 && optlen >= 60) { - memzero(&discover->dhcp.options[optoffset], optlen - optoffset); - optoffset = 60; - } - - r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); - if (r < 0) - return r; - - log_dhcp_client(client, "DISCOVER"); - return 0; -} - -static int client_send_discover(sd_dhcp_client *client) { - assert(client); - assert(client->state == 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; - int r; - - assert(client); - assert(!client->bootp); - - r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset); - if (r < 0) - return r; - - switch (client->state) { - /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC, - SELECTING should be REQUESTING) - */ - - case DHCP_STATE_REQUESTING: - /* Client inserts the address of the selected server in ’server - identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be - filled in with the yiaddr value from the chosen DHCPOFFER. - */ - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &client->lease->server_address); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->lease->address); - if (r < 0) - return r; - break; - - 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. - */ - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - if (r < 0) - return r; - break; - - case DHCP_STATE_RENEWING: - /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ - option MUST NOT be filled in, ’ciaddr’ MUST be filled in with - client’s IP address. - */ - - case DHCP_STATE_REBINDING: - /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ - option MUST NOT be filled in, ’ciaddr’ MUST be filled in with - client’s IP address. - - This message MUST be broadcast to the 0xffffffff IP broadcast address. - */ - request->dhcp.ciaddr = client->lease->address; - break; - - default: - assert_not_reached(); - } - - r = client_append_common_discover_request_options(client, request, &optoffset, optlen); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - if (client->state == DHCP_STATE_RENEWING) - r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset); - else - r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset); - if (r < 0) - return r; - - switch (client->state) { - - case DHCP_STATE_REQUESTING: - log_dhcp_client(client, "REQUEST (requesting)"); - break; - - case DHCP_STATE_REBOOTING: - log_dhcp_client(client, "REQUEST (rebooting)"); - break; - - case DHCP_STATE_RENEWING: - log_dhcp_client(client, "REQUEST (renewing)"); - break; - - case DHCP_STATE_REBINDING: - log_dhcp_client(client, "REQUEST (rebinding)"); - break; - - default: - assert_not_reached(); - } - - return 0; -} - static int client_timeout_resend( sd_event_source *s, uint64_t usec, @@ -1282,7 +792,7 @@ static int client_timeout_resend( switch (client->state) { case DHCP_STATE_SELECTING: - r = client_send_discover(client); + r = dhcp_client_send_message(client, DHCP_DISCOVER); if (r < 0 && client->discover_attempt >= client->max_discover_attempts) goto error; @@ -1291,7 +801,7 @@ static int client_timeout_resend( break; case DHCP_STATE_REBOOTING: - r = client_send_request(client); + r = dhcp_client_send_message(client, DHCP_REQUEST); if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) goto restart; break; @@ -1299,7 +809,7 @@ static int client_timeout_resend( case DHCP_STATE_REQUESTING: case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: - r = client_send_request(client); + r = dhcp_client_send_message(client, DHCP_REQUEST); if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) goto restart; break; @@ -2109,99 +1619,31 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { return r; } -static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) { +int sd_dhcp_client_send_decline(sd_dhcp_client *client) { int r; - assert(IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)); - if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) - return 0; /* there is nothing to release or decline */ - - const char *name = type == DHCP_RELEASE ? "RELEASE" : "DECLINE"; - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset, optlen; - r = client_message_init(client, type, &packet, &optlen, &optoffset); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to initialize DHCP %s message: %m", name); - - /* 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; - - 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 log_dhcp_client_errno( - client, r, - "Failed to append Server Identifier option to DHCP %s message: %m", - name); + return 0; /* there is nothing to decline */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, - SD_DHCP_OPTION_END, /* optlen= */ 0, /* optval= */ NULL); + r = dhcp_client_send_message(client, DHCP_DECLINE); if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to finalize DHCP %s message: %m", - name); - - switch (type) { - case DHCP_RELEASE: - r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset); - break; - case DHCP_DECLINE: - r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset); - break; - default: - assert_not_reached(); - } - if (r < 0) - return log_dhcp_client_errno( - client, r, - "Failed to send DHCP %s message: %m", - name); + return r; - log_dhcp_client(client, "%s", name); - return 1; /* sent */ + /* This function is mostly called when the acquired address conflicts with another host. + * Restarting the daemon to acquire another address. */ + return client_restart(client); } -int sd_dhcp_client_send_decline(sd_dhcp_client *client) { - int r; - - r = client_send_release_or_decline(client, DHCP_DECLINE); - if (r <= 0) - return r; +static int client_send_release(sd_dhcp_client *client) { + assert(client); - log_dhcp_client(client, "DECLINE"); + if (!client->send_release) + return 0; - /* This function is mostly called when the acquired address conflicts with another host. - * Restarting the daemon to acquire another address. */ - r = client_restart(client); - if (r < 0) - return r; + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to release */ - return 1; /* sent and restarted. */ + return dhcp_client_send_message(client, DHCP_RELEASE); } int sd_dhcp_client_stop(sd_dhcp_client *client) { @@ -2210,8 +1652,7 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) { DHCP_CLIENT_DONT_DESTROY(client); - if (client->send_release) - (void) client_send_release_or_decline(client, DHCP_RELEASE); + (void) client_send_release(client); client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); return 0; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index d7dd01cc813ae..c62f46222ab0e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -352,7 +352,8 @@ static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) { ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST); ASSERT_EQ(request->xid, xid); - ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); + uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); + ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); log_info(" recv DHCP Request 0x%08x", be32toh(xid)); @@ -374,7 +375,8 @@ static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER); - ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); + uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); + ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); xid = discover->xid; From 26b7c5ff3b944aa3a16d4e859e9c84ce7e968a5a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 01:45:16 +0900 Subject: [PATCH 1766/2155] sd-dhcp-lease: introduce dhcp_client_parse_message() It parses a received DHCP message and create corresponding sd_dhcp_lease object for the message. Internally, it uses sd_dhcp_message object. Note, the new parser is not used yet. The currently used one will be replaced in later commit. --- src/libsystemd-network/dhcp-lease-internal.h | 7 +- src/libsystemd-network/sd-dhcp-lease.c | 436 +++++++++++++++++++ 2 files changed, 442 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index bc411a4f36cd3..6a562901142bb 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -6,10 +6,11 @@ ***/ #include "sd-dhcp-lease.h" +#include "sd-forward.h" #include "dhcp-client-id-internal.h" +#include "dhcp-message.h" #include "dhcp-option.h" -#include "sd-forward.h" #include "list.h" struct sd_dhcp_raw_option { @@ -23,6 +24,8 @@ struct sd_dhcp_raw_option { struct sd_dhcp_lease { unsigned n_ref; + sd_dhcp_message *message; + /* each 0 if unset */ usec_t t1; usec_t t2; @@ -87,5 +90,7 @@ void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *time int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); +int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret); + #define dhcp_lease_unref_and_replace(a, b) \ free_and_replace_full(a, b, sd_dhcp_lease_unref) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 8003f37fd3122..9df1a4818dfec 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -9,6 +9,7 @@ #include "sd-dhcp-lease.h" #include "alloc-util.h" +#include "dhcp-client-internal.h" #include "dhcp-lease-internal.h" #include "dhcp-option.h" #include "dhcp-route.h" @@ -22,9 +23,11 @@ #include "hexdecoct.h" #include "hostname-util.h" #include "in-addr-util.h" +#include "ip-util.h" #include "network-common.h" #include "network-internal.h" #include "parse-util.h" +#include "set.h" #include "sort-util.h" #include "stdio-util.h" #include "string-util.h" @@ -419,6 +422,8 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { assert(lease); + sd_dhcp_message_unref(lease->message); + while ((option = LIST_POP(options, lease->private_options))) { free(option->data); free(option); @@ -1803,3 +1808,434 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { *ret = lease->timezone; return 0; } + +static int dhcp_lease_new_from_message(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(message); + assert(ret); + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + /* acquired address: mandatory */ + if (message->header.yiaddr == INADDR_ANY) + return -EBADMSG; + lease->address = message->header.yiaddr; + + /* subnet mask: mandatory */ + if (dhcp_message_get_option_be32(message, SD_DHCP_OPTION_SUBNET_MASK, &lease->subnet_mask) < 0) { + /* fall back to the default subnet masks based on address class */ + struct in_addr mask; + r = in4_addr_default_subnet_mask( + &(struct in_addr) { + .s_addr = message->header.yiaddr, + }, + &mask); + if (r < 0) + return r; + + lease->subnet_mask = mask.s_addr; + } + + /* DHCP server address: mandatory */ + r = dhcp_message_get_option_be32(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &lease->server_address); + if (r < 0) { + if (!client->bootp) + return log_dhcp_client_errno(client, r, "Failed to read %s option: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_SERVER_IDENTIFIER)); + + /* BOOTP typically does not use Server Identifier option, but uses the siaddr field. */ + lease->server_address = message->header.siaddr; + } + + /* lifetime: mandatory */ + if (client->bootp) + lease->lifetime = USEC_INFINITY; /* BOOTP does not support lifetime. */ + else { + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ true, &lease->lifetime); + if (r < 0 || lease->lifetime == 0) { + if (client->fallback_lease_lifetime == 0) { + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read %s option: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME)); + + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), + "The %s option set to 0 second.", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME)); + } + + lease->lifetime = client->fallback_lease_lifetime; + } + + /* There is nothing mentioned about the valid range of the lifetime in RFC, but if it is too + * short, then the network connection easily become unstable. Let's bump to 30 seconds in + * that case. + * TODO: filter short lifetime in selecting state. */ + if (lease->lifetime <= 30 * USEC_PER_SEC) { + log_dhcp_client(client, "The %s option is too short (%s), bumping lease lifetime to 30 seconds.", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME), + FORMAT_TIMESPAN(lease->lifetime, USEC_PER_SEC)); + lease->lifetime = 30 * USEC_PER_SEC; + } + + if (lease->lifetime != USEC_INFINITY) { + /* T2 */ + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_REBINDING_TIME, /* max_as_infinity= */ true, &lease->t2); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to read %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_REBINDING_TIME)); + + /* verify that 0 < t2 < lifetime */ + if (lease->t2 <= 0 || lease->t2 >= lease->lifetime) + /* RFC2131 section 4.4.5: T2 defaults to (0.875 * duration_of_lease). */ + lease->t2 = lease->lifetime * 7 / 8; + + /* T1 */ + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ true, &lease->t1); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to read %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_RENEWAL_TIME)); + + /* verify that 0 < t1 < t2 */ + if (lease->t1 <= 0 || lease->t1 >= lease->t2) + /* RFC2131 section 4.4.5: T1 defaults to (0.5 * duration_of_lease). */ + lease->t1 = lease->lifetime / 2; + + /* For the case when T2 is too small compared with lifetime. */ + if (lease->t1 >= lease->t2) + /* RFC2131 section 4.4.5: T2 defaults to (0.875 * duration_of_lease). */ + lease->t2 = lease->lifetime * 7 / 8; + + assert(lease->t1 > 0); + assert(lease->t1 < lease->t2); + assert(lease->t2 < lease->lifetime); + } + } + + r = dhcp_message_get_option_be32(message, SD_DHCP_OPTION_BROADCAST, &lease->broadcast); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_BROADCAST)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_ROUTER, + &lease->router_size, + &lease->router); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_ROUTER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + &lease->servers[SD_DHCP_LEASE_DNS].size, + &lease->servers[SD_DHCP_LEASE_DNS].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_NAME_SERVER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_NTP_SERVER, + &lease->servers[SD_DHCP_LEASE_NTP].size, + &lease->servers[SD_DHCP_LEASE_NTP].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_NTP_SERVER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_SIP_SERVER, + &lease->servers[SD_DHCP_LEASE_SIP].size, + &lease->servers[SD_DHCP_LEASE_SIP].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_SIP_SERVER)); + + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_STATIC_ROUTE, + &lease->n_static_routes, + &lease->static_routes); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_STATIC_ROUTE)); + + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + &lease->n_classless_routes, + &lease->classless_routes); + if (r < 0) { + if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE)); + + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, + &lease->n_classless_routes, + &lease->classless_routes); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + } + + r = dhcp_message_get_option_6rd( + message, + &lease->sixrd_ipv4masklen, + &lease->sixrd_prefixlen, + &lease->sixrd_prefix, + &lease->sixrd_n_br_addresses, + &lease->sixrd_br_addresses); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_6RD)); + + r = dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_DOMAIN_NAME, &lease->domainname); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_NAME)); + + r = dhcp_message_get_option_hostname(message, &lease->hostname); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s and/or %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_FQDN), + dhcp_option_code_to_string(SD_DHCP_OPTION_HOST_NAME)); + + r = dhcp_message_get_option_domains(message, SD_DHCP_OPTION_DOMAIN_SEARCH, &lease->search_domains); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_SEARCH)); + + r = dhcp_message_get_option_dnr(message, &lease->n_dnr, &lease->dnr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_V4_DNR)); + + _cleanup_free_ char *captive_portal = NULL; + r = dhcp_message_get_option_string(message, SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL, &captive_portal); + if (r >= 0) { + if (!in_charset(captive_portal, URI_VALID)) + log_dhcp_client(client, "Received invalid %s, ignoring: %s", + dhcp_option_code_to_string(SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL), + captive_portal); + else + lease->captive_portal = TAKE_PTR(captive_portal); + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL)); + + _cleanup_free_ char *tz = NULL; + r = dhcp_message_get_option_string(message, SD_DHCP_OPTION_TZDB_TIMEZONE, &tz); + if (r >= 0) { + if (!timezone_is_valid(tz, LOG_DEBUG)) + log_dhcp_client(client, "Received invalid %s, ignoring: %s", + dhcp_option_code_to_string(SD_DHCP_OPTION_TZDB_TIMEZONE), tz); + else + lease->timezone = TAKE_PTR(tz); + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_TZDB_TIMEZONE)); + + uint16_t mtu; + r = dhcp_message_get_option_u16(message, SD_DHCP_OPTION_MTU_INTERFACE, &mtu); + if (r >= 0) { + /* RFC 2132 section 5.1 permits MTU values down to 68 bytes, which corresponds to the minimum + * IPv4 datagram size defined in RFC 791. + * + * Such a small MTU is not generally usable for normal IP communication. RFC 791 and RFC 1122 + * require hosts to be able to reassemble datagrams of at least 576 bytes, which is treated + * as the minimum safe size for IPv4 interoperability. + * + * Ignore MTU values smaller than 576 bytes. */ + if (mtu < IPV4_MIN_REASSEMBLY_SIZE) + log_dhcp_client(client, "Received too small %s, ignoring: %u", + dhcp_option_code_to_string(SD_DHCP_OPTION_MTU_INTERFACE), mtu); + else + lease->mtu = mtu; + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_MTU_INTERFACE)); + + /* RFC 8925 section 3.2 + * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in + * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any + * messages received from the server. */ + if (!client->anonymize && + set_contains(client->req_opts, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED))) { + usec_t t; + r = dhcp_message_get_option_sec( + message, + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, + /* max_as_infinity= */ false, + &t); + if (r >= 0) { + /* RFC 8925 section 3.4 + * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. */ + if (t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled()) + lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC; + else + lease->ipv6_only_preferred_usec = t; + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + } + + lease->message = sd_dhcp_message_ref(message); + *ret = TAKE_PTR(lease); + return 0; +} + +static int client_parse_bootreply(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(message); + assert(ret); + + if (client->state != DHCP_STATE_SELECTING) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected BOOTREPLY."); + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create BOOTP lease: %m"); + + log_dhcp_client(client, "Received BOOTREPLY from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); + + *ret = TAKE_PTR(lease); + return DHCP_ACK; +} + +static int client_parse_ack(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(message); + assert(ret); + + switch (client->state) { + case DHCP_STATE_SELECTING: + if (!client->rapid_commit) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPACK."); + + r = dhcp_message_get_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to get Rapid Commit option: %m"); + + break; + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + break; + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPACK."); + } + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create DHCP lease: %m"); + + log_dhcp_client(client, "Received DHCPACK from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); + + *ret = TAKE_PTR(lease); + return DHCP_ACK; +} + +static int client_parse_offer(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(message); + assert(ret); + + if (client->state != DHCP_STATE_SELECTING) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPOFFER."); + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create DHCP lease: %m"); + + log_dhcp_client(client, "Received DHCPOFFER from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); + + *ret = TAKE_PTR(lease); + return DHCP_OFFER; +} + +static int client_parse_nak(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(message); + assert(ret); + + /* DHCPNAK is a valid reply when we sent DHCPREQUEST. When we receive it after sending + * DHCPDISCOVER (or even we sent nothing), we should ignore the message. */ + if (!IN_SET(client->state, DHCP_STATE_REBOOTING, DHCP_STATE_REQUESTING, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPNAK."); + + /* Always ignore DHCPNAK without Server Identifier option. */ + struct in_addr a; + r = dhcp_message_get_option_address(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read Server Identifier option in DHCPNAK: %m"); + + if (client->lease && client->lease->server_address != a.s_addr) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), + "Received DHCPNAK from unexpected server (%s).", + IN4_ADDR_TO_STRING(&a)); + + _cleanup_free_ char *e = NULL; + (void) dhcp_message_get_option_string(message, SD_DHCP_OPTION_ERROR_MESSAGE, &e); + log_dhcp_client(client, "Received DHCPNAK: %s", strna(e)); + + *ret = NULL; + return DHCP_NAK; +} + +int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret) { + int r; + + assert(client); + assert(iov); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREPLY, + &client->xid, + client->arp_type, + &client->hw_addr, + &message); + if (r < 0) + return r; + + if (client->bootp) + return client_parse_bootreply(client, message, ret); + + uint8_t type; + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &type); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read Message Type option: %m"); + + switch (type) { + case DHCP_OFFER: + return client_parse_offer(client, message, ret); + case DHCP_ACK: + return client_parse_ack(client, message, ret); + case DHCP_NAK: + return client_parse_nak(client, message, ret); + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received message with unexpected type (%u).", type); + } +} From 29e221c9e6aed1b2fa5097fb11a8bffc78f4abdd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 06:51:50 +0900 Subject: [PATCH 1767/2155] network: append DHCP message in json output Also, get private options from sd_dhcp_message object if sd_dhcp_lease has it. With this change, networkctl will be able to use the serialized DHCP message object. Preparation for later changes. --- src/network/networkd-json.c | 38 +++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index d5bed7718a0c5..35c32a0d18b9a 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -11,6 +11,7 @@ #include "dhcp6-lease-internal.h" #include "extract-word.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "ip-protocol-list.h" #include "json-util.h" #include "netif-util.h" @@ -1352,12 +1353,20 @@ static int dhcp_client_lease_append_json(Link *link, sd_json_variant **v) { if (r < 0 && r != -ENODATA) return r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; + if (link->dhcp_lease->message) { + r = dhcp_message_build_json(link->dhcp_lease->message, &m); + if (r < 0) + return r; + } + r = sd_json_buildo( &w, JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec", lease_timestamp_usec), JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec", t1), JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec", t2), - JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", hostname)); + JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", hostname), + JSON_BUILD_PAIR_VARIANT_NON_NULL("Message", m)); if (r < 0) return r; @@ -1419,14 +1428,35 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * if (!link->dhcp_lease) return 0; - LIST_FOREACH(options, option, link->dhcp_lease->private_options) { + if (!link->dhcp_lease->message) { + 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 r; + } + + return json_variant_set_field_non_null(v, "PrivateOptions", array); + } + + for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) { + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(link->dhcp_lease->message, i, &iov); + if (r == -ENODATA) + continue; + if (r < 0) + return r; + 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)); + SD_JSON_BUILD_PAIR_UNSIGNED("Option", i), + SD_JSON_BUILD_PAIR_HEX("PrivateOptionData", iov.iov_base, iov.iov_len)); if (r < 0) return r; } + return json_variant_set_field_non_null(v, "PrivateOptions", array); } From c0e40b14ad14bfa2eeed9a8c88176a6381c9e9be Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 07:56:09 +0900 Subject: [PATCH 1768/2155] networkctl: load information about DHCP client from varlink By the previous commit, networkd now exposes the received DHCP message in the Descibe() DBus/Varlink method. Let's make networkctl deserialize the DHCP message and use it where necessary. --- src/network/meson.build | 1 + src/network/networkctl-dump-util.c | 19 +++-- src/network/networkctl-dump-util.h | 4 +- src/network/networkctl-link-info-json.c | 92 +++++++++++++++++++++++++ src/network/networkctl-link-info-json.h | 8 +++ src/network/networkctl-link-info.c | 35 +--------- src/network/networkctl-link-info.h | 6 ++ src/network/networkctl-status-link.c | 38 ++++++---- src/network/networkctl-status-system.c | 2 +- 9 files changed, 150 insertions(+), 55 deletions(-) create mode 100644 src/network/networkctl-link-info-json.c create mode 100644 src/network/networkctl-link-info-json.h diff --git a/src/network/meson.build b/src/network/meson.build index 134416bcc8dd8..7370d011d0a22 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -131,6 +131,7 @@ networkctl_sources = files( 'networkctl-dump-util.c', 'networkctl-journal.c', 'networkctl-link-info.c', + 'networkctl-link-info-json.c', 'networkctl-list.c', 'networkctl-lldp.c', 'networkctl-misc.c', diff --git a/src/network/networkctl-dump-util.c b/src/network/networkctl-dump-util.c index 20d5e8f43cc00..15fdb103b6d76 100644 --- a/src/network/networkctl-dump-util.c +++ b/src/network/networkctl-dump-util.c @@ -5,6 +5,7 @@ #include "sd-netlink.h" #include "alloc-util.h" +#include "dhcp-protocol.h" #include "ether-addr-util.h" #include "format-ifname.h" #include "format-table.h" @@ -210,13 +211,14 @@ int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) { int dump_addresses( sd_netlink *rtnl, + sd_dhcp_message *message, sd_dhcp_lease *lease, Table *table, int ifindex) { _cleanup_free_ struct local_address *local_addrs = NULL; _cleanup_strv_free_ char **buf = NULL; - struct in_addr dhcp4_address = {}; + struct in_addr dhcp4_address = {}, server_address = {}; int r, n; assert(rtnl); @@ -226,15 +228,18 @@ int dump_addresses( if (n <= 0) return n; - if (lease) + if (message) { + dhcp4_address.s_addr = message->header.yiaddr; + if (dhcp_message_get_option_address(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &server_address) < 0) + /* The message should be BOOTP, let's fallback to the siaddr field. */ + server_address.s_addr = message->header.siaddr; + } else if (lease) { (void) sd_dhcp_lease_get_address(lease, &dhcp4_address); + (void) sd_dhcp_lease_get_server_identifier(lease, &server_address); + } FOREACH_ARRAY(local, local_addrs, n) { - struct in_addr server_address; - bool dhcp4 = false; - - if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address)) - dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0; + bool dhcp4 = local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address); r = strv_extendf(&buf, "%s%s%s%s%s%s", IN_ADDR_TO_STRING(local->family, &local->address), diff --git a/src/network/networkctl-dump-util.h b/src/network/networkctl-dump-util.h index cfd0dd3de0122..2aafaae475670 100644 --- a/src/network/networkctl-dump-util.h +++ b/src/network/networkctl-dump-util.h @@ -3,7 +3,9 @@ #include "shared-forward.h" +#include "dhcp-message.h" + int dump_list(Table *table, const char *key, char * const *l); int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret); int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex); -int dump_addresses(sd_netlink *rtnl, sd_dhcp_lease *lease, Table *table, int ifindex); +int dump_addresses(sd_netlink *rtnl, sd_dhcp_message *message, sd_dhcp_lease *lease, Table *table, int ifindex); diff --git a/src/network/networkctl-link-info-json.c b/src/network/networkctl-link-info-json.c new file mode 100644 index 0000000000000..b187432b805ed --- /dev/null +++ b/src/network/networkctl-link-info-json.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-client-id.h" +#include "sd-json.h" + +#include "iovec-util.h" +#include "json-util.h" +#include "networkctl-link-info.h" +#include "networkctl-link-info-json.h" +#include "networkctl-util.h" + +static int acquire_link_bitrates(LinkInfo *link) { + int r; + + assert(link); + + 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 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 }, + {} + }; + + 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; +} + +static int acquire_link_dhcp_client(LinkInfo *link) { + int r; + + assert(link); + + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPv4Client", "ClientIdentifier"), &v); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + _cleanup_(iovec_done) struct iovec iov = {}; + r = json_dispatch_byte_array_iovec("ClientIdentifier", v, /* flags= */ 0, &iov); + if (r < 0) + return r; + + return sd_dhcp_client_id_set_raw(&link->dhcp_client_id, iov.iov_base, iov.iov_len); +} + +static int acquire_link_dhcp_message(LinkInfo *link) { + int r; + + assert(link); + + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPv4Client", "Lease", "Message"), &v); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + return dhcp_message_parse_json(v, &link->dhcp_message); +} + +int link_info_parse_description(LinkInfo *link, sd_varlink *vl) { + int r; + + assert(link); + + if (!vl) + return 0; + + r = acquire_link_description(vl, link->ifindex, &link->description); + if (r < 0) + return r; + + (void) acquire_link_bitrates(link); + (void) acquire_link_dhcp_client(link); + (void) acquire_link_dhcp_message(link); + + return 0; +} diff --git a/src/network/networkctl-link-info-json.h b/src/network/networkctl-link-info-json.h new file mode 100644 index 0000000000000..2b15e5b01b68c --- /dev/null +++ b/src/network/networkctl-link-info-json.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef struct LinkInfo LinkInfo; + +int link_info_parse_description(LinkInfo *link, sd_varlink *vl); diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c index 0b40b442537ac..789f2a00eedd4 100644 --- a/src/network/networkctl-link-info.c +++ b/src/network/networkctl-link-info.c @@ -11,7 +11,7 @@ #include "glob-util.h" #include "netlink-util.h" #include "networkctl-link-info.h" -#include "networkctl-util.h" +#include "networkctl-link-info-json.h" #include "sort-util.h" #include "stdio-util.h" #include "string-util.h" @@ -26,6 +26,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); + sd_dhcp_message_unref(array[i].dhcp_message); free(array[i].netdev_kind); free(array[i].ssid); free(array[i].qdisc); @@ -278,34 +279,6 @@ static int decode_link( return 1; } -static int acquire_link_bitrates(LinkInfo *link) { - int r; - - assert(link); - - 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 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 }, - {} - }; - - 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; -} - static void acquire_ether_link_info(int *fd, LinkInfo *link) { assert(fd); assert(link); @@ -398,9 +371,7 @@ int acquire_link_info(sd_varlink *vl, sd_netlink *rtnl, char * const *patterns, 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]); + (void) link_info_parse_description(&links[c], vl); c++; } diff --git a/src/network/networkctl-link-info.h b/src/network/networkctl-link-info.h index ebea57348a30d..ae1bf71687510 100644 --- a/src/network/networkctl-link-info.h +++ b/src/network/networkctl-link-info.h @@ -5,6 +5,8 @@ #include #include +#include "dhcp-client-id-internal.h" +#include "dhcp-message.h" #include "ether-addr-util.h" #include "ethtool-util.h" #include "shared-forward.h" @@ -58,6 +60,10 @@ typedef struct LinkInfo { uint64_t tx_bitrate; uint64_t rx_bitrate; + /* DHCPv4 */ + sd_dhcp_message *dhcp_message; + sd_dhcp_client_id dhcp_client_id; + /* bridge info */ uint32_t forward_delay; uint32_t hello_time; diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 15c9c46336b3a..9543953df496d 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -3,6 +3,7 @@ #include "sd-device.h" #include "sd-dhcp-client-id.h" #include "sd-dhcp-lease.h" +#include "sd-dhcp-protocol.h" #include "sd-hwdb.h" #include "sd-netlink.h" #include "sd-network.h" @@ -779,7 +780,7 @@ static int link_status_one( return r; } - r = dump_addresses(rtnl, lease, table, info->ifindex); + r = dump_addresses(rtnl, info->dhcp_message, lease, table, info->ifindex); if (r < 0) return r; @@ -837,8 +838,18 @@ static int link_status_one( return table_log_add_error(r); } - if (lease) { - const sd_dhcp_client_id *client_id; + if (info->dhcp_message) { + _cleanup_free_ char *tz = NULL; + + if (dhcp_message_get_option_string(info->dhcp_message, SD_DHCP_OPTION_TZDB_TIMEZONE, &tz) >= 0 + && timezone_is_valid(tz, LOG_DEBUG)) { + r = table_add_many(table, + TABLE_FIELD, "Time Zone", + TABLE_STRING, tz); + if (r < 0) + return table_log_add_error(r); + } + } else if (lease) { const char *tz; r = sd_dhcp_lease_get_timezone(lease, &tz); @@ -849,19 +860,18 @@ static int link_status_one( if (r < 0) return table_log_add_error(r); } + } + + if (sd_dhcp_client_id_is_set(&info->dhcp_client_id)) { + _cleanup_free_ char *id = NULL; - r = sd_dhcp_lease_get_client_id(lease, &client_id); + r = sd_dhcp_client_id_to_string(&info->dhcp_client_id, &id); if (r >= 0) { - _cleanup_free_ char *id = NULL; - - r = sd_dhcp_client_id_to_string(client_id, &id); - if (r >= 0) { - r = table_add_many(table, - TABLE_FIELD, "DHCPv4 Client ID", - TABLE_STRING, id); - if (r < 0) - return table_log_add_error(r); - } + r = table_add_many(table, + TABLE_FIELD, "DHCPv4 Client ID", + TABLE_STRING, id); + if (r < 0) + return table_log_add_error(r); } } diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index a03004b882aad..78dd77819dac2 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -94,7 +94,7 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { if (r < 0) return table_log_add_error(r); - r = dump_addresses(rtnl, NULL, table, 0); + r = dump_addresses(rtnl, /* message= */ NULL, /* lease= */ NULL, table, /* ifindex= */ 0); if (r < 0) return r; From c043b9018733382c49d3d40f7ca406c43e9ae9fc Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 15 May 2026 22:52:13 +0100 Subject: [PATCH 1769/2155] Revert "shared/pager: add support for more(1) pager in secure mode" --- README | 6 +++--- man/common-variables.xml | 11 ++++------- src/shared/pager.c | 11 ++--------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/README b/README index 30bb9ee5116d8..1cae26ba3a5f6 100644 --- a/README +++ b/README @@ -264,9 +264,9 @@ REQUIREMENTS: During runtime, you need the following additional dependencies: - util-linux >= v2.42 required (including but not limited to: mount, - umount, swapon, swapoff, sulogin, - agetty, fsck, more) + 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). diff --git a/man/common-variables.xml b/man/common-variables.xml index 74463c23d6083..0f807b2cde1d2 100644 --- a/man/common-variables.xml +++ b/man/common-variables.xml @@ -190,14 +190,11 @@ allowing untrusted users to execute commands with elevated privileges. This option takes a boolean argument. When set to true, the "secure mode" of the pager is - enabled. In "secure mode", and will be set - when invoking the pager, which instructs the pager to disable commands that open or create new files or - start new subprocesses. + enabled. In "secure mode", will be set when invoking the pager, which + instructs the pager to disable commands that open or create new files or start new subprocesses. Currently only less1 and - more1 are known - to understand these variables, respectively, and implement "secure mode". + project='man-pages'>less1 is known + to understand this variable and implement "secure mode". When set to false, no limitation is placed on the pager. Setting SYSTEMD_PAGERSECURE=0 or not removing it from the inherited environment may allow diff --git a/src/shared/pager.c b/src/shared/pager.c index 61718aeaa6807..3c89aacab48d0 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -206,13 +206,6 @@ void pager_open(PagerFlags flags) { _exit(EXIT_FAILURE); } - /* Some pager implementations support the PAGERSECURE environment variable, e.g. more(1) */ - r = set_unset_env("PAGERSECURE", use_secure_mode ? "1" : NULL, true); - if (r < 0) { - log_error_errno(r, "Failed to adjust environment variable PAGERSECURE: %m"); - _exit(EXIT_FAILURE); - } - if (trust_pager && pager_args) { /* The pager config might be set globally, and we cannot * know if the user adjusted it to be appropriate for the * secure mode. Thus, start the pager specified through @@ -235,8 +228,8 @@ void pager_open(PagerFlags flags) { static const char* pagers[] = { "pager", "less", "more", "(built-in)" }; for (unsigned i = 0; i < ELEMENTSOF(pagers); i++) { - /* Only less, more (and our trivial fallback) implement secure mode right now. */ - if (use_secure_mode && !STR_IN_SET(pagers[i], "less", "more", "(built-in)")) + /* Only less (and our trivial fallback) implement secure mode right now. */ + if (use_secure_mode && !STR_IN_SET(pagers[i], "less", "(built-in)")) continue; r = loop_write(exe_name_pipe[1], pagers[i], strlen(pagers[i]) + 1); From 980e4132ac708b02e4851e3209c6d52eb8cbfbce Mon Sep 17 00:00:00 2001 From: "Fco. Javier F. Serrador" Date: Sat, 16 May 2026 11:59:10 +0000 Subject: [PATCH 1770/2155] po: Translated using Weblate (Spanish) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Fco. Javier F. Serrador Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/es/ Translation: systemd/main --- po/es.po | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/po/es.po b/po/es.po index 7be1c80413602..0b85cc3e1c40c 100644 --- a/po/es.po +++ b/po/es.po @@ -8,12 +8,12 @@ # Emilio Herrera , 2021. # Jose Ortuno , 2025. # Javier Francisco , 2025. -# "Fco. Javier F. Serrador" , 2025. +# "Fco. Javier F. Serrador" , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-11-29 03:49+0000\n" +"PO-Revision-Date: 2026-05-16 11:59+0000\n" "Last-Translator: \"Fco. Javier F. Serrador\" \n" "Language-Team: Spanish \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.14.3\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1057,12 +1057,12 @@ msgid "DHCP server sends force renew message" msgstr "El servidor DHCP envía un mensaje de renovación forzada" #: 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 "Necesita autenticarse para enviar el mensaje de renovación forzada." +msgstr "" +"Necesita autenticarse para enviar un mensaje de renovación forzada desde el " +"servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1104,11 +1104,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestionar enlaces de red" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "La autenticación está requerida para gestionar enlaces de red." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 8fe609445033d1e3b00dd75f1b4812a66abc8339 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 14:45:42 +0900 Subject: [PATCH 1771/2155] dhcp-message: introduce dhcp_message_send_{udp,raw}() --- src/libsystemd-network/dhcp-message.c | 124 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 17 +++ src/libsystemd-network/test-dhcp-message.c | 93 ++++++++++++++++ 3 files changed, 234 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 07664588301de..ae12c5b46a976 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -20,6 +20,7 @@ #include "json-util.h" #include "network-common.h" #include "set.h" +#include "socket-util.h" #include "sort-util.h" #include "string-util.h" #include "unaligned.h" @@ -1645,3 +1646,126 @@ int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret) { *ret = TAKE_PTR(message); return 0; } + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port) { + + int r; + + assert(message); + assert(fd >= 0); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(dst_port), + .in.sin_addr.s_addr = dst_addr, + }; + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + + struct msghdr msg = { + .msg_name = &sa, + .msg_namelen = sizeof(sa.in), + .msg_iov = payload.iovec, + .msg_iovlen = payload.count, + }; + + if (src_addr != INADDR_ANY) { + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); + pktinfo->ipi_spec_dst.s_addr = src_addr; + } + + if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type) { + + int r; + + assert(message); + assert(fd >= 0); + assert(ifindex > 0); + assert(dst_hw_addr); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + struct iphdr ip; + struct udphdr udp; + r = udp_packet_build( + src_addr, + src_port, + dst_addr, + dst_port, + ip_service_type, + &payload, + &ip, + &udp); + if (r < 0) + return r; + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); + if (r < 0) + return r; + + r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); + if (r < 0) + return r; + + r = iovw_put_iovw(&iovw, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = dst_hw_addr->length, + }; + + memcpy_safe(sa.ll.sll_addr, dst_hw_addr->bytes, dst_hw_addr->length); + + struct msghdr mh = { + .msg_name = &sa.sa, + .msg_namelen = sockaddr_ll_len(&sa.ll), + .msg_iov = iovw.iovec, + .msg_iovlen = iovw.count, + }; + + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 41b22d2488092..2e7e74def3eb2 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -93,3 +93,20 @@ int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret); int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret); + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port); +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 5194cffcc24ec..b38220aef5259 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -12,10 +12,13 @@ #include "dns-packet.h" #include "dns-resolver-internal.h" #include "ether-addr-util.h" +#include "fd-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" +#include "ip-util.h" #include "random-util.h" #include "set.h" +#include "socket-util.h" #include "strv.h" #include "tests.h" @@ -189,6 +192,92 @@ static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_w ASSERT_TRUE(iovw_equal(&iovw, expected)); } +static void verify_send_udp(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_udp( + message, + socket_fd[0], + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &IOVEC_MAKE(buf, len), + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +static void verify_send_raw(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_raw( + message, + socket_fd[0], + /* ifindex= */ 42, + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* src_port= */ DHCP_PORT_SERVER, + /* dst_hw_addr= */ &(struct hw_addr_data) { + .length = 6, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }}, + }, + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT, + /* ip_service_type= */ IPTOS_CLASS_CS6)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + + struct iovec payload; + ASSERT_OK(udp_packet_verify( + &IOVEC_MAKE(buf, len), + DHCP_PORT_CLIENT, + /* checksum= */ true, + &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -452,6 +541,10 @@ TEST(dhcp_message) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw3 = {}; ASSERT_OK(dhcp_message_build(m3, &iovw3)); ASSERT_TRUE(iovw_equal(&iovw, &iovw3)); + + /* send */ + verify_send_udp(m, xid, &hw_addr); + verify_send_raw(m, xid, &hw_addr); } static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { From 6ae696a9568457aba24b3d3265cb9f9f38228eb4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 16 May 2026 07:18:37 +0900 Subject: [PATCH 1772/2155] socket-util: extend the buffer in sockaddr_union to make it consistent with hw_addr_data Concerned by Claude: https://github.com/systemd/systemd/pull/42119#discussion_r3251133516 --- src/basic/basic-forward.h | 4 ++++ src/basic/ether-addr-util.h | 4 ---- src/basic/socket-util.c | 2 ++ src/basic/socket-util.h | 6 ++---- src/libsystemd-network/lldp-network.c | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 396056a8e55eb..064af284a654c 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -153,3 +153,7 @@ typedef struct SocketAddress SocketAddress; /* MAX_ERRNO is defined as 4095 in linux/err.h. We use the same value here. */ #define ERRNO_MAX 4095 + +/* This is MAX_ADDR_LEN as defined in linux/netdevice.h, but net/if_arp.h + * defines a macro of the same name with a much lower size. */ +#define HW_ADDR_MAX_SIZE 32 diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index 1d6e4cda7f016..42b899e7ebc86 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -7,10 +7,6 @@ #include "basic-forward.h" -/* This is MAX_ADDR_LEN as defined in linux/netdevice.h, but net/if_arp.h - * defines a macro of the same name with a much lower size. */ -#define HW_ADDR_MAX_SIZE 32 - struct hw_addr_data { size_t length; union { diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index d53208f138990..d84537bf4413a 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 208eb7ac077be..1703a518e48de 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include -#include #include #include #include @@ -29,8 +27,8 @@ union sockaddr_union { struct sockaddr_ll ll; struct sockaddr_vm vm; - /* Ensure there is enough space to store Infiniband addresses */ - uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)]; + /* Ensure there is enough space to store an arbitrary hardware address, e.g. Infiniband */ + uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + HW_ADDR_MAX_SIZE]; /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path * component is always followed by at least one NUL byte. */ diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c index 53dd50c606887..f8185a28029bb 100644 --- a/src/libsystemd-network/lldp-network.c +++ b/src/libsystemd-network/lldp-network.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "fd-util.h" #include "lldp-network.h" From fcf2de89bb7c3ba3914f047647fd1ce61a35d7b6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 22:52:12 +0900 Subject: [PATCH 1773/2155] fuzz-dhcp-client: fuzz dhcp_client_parse_message() Also, if the fuzzing engine provides a valid message, then try to build json variant and UDP payload from the parsed message. We will drop dhcp_lease_save() and dhcp_lease_load(), hence the tests for them are dropped. --- src/libsystemd-network/fuzz-dhcp-client.c | 41 +++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index d6a2577194d51..bc6be37be80d6 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -1,14 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include +#include "sd-event.h" +#include "sd-json.h" + +#include "dhcp-client-internal.h" +#include "dhcp-lease-internal.h" +#include "dhcp-message.h" #include "dhcp-network.h" -#include "fd-util.h" #include "fuzz.h" -#include "network-internal.h" -#include "sd-dhcp-client.c" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "tests.h" -#include "tmpfile-util.h" int dhcp_network_bind_raw_socket( int ifindex, @@ -61,14 +66,30 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { client->xid = 2; client->state = DHCP_STATE_SELECTING; - 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)); + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + if (dhcp_client_parse_message(client, &IOVEC_MAKE(data, size), &lease) >= 0) { + /* Build json variant and parse it. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ASSERT_OK(dhcp_message_build_json(lease->message, &v)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse_json(v, &m)); + + /* Build UDP payload and parse it. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_build(lease->message, &iovw)); + + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(&iovw, &iov)); + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease2 = NULL; + ASSERT_OK(dhcp_client_parse_message(client, &iov, &lease2)); - ASSERT_OK(dhcp_lease_save(client->lease, lease_file)); + /* Build UDP payload again, and compare with the previous one. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; + ASSERT_OK(dhcp_message_build(lease2->message, &iovw2)); - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - ASSERT_OK(dhcp_lease_load(&lease, lease_file)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); } ASSERT_OK(sd_dhcp_client_stop(client)); From 0435151bc8493f68629020dec369dc2fac62d6fc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 22:45:04 +0900 Subject: [PATCH 1774/2155] sd-dhcp-client: use dhcp_client_parse_message() This also unify two IO event source handlers. --- src/libsystemd-network/sd-dhcp-client.c | 468 ++++-------------------- 1 file changed, 71 insertions(+), 397 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index e2513567a2f58..afe40ee09afa3 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -12,15 +12,13 @@ #include "dhcp-client-internal.h" #include "dhcp-client-send.h" #include "dhcp-lease-internal.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" #include "dns-domain.h" #include "errno-util.h" #include "event-util.h" #include "hostname-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" -#include "memory-util.h" +#include "ip-util.h" #include "network-common.h" #include "random-util.h" #include "set.h" @@ -131,12 +129,6 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option)); } -static int client_request_contains(sd_dhcp_client *client, uint8_t option) { - assert(client); - - return set_contains(client->req_opts, UINT8_TO_PTR(option)); -} - int sd_dhcp_client_set_request_address( sd_dhcp_client *client, const struct in_addr *last_addr) { @@ -947,195 +939,6 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) return client_timeout_resend(s, usec, userdata); } -static int dhcp_option_parse_and_verify( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease *lease) { - - _cleanup_free_ char *error_message = NULL; - int r; - - assert(client); - assert(message); - assert(lease); - - r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, &error_message); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to parse DHCP options, ignoring: %m"); - - switch (client->state) { - case DHCP_STATE_SELECTING: - if (r == DHCP_ACK) { - if (!client->rapid_commit) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received unexpected ACK, ignoring."); - if (!lease->rapid_commit) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received rapid ACK without Rapid Commit option, ignoring."); - } else if (r == DHCP_OFFER) { - if (lease->rapid_commit) { - /* Some RFC incompliant servers provides an OFFER with a rapid commit option. - * See https://github.com/systemd/systemd/issues/29904. - * Let's support such servers gracefully. */ - log_dhcp_client(client, "received OFFER with Rapid Commit option, ignoring."); - lease->rapid_commit = false; - } - if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) - lease->lifetime = client->fallback_lease_lifetime; - } else - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received unexpected message, ignoring."); - - break; - - case DHCP_STATE_REBOOTING: - case DHCP_STATE_REQUESTING: - case DHCP_STATE_RENEWING: - case DHCP_STATE_REBINDING: - if (r == DHCP_NAK) { - if (client->lease && client->lease->server_address != lease->server_address) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "NAK from unexpected server, ignoring: %s", - strna(error_message)); - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "NAK: %s", strna(error_message)); - } - if (r != DHCP_ACK) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received message was not an ACK, ignoring."); - break; - - default: - assert_not_reached(); - } - - lease->next_server = message->siaddr; - lease->address = message->yiaddr; - - if (lease->address == 0 || - lease->server_address == 0 || - lease->lifetime == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks address, server address or lease lifetime, ignoring."); - - return 0; -} - -static int bootp_option_parse_and_verify( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease *lease) { - - int r; - - assert(client); - assert(message); - assert(lease); - - r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, /* ret_error_message= */ NULL); - if (r == -ENOMSG) - r = DHCP_ACK; /* BOOTP messages don't have a DHCP message type option */ - else if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to parse BOOTP options, ignoring: %m"); - else - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "Received unexpected message, ignoring."); - - log_dhcp_client(client, "BOOTP identified, using infinite lease. BOOTP siaddr=(%#x), DHCP Server Identifier=(%#x)", - message->siaddr, lease->server_address); - - lease->lifetime = USEC_INFINITY; - lease->address = message->yiaddr; - if (lease->server_address == 0) - lease->server_address = message->siaddr; - - /* BOOTP protocol does not have any OFFER and REQUEST process. Hence, it is mostly equivalent to - * Rapid Commit process in DHCP. */ - lease->rapid_commit = true; - - if (lease->address == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "received lease lacks address, ignoring."); - - return 0; -} - -static int client_parse_message( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease **ret) { - - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - assert(ret); - - r = dhcp_lease_new(&lease); - if (r < 0) - return r; - - if (sd_dhcp_client_id_is_set(&client->client_id)) { - r = dhcp_lease_set_client_id(lease, &client->client_id); - if (r < 0) - return r; - } - - if (client->bootp) - r = bootp_option_parse_and_verify(client, message, len, lease); - else - r = dhcp_option_parse_and_verify(client, message, len, lease); - if (r < 0) - return r; - - r = dhcp_lease_set_default_subnet_mask(lease); - if (r < 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring."); - - /* RFC 8925 section 3.2 - * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in - * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any - * messages received from the server. */ - if (lease->ipv6_only_preferred_usec > 0 && - !client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) { - log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option."); - lease->ipv6_only_preferred_usec = 0; - } - - *ret = TAKE_PTR(lease); - return 0; -} - -static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - - r = client_parse_message(client, message, len, &lease); - if (r < 0) - return r; - - dhcp_lease_set_timestamp(lease, timestamp); - - dhcp_lease_unref_and_replace(client->lease, lease); - - if (client->lease->rapid_commit) { - log_dhcp_client(client, "ACK"); - return SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - } - - if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) - return -ENOMSG; - - log_dhcp_client(client, "OFFER"); - return 0; -} - static int client_enter_requesting(sd_dhcp_client *client) { assert(client); assert(client->lease); @@ -1186,32 +989,6 @@ static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { return true; } -static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - - r = client_parse_message(client, message, len, &lease); - if (r < 0) - return r; - - dhcp_lease_set_timestamp(lease, timestamp); - - if (!client->lease) - r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - else if (lease_equal(client->lease, lease)) - r = SD_DHCP_CLIENT_EVENT_RENEW; - else - r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - - dhcp_lease_unref_and_replace(client->lease, lease); - - log_dhcp_client(client, "ACK"); - return r; -} - static int client_set_lease_timeouts(sd_dhcp_client *client) { usec_t time_now; int r; @@ -1319,14 +1096,32 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; } -static int client_enter_bound(sd_dhcp_client *client, int notify_event) { +static int client_enter_bound(sd_dhcp_client *client, sd_dhcp_lease *lease) { int r; assert(client); - assert(client->lease); + assert(lease); - if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) + int notify_event; + switch (client->state) { + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_REBOOTING: notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + break; + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + assert(client->lease); + if (lease_equal(client->lease, lease)) + notify_event = SD_DHCP_CLIENT_EVENT_RENEW; + else + notify_event = SD_DHCP_CLIENT_EVENT_IP_CHANGE; + break; + default: + assert_not_reached(); + } + + unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref); client_disable_event_sources(client); @@ -1344,136 +1139,51 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { 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; - - assert(client); - assert(message); - - if (len < sizeof(DHCPMessage)) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Too small to be a DHCP message, ignoring."); - - if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Not a DHCP message, ignoring."); - - if (message->op != BOOTREPLY) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Not a BOOTREPLY message, ignoring."); - - if (message->htype != client->arp_type) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Packet type does not match client type, ignoring."); - - if (client->arp_type == ARPHRD_ETHER) { - expected_hlen = ETH_ALEN; - expected_chaddr = client->hw_addr.bytes; - } - - if (message->hlen != expected_hlen) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Received packet hlen (%u) does not match expected (%u), ignoring.", - message->hlen, expected_hlen); - - if (memcmp_safe(message->chaddr, expected_chaddr, expected_hlen)) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Received chaddr does not match expected, ignoring."); - - 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); - - return 0; -} - -static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { +static int client_handle_message(sd_dhcp_client *client, const struct iovec *iov, const triple_timestamp *timestamp) { DHCP_CLIENT_DONT_DESTROY(client); int r; assert(client); - assert(message); - assert(timestamp); - - if (client_verify_message_header(client, message, len) < 0) - return 0; + assert(iov); - switch (client->state) { - case DHCP_STATE_SELECTING: + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_client_parse_message(client, iov, &lease); + if (ERRNO_IS_NEG_RESOURCE(r)) + return r; + if (r < 0) + return 0; /* Ignore all parse errors. */ - r = client_handle_offer_or_rapid_ack(client, message, len, timestamp); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r == -EADDRNOTAVAIL) - /* got a rapid NAK, let's restart the client */ - return client_restart(client); - if (r < 0) - return 0; /* invalid message, let's ignore it */ + switch (r) { - if (client->lease->rapid_commit) - /* got a successful rapid commit */ - return client_enter_bound(client, r); + case DHCP_OFFER: + dhcp_lease_set_timestamp(lease, timestamp); + unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref); + if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) + return 0; /* networkd refused the server, ignoring the message. */ + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ return client_enter_requesting(client); - case DHCP_STATE_REBOOTING: - case DHCP_STATE_REQUESTING: - case DHCP_STATE_RENEWING: - case DHCP_STATE_REBINDING: - - r = client_handle_ack(client, message, len, timestamp); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r == -EADDRNOTAVAIL) - /* got a NAK, let's restart the client */ - return client_restart(client); - if (r < 0) - return 0; /* invalid message, let's ignore it */ - - return client_enter_bound(client, r); + case DHCP_ACK: + dhcp_lease_set_timestamp(lease, timestamp); + return client_enter_bound(client, lease); - case DHCP_STATE_BOUND: - log_dhcp_client(client, "Unexpected DHCP message received in BOUND state, ignoring."); - return 0; - - case DHCP_STATE_INIT: - case DHCP_STATE_INIT_REBOOT: - log_dhcp_client(client, "Unexpectedly receive message without sending any requests, ignoring."); - return 0; + case DHCP_NAK: + return client_restart(client); default: assert_not_reached(); } - - return 0; } -int client_receive_message_udp( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - - sd_dhcp_client *client = ASSERT_PTR(userdata); - _cleanup_free_ DHCPMessage *message = NULL; - ssize_t len, buflen; - /* This needs to be initialized with zero. See #20741. - * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ - CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {}; - struct iovec iov; - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; +static int client_receive_message(sd_dhcp_client *client, int fd, bool raw) { int r; - assert(s); + assert(client); + assert(fd >= 0); - buflen = next_datagram_size_fd(fd); + ssize_t buflen = next_datagram_size_fd(fd); if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) return 0; if (buflen < 0) { @@ -1481,92 +1191,56 @@ int client_receive_message_udp( return 0; } - message = malloc0(buflen); - if (!message) + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) return -ENOMEM; - iov = IOVEC_MAKE(message, buflen); - - len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); - if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) - return 0; - if (len < 0) { - log_dhcp_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m"); - return 0; - } - - log_dhcp_client(client, "Received message from UDP socket, processing."); - r = client_handle_message(client, message, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - client_stop(client, r); - - return 0; -} - -int client_receive_message_raw( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - - sd_dhcp_client *client = ASSERT_PTR(userdata); - _cleanup_free_ DHCPPacket *packet = NULL; /* This needs to be initialized with zero. See #20741. * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + CMSG_SPACE(sizeof(struct tpacket_auxdata))) control = {}; - struct iovec iov = {}; struct msghdr msg = { - .msg_iov = &iov, + .msg_iov = &IOVEC_MAKE(buf, buflen), .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), }; - bool checksum = true; - ssize_t buflen, len; - int r; - - assert(s); - - buflen = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) - return 0; - if (buflen < 0) { - log_dhcp_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } - - packet = malloc0(buflen); - if (!packet) - return -ENOMEM; - iov = IOVEC_MAKE(packet, buflen); - - len = recvmsg_safe(fd, &msg, 0); + ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) return 0; if (len < 0) { - log_dhcp_client_errno(client, len, "Could not receive message from raw socket, ignoring: %m"); + log_dhcp_client_errno(client, len, + "Could not receive message from %s socket, ignoring: %m", + raw ? "RAW" : "UDP"); return 0; } - struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata); - if (aux) - checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); - - if (dhcp_packet_verify_headers(packet, len, checksum, client->port) < 0) - return 0; + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) { + struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata); + bool checksum = !aux || !(aux->tp_status & TP_STATUS_CSUMNOTREADY); - len -= DHCP_IP_UDP_SIZE; + if (udp_packet_verify(&payload, client->port, checksum, &payload) < 0) + return 0; + } - log_dhcp_client(client, "Received message from RAW socket, processing."); - r = client_handle_message(client, &packet->dhcp, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + log_dhcp_client(client, "Received message from %s socket, processing.", raw ? "RAW" : "UDP"); + r = client_handle_message(client, &payload, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); if (r < 0) client_stop(client, r); return 0; } +int client_receive_message_udp(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + return client_receive_message(userdata, fd, /* raw= */ false); +} + +int client_receive_message_raw(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + return client_receive_message(userdata, fd, /* raw= */ true); +} + 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 */ From de079ebc04c421db29d936ea374cfac90431aba0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 11 May 2026 21:17:51 +0900 Subject: [PATCH 1775/2155] sd-dhcp-lease: drop unused dhcp_lease_unref_and_replace() --- src/libsystemd-network/dhcp-lease-internal.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 6a562901142bb..b26de88351a8e 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -91,6 +91,3 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret); - -#define dhcp_lease_unref_and_replace(a, b) \ - free_and_replace_full(a, b, sd_dhcp_lease_unref) From 1a0f2c1fb253a97cbfb66755a8633acde2da60d2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:26:34 +0900 Subject: [PATCH 1776/2155] sd-dhcp-client: drop unnecessary T1/T2 adjustment logic It is already done in client_parse_ack() -> dhcp_lease_new_from_message(). --- src/libsystemd-network/sd-dhcp-client.c | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index afe40ee09afa3..d16ffc0655255 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -690,12 +690,6 @@ static usec_t client_compute_request_timeout(uint64_t attempt) { return usec_sub_signed(timeout, RFC2131_RANDOM_FUZZ); } -/* RFC2131 section 4.4.5: - * T1 defaults to (0.5 * duration_of_lease). - * T2 defaults to (0.875 * duration_of_lease). */ -#define T1_DEFAULT(lifetime) ((lifetime) / 2) -#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8) - /* RFC2131 section 4.4.5: * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state) * and one-half of the remaining lease time (in REBINDING state), down to a minimum @@ -1012,22 +1006,6 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (r < 0) return r; - /* verify that 0 < t2 < lifetime */ - if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime) - client->lease->t2 = T2_DEFAULT(client->lease->lifetime); - /* verify that 0 < t1 < lifetime */ - if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2) - client->lease->t1 = T1_DEFAULT(client->lease->lifetime); - /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check - * could not evaluate to false if t1 >= t2; so setting t2 to T2_DEFAULT - * guarantees t1 < t2. */ - if (client->lease->t1 >= client->lease->t2) - client->lease->t2 = T2_DEFAULT(client->lease->lifetime); - - assert(client->lease->t1 > 0); - assert(client->lease->t1 < client->lease->t2); - assert(client->lease->t2 < client->lease->lifetime); - r = sd_dhcp_lease_get_lifetime_timestamp(client->lease, CLOCK_BOOTTIME, &client->expire_time); if (r < 0) return r; From 8de24db5de6063feb1d23f9274fb52feb5995714 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 14 Apr 2026 08:57:47 +0900 Subject: [PATCH 1777/2155] sd-dhcp-client: do not trigger assertion when running on interface with small MTU This also drops spurious default value. If mtu is unset, now the correct default value is set in client_new_message() in dhcp-client-send.c. --- src/libsystemd-network/sd-dhcp-client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index d16ffc0655255..10161148a4d79 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -487,13 +487,14 @@ int sd_dhcp_client_set_port( int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { assert_return(client, -EINVAL); - assert_return(mtu >= DHCP_MIN_PACKET_SIZE, -ERANGE); /* MTU may be changed by the acquired lease. Hence, we cannot require that the client is stopped here. * Please do not add assertion for !sd_dhcp_client_is_running(client) here. */ - client->mtu = mtu; + if (mtu < IPV4_MIN_MTU) + return -ERANGE; + client->mtu = mtu; return 0; } @@ -1441,7 +1442,6 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .n_ref = 1, .state = DHCP_STATE_STOPPED, .ifindex = -1, - .mtu = DHCP_MIN_PACKET_SIZE, .port = DHCP_PORT_CLIENT, .server_port = DHCP_PORT_SERVER, .anonymize = !!anonymize, From cab5c5ef8177a077e0b6f61bd31903e61ad2cc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 19:29:36 +0200 Subject: [PATCH 1778/2155] shared/verbs: add VERB_DEFAULT_NOARG --- src/bless-boot/bless-boot.c | 2 +- src/busctl/busctl.c | 3 +-- src/creds/creds.c | 3 +-- src/factory-reset/factory-reset-tool.c | 2 +- src/hostname/hostnamectl.c | 2 +- src/import/importctl.c | 2 +- src/locale/localectl.c | 3 +-- src/login/loginctl.c | 3 +-- src/measure/measure-tool.c | 3 +-- src/oom/oomctl.c | 3 +-- src/pcrlock/pcrlock.c | 2 +- src/portable/portablectl.c | 2 +- src/shared/verbs.h | 2 ++ src/sysext/sysext.c | 2 +- src/timedate/timedatectl.c | 2 +- 15 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index e0afb3611c278..3680dfdc1883e 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -339,7 +339,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"); +VERB_DEFAULT_NOARG(verb_status, "status", "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; diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 79ee1c55e8ecd..9bf990d3dbfdd 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -176,8 +176,7 @@ static void notify_bus_error(const sd_bus_error *error) { (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name); } -VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, - "List bus names"); +VERB_DEFAULT_NOARG(verb_list, "list", "List bus names"); static int verb_list(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; diff --git a/src/creds/creds.c b/src/creds/creds.c index 95af91c120db7..a4087fc2be650 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -305,8 +305,7 @@ 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"); +VERB_DEFAULT_NOARG(verb_list, "list", "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; diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index e26e948e93416..c21e84c2ef7af 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -99,7 +99,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return 1; } -VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Report current factory reset status"); +VERB_DEFAULT_NOARG(verb_status, "status", "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 */ diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 257b31fb88dda..ab21fcca1f070 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -545,7 +545,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"); +VERB_DEFAULT_NOARG(verb_show_status, "status", "Show current hostname settings"); static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = userdata; int r; diff --git a/src/import/importctl.c b/src/import/importctl.c index d4a6483f36d7c..0140d0f67de48 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -833,7 +833,7 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda return transfer_image_common(bus, m); } -VERB(verb_list_transfers, "list-transfers", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show list of transfers in progress"); +VERB_DEFAULT_NOARG(verb_list_transfers, "list-transfers", "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; diff --git a/src/locale/localectl.c b/src/locale/localectl.c index e20d5da9a0fc1..f1f9ffbce0c05 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -147,8 +147,7 @@ static int print_status_info(StatusInfo *i) { return table_print_or_warn(table); } -VERB(verb_show_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, - "Show current locale settings"); +VERB_DEFAULT_NOARG(verb_show_status, "status", "Show current locale settings"); 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[] = { diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 5647621ba5460..39c048e70891c 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -269,8 +269,7 @@ static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, VERB_GROUP("Session Commands"); -VERB(verb_list_sessions, "list-sessions", NULL, VERB_ANY, 1, VERB_DEFAULT, - "List sessions"); +VERB_DEFAULT_NOARG(verb_list_sessions, "list-sessions", "List sessions"); 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; diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index d5bb5bea2351c..b8acd6d4b0922 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -416,8 +416,7 @@ static int validate_stub(void) { return 0; } -VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, - "Show current PCR values"); +VERB_DEFAULT_NOARG(verb_status, "status", "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; diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 82ffe0e8379fd..f6d451612b725 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -65,8 +65,7 @@ static int help(void) { VERB_COMMON_HELP_HIDDEN(help); -VERB(verb_dump_state, "dump", NULL, VERB_ANY, 1, VERB_DEFAULT, - "Output the current state of systemd-oomd"); +VERB_DEFAULT_NOARG(verb_dump_state, "dump", "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; diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 4ebe3f995bdd8..8315384b4d10e 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2500,7 +2500,7 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } -VERB(verb_show_log, "log", NULL, VERB_ANY, 1, VERB_DEFAULT, +VERB_DEFAULT_NOARG(verb_show_log, "log", "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; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index c70d50634676a..763acfc258656 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -253,7 +253,7 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } -VERB(verb_list_images, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, +VERB_DEFAULT_NOARG(verb_list_images, "list", "List available portable service images (default)"); 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; diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 4fb0486f7567d..e071cc5d89aa9 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -60,6 +60,8 @@ typedef struct { VERB_SCOPE(scope, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) #define VERB_NOARG(d, v, h) \ VERB_SCOPE_NOARG(static, d, v, h) +#define VERB_DEFAULT_NOARG(d, v, h) \ + VERB_SCOPE(static, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ VERB_DEFAULT, 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. */ diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 40d39b7654d2c..ff00196cc654d 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -2305,7 +2305,7 @@ static int merge(ImageClass image_class, return 1; } -VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current merge status (default)"); +VERB_DEFAULT_NOARG(verb_status, "status", "Show current merge status (default)"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, ret = 0; diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index c35b090035eac..7607b1ac4b1fe 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; } -VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current time settings"); +VERB_DEFAULT_NOARG(verb_status, "status", "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[] = { From 3b37439afb88cfe55e111ad1a5421bd57405b3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 19:19:15 +0200 Subject: [PATCH 1779/2155] machinectl: convert to OPTION and VERB macros For the shell verb we want switches specified after the program name to be passed to the program to execute, not processed by us. Mirror the approach in 'userdbctl ssh-authorized-keys': start with OPTION_PARSER_RETURN_POSITIONAL_ARGS, then lates switch to STOP_AT_FIRST_NONOPTION for "shell" or NORMAL otherwise. VERB declarations are placed directly above each function; functions that dispatch multiple verb names get stacked VERB() declarations. chainload_importctl() now takes the args strv instead of relying on the global optind. --help output is mostly the same. --no-pager/--no-legend/--no-ask-password/-q/--quiet are now at the end. bind-volume/unbind-volume are documented. Fixes c9f461a8067996c6b0c3ac3bf6f9097aedbf4734. Co-developed-by: Claude Opus 4.7 (1M context) --- src/machine/machinectl.c | 539 ++++++++++---------------- test/units/TEST-13-NSPAWN.machined.sh | 1 + 2 files changed, 215 insertions(+), 325 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 657c69e2a17d6..c0f9fd51e07c5 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -33,6 +32,7 @@ #include "format-ifname.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "hostname-util.h" #include "import-util.h" #include "in-addr-util.h" @@ -44,6 +44,7 @@ #include "machine-util.h" #include "main-func.h" #include "nulstr-util.h" +#include "options.h" #include "osc-context.h" #include "output-mode.h" #include "pager.h" @@ -284,6 +285,9 @@ static int show_table(Table *table, const char *word) { return 0; } +VERB_GROUP("Machine Commands"); + +VERB_DEFAULT_NOARG(verb_list_machines, "list", "List running VMs and containers"); 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; @@ -691,6 +695,10 @@ static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line return r; } +VERB(verb_show_machine, "status", "NAME…", 2, VERB_ANY, 0, + "Show VM/container details"); +VERB(verb_show_machine, "show", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of one or more VMs/containers"); 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; @@ -767,6 +775,8 @@ static int image_exists(sd_bus *bus, const char *name) { return 1; } +VERB(verb_start_machine, "start", "NAME…", 2, VERB_ANY, 0, + "Start container as a service"); 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; @@ -934,6 +944,8 @@ static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *r return 0; } +VERB(verb_login_machine, "login", "[NAME]", VERB_ANY, 2, 0, + "Get a login prompt in a container or on the local host"); 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; @@ -985,6 +997,8 @@ static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *use return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } +VERB(verb_shell_machine, "shell", "[[USER@]NAME [COMMAND…]]", VERB_ANY, VERB_ANY, 0, + "Invoke a shell (or other command) in a container or on the local host"); 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; @@ -1075,6 +1089,10 @@ static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *use static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata); +VERB(verb_enable_machine, "enable", "NAME…", 2, VERB_ANY, 0, + "Enable automatic container start at boot"); +VERB(verb_enable_machine, "disable", "NAME…", 2, VERB_ANY, 0, + "Disable automatic container start at boot"); 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; @@ -1242,6 +1260,10 @@ static int verb_machine_control_one(const char *machine_name, const char *method return 0; } +VERB(verb_poweroff_machine, "poweroff", "NAME…", 2, VERB_ANY, 0, + "Power off one or more machines"); +VERB(verb_poweroff_machine, "stop", "NAME…", 2, VERB_ANY, 0, + /* help= */ NULL); /* Convenience alias */ static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1267,6 +1289,10 @@ static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *u return 0; } +VERB(verb_reboot_machine, "reboot", "NAME…", 2, VERB_ANY, 0, + "Reboot one or more machines"); +VERB(verb_reboot_machine, "restart", "NAME…", 2, VERB_ANY, 0, + /* help= */ NULL); /* Convenience alias */ static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1305,14 +1331,20 @@ static int verb_vm_control(int argc, char *argv[], const char *method) { return 0; } +VERB(verb_pause, "pause", "NAME…", 2, VERB_ANY, 0, + "Pause one or more machines"); static int verb_pause(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Pause"); } +VERB(verb_resume, "resume", "NAME…", 2, VERB_ANY, 0, + "Resume one or more paused machines"); static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume"); } +VERB(verb_terminate_machine, "terminate", "NAME…", 2, VERB_ANY, 0, + "Terminate one or more machines"); static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1337,6 +1369,8 @@ static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB(verb_kill_machine, "kill", "NAME…", 2, VERB_ANY, 0, + "Send signal to processes of a machine"); 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); @@ -1369,6 +1403,10 @@ static const char* select_copy_method(bool copy_from, bool force) { return copy_from ? "CopyFromMachine" : "CopyToMachine"; } +VERB(verb_copy_files, "copy-to", "NAME PATH [PATH]", 3, 4, 0, + "Copy files from the host to a container"); +VERB(verb_copy_files, "copy-from", "NAME PATH [PATH]", 3, 4, 0, + "Copy files from a container to the host"); 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; @@ -1424,6 +1462,8 @@ static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userda return 0; } +VERB(verb_bind_mount, "bind", "NAME PATH [PATH]", 3, 4, 0, + "Bind mount a path from the host into a container"); 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); @@ -1449,6 +1489,10 @@ static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userda return 0; } +VERB_GROUP("Image Commands"); + +VERB(verb_list_images, "list-images", /* argspec= */ NULL, VERB_ANY, 1, 0, + "Show available container and VM 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; @@ -1757,6 +1801,10 @@ static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) return r; } +VERB(verb_show_image, "image-status", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show image details"); +VERB(verb_show_image, "show-image", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of image"); 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; @@ -1841,6 +1889,8 @@ static int get_settings_path(const char *name, char **ret_path) { return -ENOENT; } +VERB(verb_edit_settings, "edit", "NAME|FILE…", 2, VERB_ANY, 0, + "Edit settings of one or more VMs/containers"); static int verb_edit_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = {}; int r; @@ -1911,6 +1961,8 @@ static int verb_edit_settings(int argc, char *argv[], uintptr_t _data, void *use return do_edit_files_and_install(&context); } +VERB(verb_cat_settings, "cat", "NAME|FILE…", 2, VERB_ANY, 0, + "Show settings of one or more VMs/containers"); static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; @@ -1962,6 +2014,8 @@ static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *user return r; } +VERB(verb_clone_image, "clone", "NAME NAME", 3, 3, 0, + "Clone an image"); 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; @@ -1986,6 +2040,8 @@ static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_rename_image, "rename", "NAME NAME", 3, 3, 0, + "Rename an image"); 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); @@ -2006,6 +2062,8 @@ static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_read_only_image, "read-only", "NAME [BOOL]", 2, 3, 0, + "Mark or unmark image read-only"); 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); @@ -2028,6 +2086,8 @@ static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_remove_image, "remove", "NAME…", 2, VERB_ANY, 0, + "Remove an image"); static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -2055,6 +2115,8 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_set_limit, "set-limit", "[NAME] BYTES", 2, 3, 0, + "Set image or pool size limit (disk quota)"); 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; @@ -2085,6 +2147,8 @@ static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB(verb_clean_images, "clean", /* argspec= */ NULL, VERB_ANY, 1, 0, + "Remove hidden (or all) images"); 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; @@ -2139,6 +2203,8 @@ static int verb_clean_images(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_bind_volume, "bind-volume", "MACHINE PROVIDER:VOLUME[:…]", 3, 3, 0, + "Attach a volume to a running machine"); static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2209,6 +2275,8 @@ static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_unbind_volume, "unbind-volume", "MACHINE PROVIDER:VOLUME", 3, 3, 0, + "Detach a volume from a running machine"); static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2253,11 +2321,12 @@ static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *use return 0; } -static int chainload_importctl(int argc, char *argv[]) { +static int chainload_importctl(char **args) { int r; if (!arg_quiet) - log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. Redirecting invocation.", argv[optind]); + log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. " + "Redirecting invocation.", args[0]); _cleanup_strv_free_ char **c = strv_new("importctl", "--class=machine"); @@ -2288,7 +2357,7 @@ static int chainload_importctl(int argc, char *argv[]) { if (strv_extend_many(&c, "--format", arg_format) < 0) return log_oom(); - if (strv_extend_strv(&c, argv + optind, /* filter_duplicates= */ false) < 0) + if (strv_extend_strv(&c, args, /* filter_duplicates= */ false) < 0) return log_oom(); if (DEBUG_LOGGING) { @@ -2301,465 +2370,285 @@ static int chainload_importctl(int argc, char *argv[]) { } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char* const vgroups[] = { + "Machine Commands", + "Image Commands", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; - pager_open(arg_pager_flags); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } - r = terminal_urlify_man("machinectl", "1", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sSend control commands to or query the virtual machine and container%6$s\n" - "%5$sregistration manager.%6$s\n" - "\n%3$sMachine Commands:%4$s\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container details\n" - " show [NAME...] Show properties of one or more VMs/containers\n" - " start NAME... Start container as a service\n" - " login [NAME] Get a login prompt in a container or on the\n" - " local host\n" - " shell [[USER@]NAME [COMMAND...]]\n" - " Invoke a shell (or other command) in a container\n" - " 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 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" - "\n%3$sImage Commands:%4$s\n" - " list-images Show available container and VM images\n" - " image-status [NAME...] Show image details\n" - " show-image [NAME...] Show properties of image\n" - " edit NAME|FILE... Edit settings of one or more VMs/containers\n" - " cat NAME|FILE... Show settings of one or more VMs/containers\n" - " clone NAME NAME Clone an image\n" - " rename NAME NAME Rename an image\n" - " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" - " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n" - " clean Remove hidden (or all) 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" - " -p --property=NAME Show only properties by this name\n" - " --value When showing properties, only print the value\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -q --quiet Suppress output\n" - " -a --all Show all properties, including empty ones\n" - " -l --full Do not ellipsize output\n" - " --kill-whom=WHOM Whom to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --uid=USER Specify user ID to invoke shell as\n" - " -E --setenv=VAR[=VALUE] Add an environment variable for shell\n" - " --read-only Create read-only bind mount or clone\n" - " --mkdir Create directory before bind mounting, if missing\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --max-addresses=INTEGER Number of internet addresses to show at most\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " verbose, export, with-unit)\n" - " --force Replace target file when copying, if necessary\n" - " --now Start or power off container after enabling or\n" - " disabling it\n" - " --runner=RUNNER Select between nspawn and vmspawn as the runner\n" - " -V Short for --runner=vmspawn\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), + assert_cc(ELEMENTSOF(vtables) == 2); + (void) table_sync_column_widths(0, options, vtables[0], vtables[1]); + + pager_open(arg_pager_flags); + + help_cmdline("[OPTIONS…] COMMAND …"); + + printf("\n%1$s%2$sSend control commands to or query the virtual machine and container%3$s\n" + "%1$s%2$sregistration manager.%3$s\n", ansi_highlight(), + ansi_add_italics(), ansi_normal()); - return 0; -} + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + help_section("Options"); + 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_NO_LEGEND, - ARG_VALUE, - ARG_KILL_WHOM, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_NO_ASK_PASSWORD, - ARG_VERIFY, - ARG_RUNNER, - ARG_NOW, - ARG_FORCE, - ARG_FORMAT, - ARG_UID, - ARG_MAX_ADDRESSES, - ARG_SYSTEM, - ARG_USER, - }; + help_man_page_reference("machinectl", "1"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "property", required_argument, NULL, 'p' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "quiet", no_argument, NULL, 'q' }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "runner", required_argument, NULL, ARG_RUNNER }, - { "now", no_argument, NULL, ARG_NOW }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "uid", required_argument, NULL, ARG_UID }, - { "setenv", required_argument, NULL, 'E' }, - { "max-addresses", required_argument, NULL, ARG_MAX_ADDRESSES }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; + return 0; +} - bool reorder = false; - int c, r, shell = -1; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + /* For "shell" we don't want option reordering; options specified after the command should be passed + * to the program to execute, and not processed by us. For other verbs, we consume all options as + * usual. To make this work, start with OPTION_PARSER_RETURN_POSITIONAL_ARGS and switch to either + * OPTION_PARSER_STOP_AT_FIRST_NONOPTION or OPTION_PARSER_NORMAL after we've seen the verb and one + * more argument after that. */ + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + _cleanup_strv_free_ char **args = NULL; + 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; - - for (;;) { - static const char option_string[] = "-hp:P:als:H:M:qn:o:E:V"; - - c = getopt_long(argc, argv, option_string + reorder, options, NULL); - if (c < 0) - break; - + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a - * non-option argument was discovered. */ - - assert(!reorder); - - /* We generally are fine with the fact that getopt_long() reorders the command line, and looks - * for switches after the main verb. However, for "shell" we really don't want that, since we - * want that switches specified after the machine name are passed to the program to execute, - * and not processed by us. To make this possible, we'll first invoke getopt_long() with - * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first - * non-option parameter. If it's the verb "shell" we remember its position and continue - * processing options. In this case, as soon as we hit the next non-option argument we found - * the machine name, and stop further processing. If the first non-option argument is any other - * verb than "shell" we switch to normal reordering mode and continue processing arguments - * normally. */ - - if (shell >= 0) { - /* If we already found the "shell" verb on the command line, and now found the next - * non-option argument, then this is the machine name and we should stop processing - * further arguments. */ - optind--; /* don't process this argument, go one step back */ - goto done; - } - if (streq(optarg, "shell")) - /* Remember the position of the "shell" verb, and continue processing normally. */ - shell = optind - 1; - else { - int saved_optind; - - /* OK, this is some other verb. In this case, turn on reordering again, and continue - * processing normally. */ - reorder = true; - - /* We changed the option string. getopt_long() only looks at it again if we invoke it - * at least once with a reset option index. Hence, let's reset the option index here, - * then invoke getopt_long() again (ignoring what it has to say, after all we most - * likely already processed it), and the bump the option index so that we read the - * intended argument again. */ - saved_optind = optind; - optind = 0; - (void) getopt_long(argc, argv, option_string + reorder, options, NULL); - optind = saved_optind - 1; /* go one step back, process this argument again */ - } + OPTION_POSITIONAL: + assert(opts.mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + if (!args && !streq(opts.arg, "shell")) + opts.mode = OPTION_PARSER_NORMAL; + else if (args) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; + r = strv_extend(&args, opts.arg); + if (r < 0) + return log_oom(); break; - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': + OPTION_COMMON_MACHINE: arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; + arg_host = opts.arg; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system machine manager"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user machine manager"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'p': - case 'P': - r = strv_extend(&arg_property, optarg); + OPTION_LONG("value", NULL, "When showing properties, only print the value"): + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; + + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = strv_extend(&arg_property, opts.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') - break; - _fallthrough_; + if (opts.opt->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - case ARG_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); arg_all = true; break; - case 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case 's': - r = parse_signal_argument(optarg, &arg_signal); + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); if (r <= 0) return r; break; - case ARG_UID: - arg_uid = optarg; + OPTION_LONG("uid", "USER", "Specify user ID to invoke shell as"): + arg_uid = opts.arg; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + OPTION('E', "setenv", "VAR[=VALUE]", "Add an environment variable for shell"): + r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.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", opts.arg); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create read-only bind mount or clone"): arg_read_only = true; break; - case ARG_MKDIR: + OPTION_LONG("mkdir", NULL, "Create directory before bind mounting, if missing"): arg_mkdir = true; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + "Failed to parse lines '%s'", opts.arg); break; - case ARG_MAX_ADDRESSES: - if (streq(optarg, "all")) + OPTION_LONG("max-addresses", "INTEGER", + "Number of internet addresses to show at most"): + if (streq(opts.arg, "all")) arg_max_addresses = UINT_MAX; - else if (safe_atou(optarg, &arg_max_addresses) < 0) + else if (safe_atou(opts.arg, &arg_max_addresses) < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid number of addresses: %s", optarg); + "Invalid number of addresses: %s", opts.arg); break; - case 'o': - if (streq(optarg, "help")) + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, " + "short-iso-precise, short-full, short-monotonic, short-unix, short-delta, " + "json, json-pretty, json-sse, json-seq, cat, verbose, export, with-unit)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - r = output_mode_from_string(optarg); + r = output_mode_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Unknown output '%s'.", optarg); + return log_error_errno(r, "Unknown output '%s'.", opts.arg); arg_output = r; if (OUTPUT_MODE_IS_JSON(arg_output)) arg_legend = false; break; - case ARG_FORCE: + OPTION_LONG("force", NULL, "Replace target file when copying, if necessary"): arg_force = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Start or power off container after enabling or disabling it"): arg_now = true; break; - case ARG_RUNNER: - r = machine_runner_from_string(optarg); + OPTION_LONG("runner", "RUNNER", + "Select between nspawn and vmspawn as the runner"): + r = machine_runner_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --runner= setting: %s", optarg); + return log_error_errno(r, "Failed to parse --runner= setting: %s", opts.arg); arg_runner = r; break; - case 'V': + OPTION_SHORT('V', NULL, "Short for --runner=vmspawn"): arg_runner = RUNNER_VMSPAWN; break; - case ARG_VERIFY: - if (streq(optarg, "help")) + /* Hidden options below */ + + OPTION_LONG("verify", "MODE", /* help= */ NULL): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - r = import_verify_from_string(optarg); + r = import_verify_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); + return log_error_errno(r, "Failed to parse --verify= setting: %s", opts.arg); arg_verify = r; break; - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) + OPTION_LONG("format", "FORMAT", /* help= */ NULL): + if (!STR_IN_SET(opts.arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + "Unknown format: %s", opts.arg); - arg_format = optarg; + arg_format = opts.arg; 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_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } - -done: - if (shell >= 0) { - char *t; - - /* We found the "shell" verb while processing the argument list. Since we turned off reordering of the - * argument list initially let's readjust it now, and move the "shell" verb to the back. */ - - optind -= 1; /* place the option index where the "shell" verb will be placed */ - t = argv[shell]; - for (int i = shell; i < optind; i++) - argv[i] = argv[i+1]; - argv[optind] = t; - } + /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */ + if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0) + return log_oom(); + *ret_args = TAKE_PTR(args); return 1; } -static int machinectl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "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 }, - { "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 }, - { "bind-volume", 3, 3, 0, verb_bind_volume }, - { "unbind-volume", 3, 3, 0, verb_unbind_volume }, - { "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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_strv_free_ 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; journal_browse_prepare(); - if (STRPTR_IN_SET(argv[optind], - "import-tar", "import-raw", "import-fs", - "export-tar", "export-raw", - "pull-tar", "pull-raw", - "list-transfers", "cancel-transfer")) - return chainload_importctl(argc, argv); + if (args && STR_IN_SET(args[0], + "import-tar", "import-raw", "import-fs", + "export-tar", "export-raw", + "pull-tar", "pull-raw", + "list-transfers", "cancel-transfer")) + return chainload_importctl(args); r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus); if (r < 0) @@ -2767,7 +2656,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return machinectl_main(argc, argv, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index de51daa24c73d..6b07137ef811f 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -120,6 +120,7 @@ machinectl disable long-running long-running long-running container1 [[ "$(TERM=dumb machinectl shell testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "" ]] [[ "$(TERM=dumb machinectl shell --setenv=FOO=bar testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] +[[ "$(TERM=dumb machinectl shell testuser@ --setenv=FOO=bar /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] [[ "$(machinectl show --property=State --value long-running)" == "running" ]] # Equivalent to machinectl kill --signal=SIGRTMIN+4 --kill-whom=leader From c5bcbb4c85b35aea1e1647a56e434ede4f21218b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:32:41 +0900 Subject: [PATCH 1780/2155] network: drop unmet conditions Now sd_dhcp_lease object always wraps sd_dhcp_message object, hence we can unconditionally use it. --- src/network/networkd-json.c | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 35c32a0d18b9a..dcf74d4b8b0a0 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -6,7 +6,7 @@ #include "sd-dhcp-client.h" #include "sd-dhcp6-client.h" -#include "dhcp-lease-internal.h" +#include "dhcp-lease-internal.h" /* IWYU pragma: keep */ #include "dhcp-server-lease-internal.h" #include "dhcp6-lease-internal.h" #include "extract-word.h" @@ -1354,11 +1354,9 @@ static int dhcp_client_lease_append_json(Link *link, sd_json_variant **v) { return r; _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; - if (link->dhcp_lease->message) { - r = dhcp_message_build_json(link->dhcp_lease->message, &m); - if (r < 0) - return r; - } + r = dhcp_message_build_json(link->dhcp_lease->message, &m); + if (r < 0) + return r; r = sd_json_buildo( &w, @@ -1428,19 +1426,6 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * if (!link->dhcp_lease) return 0; - if (!link->dhcp_lease->message) { - 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 r; - } - - return json_variant_set_field_non_null(v, "PrivateOptions", array); - } - for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) { _cleanup_(iovec_done) struct iovec iov = {}; r = dhcp_message_get_option_alloc(link->dhcp_lease->message, i, &iov); From 7f2c6185fe0244b3ae49e5dfee09d94b282971c3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:34:19 +0900 Subject: [PATCH 1781/2155] networkctl: always use DHCP message serialized in JSON desciption Now the interface description by networkd always contains DHCP message (of course when there is a bound DHCP lease). Let's use it. --- src/network/networkctl-dump-util.c | 5 ----- src/network/networkctl-dump-util.h | 2 +- src/network/networkctl-status-link.c | 21 +-------------------- src/network/networkctl-status-system.c | 2 +- 4 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/network/networkctl-dump-util.c b/src/network/networkctl-dump-util.c index 15fdb103b6d76..e6fbc45d9a923 100644 --- a/src/network/networkctl-dump-util.c +++ b/src/network/networkctl-dump-util.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-dhcp-lease.h" #include "sd-hwdb.h" #include "sd-netlink.h" @@ -212,7 +211,6 @@ int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) { int dump_addresses( sd_netlink *rtnl, sd_dhcp_message *message, - sd_dhcp_lease *lease, Table *table, int ifindex) { @@ -233,9 +231,6 @@ int dump_addresses( if (dhcp_message_get_option_address(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &server_address) < 0) /* The message should be BOOTP, let's fallback to the siaddr field. */ server_address.s_addr = message->header.siaddr; - } else if (lease) { - (void) sd_dhcp_lease_get_address(lease, &dhcp4_address); - (void) sd_dhcp_lease_get_server_identifier(lease, &server_address); } FOREACH_ARRAY(local, local_addrs, n) { diff --git a/src/network/networkctl-dump-util.h b/src/network/networkctl-dump-util.h index 2aafaae475670..a4c02f0390625 100644 --- a/src/network/networkctl-dump-util.h +++ b/src/network/networkctl-dump-util.h @@ -8,4 +8,4 @@ int dump_list(Table *table, const char *key, char * const *l); int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret); int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex); -int dump_addresses(sd_netlink *rtnl, sd_dhcp_message *message, sd_dhcp_lease *lease, Table *table, int ifindex); +int dump_addresses(sd_netlink *rtnl, sd_dhcp_message *message, Table *table, int ifindex); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 9543953df496d..dc765a74f1216 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -2,7 +2,6 @@ #include "sd-device.h" #include "sd-dhcp-client-id.h" -#include "sd-dhcp-lease.h" #include "sd-dhcp-protocol.h" #include "sd-hwdb.h" #include "sd-netlink.h" @@ -24,7 +23,6 @@ #include "json-util.h" #include "macvlan-util.h" #include "netif-util.h" -#include "network-internal.h" #include "networkctl.h" #include "networkctl-description.h" #include "networkctl-dump-util.h" @@ -242,7 +240,6 @@ static int link_status_one( const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL, *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup, *on_color_online; _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL; - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -294,11 +291,6 @@ static int link_status_one( if (r == -ENOMEM) return log_oom(); - char lease_file[STRLEN("/run/systemd/netif/leases/") + DECIMAL_STR_MAX(int)]; - xsprintf(lease_file, "/run/systemd/netif/leases/%i", info->ifindex); - - (void) dhcp_lease_load(&lease, lease_file); - r = format_config_files(&network_dropins, network); if (r < 0) return r; @@ -780,7 +772,7 @@ static int link_status_one( return r; } - r = dump_addresses(rtnl, info->dhcp_message, lease, table, info->ifindex); + r = dump_addresses(rtnl, info->dhcp_message, table, info->ifindex); if (r < 0) return r; @@ -849,17 +841,6 @@ static int link_status_one( if (r < 0) return table_log_add_error(r); } - } else if (lease) { - const char *tz; - - r = sd_dhcp_lease_get_timezone(lease, &tz); - if (r >= 0) { - r = table_add_many(table, - TABLE_FIELD, "Time Zone", - TABLE_STRING, tz); - if (r < 0) - return table_log_add_error(r); - } } if (sd_dhcp_client_id_is_set(&info->dhcp_client_id)) { diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index 78dd77819dac2..35de930423a45 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -94,7 +94,7 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { if (r < 0) return table_log_add_error(r); - r = dump_addresses(rtnl, /* message= */ NULL, /* lease= */ NULL, table, /* ifindex= */ 0); + r = dump_addresses(rtnl, /* message= */ NULL, table, /* ifindex= */ 0); if (r < 0) return r; From ad350e6ecf2061711e5c17b7c0391b872ad0a4e7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:36:30 +0900 Subject: [PATCH 1782/2155] network: drop serialization of DHCP lease No one use it now anymore. Let's drop it. --- src/network/networkd-link.c | 7 +------ src/network/networkd-link.h | 1 - src/network/networkd-state-file.c | 9 --------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index dcd081039bf55..33275538364db 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -288,7 +288,6 @@ static Link* link_free(Link *link) { free(link->previous_ssid); free(link->driver); - unlink_and_free(link->lease_file); unlink_and_free(link->state_file); sd_device_unref(link->dev); @@ -2713,7 +2712,7 @@ static Link *link_drop_or_unref(Link *link) { DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_drop_or_unref); static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { - _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL; + _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL; _cleanup_(link_drop_or_unrefp) Link *link = NULL; unsigned short iftype; int r, ifindex; @@ -2751,9 +2750,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { /* Do not update state files when running in test mode. */ if (asprintf(&state_file, "/run/systemd/netif/links/%d", ifindex) < 0) return log_oom_debug(); - - if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", ifindex) < 0) - return log_oom_debug(); } link = new(Link, 1); @@ -2775,7 +2771,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, .state_file = TAKE_PTR(state_file), - .lease_file = TAKE_PTR(lease_file), .n_dns = UINT_MAX, .dns_default_route = -1, diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 33c2fd35a5c21..456ec99185624 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -117,7 +117,6 @@ typedef struct Link { sd_dhcp_client *dhcp_client; sd_dhcp_lease *dhcp_lease; - char *lease_file; unsigned dhcp4_messages; bool dhcp4_configured; char *dhcp4_6rd_tunnel_name; diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 7943314b06e3d..3c505f39e6c95 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -959,15 +959,6 @@ static int link_save(Link *link) { print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links); print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links); - if (link->dhcp_lease) { - r = dhcp_lease_save(link->dhcp_lease, link->lease_file); - if (r < 0) - return r; - - fprintf(f, "DHCP_LEASE=%s\n", link->lease_file); - } else - (void) unlink(link->lease_file); - r = link_serialize_dhcp6_client(link, f); if (r < 0) return r; From c63c262875f44295b5cf3056f33456fd07947ded Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:40:16 +0900 Subject: [PATCH 1783/2155] sd-dhcp-lease: drop unused dhcp_lease_save() and dhcp_lease_load() They are not used anymore. Let's kill them. --- src/libsystemd-network/network-internal.c | 113 ----- src/libsystemd-network/network-internal.h | 13 - src/libsystemd-network/sd-dhcp-lease.c | 481 ---------------------- src/network/test-network.c | 48 --- 4 files changed, 655 deletions(-) diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index cc29d9fd1b14e..e32b145370ee2 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -1,18 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-dhcp-lease.h" #include "alloc-util.h" -#include "dhcp-route.h" #include "dns-resolver-internal.h" #include "extract-word.h" -#include "hexdecoct.h" #include "in-addr-util.h" #include "network-internal.h" -#include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -216,112 +212,3 @@ int deserialize_dnr(sd_dns_resolver **ret, const char *string) { *ret = TAKE_PTR(dnr); return n; } - -void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { - assert(f); - assert(key); - assert(routes); - assert(size); - - fprintf(f, "%s=", key); - - for (size_t i = 0; i < size; i++) { - struct in_addr dest, gw; - uint8_t length; - - assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); - assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); - assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); - - fprintf(f, "%s,%s%s", - IN4_ADDR_PREFIX_TO_STRING(&dest, length), - IN4_ADDR_TO_STRING(&gw), - i < size - 1 ? " ": ""); - } - - fputs("\n", f); -} - -int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string) { - _cleanup_free_ struct sd_dhcp_route *routes = NULL; - size_t size = 0; - - assert(ret); - assert(ret_size); - assert(string); - - /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ - for (;;) { - _cleanup_free_ char *word = NULL; - char *tok, *tok_end; - unsigned n; - int r; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - struct sd_dhcp_route route = {}; - - tok = word; - - /* get the subnet */ - tok_end = strchr(tok, '/'); - if (!tok_end) - continue; - *tok_end = '\0'; - - r = inet_aton(tok, &route.dst_addr); - if (r == 0) - continue; - - tok = tok_end + 1; - - /* get the prefixlen */ - tok_end = strchr(tok, ','); - if (!tok_end) - continue; - - *tok_end = '\0'; - - r = safe_atou(tok, &n); - if (r < 0 || n > 32) - continue; - - route.dst_prefixlen = (uint8_t) n; - tok = tok_end + 1; - - /* get the gateway */ - r = inet_aton(tok, &route.gw_addr); - if (r == 0) - continue; - - if (!GREEDY_REALLOC(routes, size + 1)) - return -ENOMEM; - - routes[size++] = route; - } - - *ret_size = size; - *ret = TAKE_PTR(routes); - - return 0; -} - -int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) { - _cleanup_free_ char *hex_buf = NULL; - - assert(f); - assert(key); - assert(data); - - hex_buf = hexmem(data, size); - if (!hex_buf) - return -ENOMEM; - - fprintf(f, "%s=%s\n", key, hex_buf); - - return 0; -} diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 658247e350182..773f42c42de36 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -16,16 +16,3 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string); int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space); int deserialize_dnr(sd_dns_resolver **ret, const char *string); - -/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ -struct sd_dhcp_route; -struct sd_dhcp_lease; - -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route **routes, size_t size); -int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string); - -/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */ -int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); - -int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); -int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 9df1a4818dfec..d74dbe791e122 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -3,7 +3,6 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include #include #include "sd-dhcp-lease.h" @@ -16,24 +15,15 @@ #include "dns-def.h" #include "dns-domain.h" #include "dns-resolver-internal.h" -#include "env-file.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" #include "hostname-util.h" #include "in-addr-util.h" #include "ip-util.h" #include "network-common.h" -#include "network-internal.h" -#include "parse-util.h" #include "set.h" #include "sort-util.h" -#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "tmpfile-util.h" #include "unaligned.h" void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp) { @@ -1280,477 +1270,6 @@ int dhcp_lease_new(sd_dhcp_lease **ret) { return 0; } -int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct in_addr address; - const struct in_addr *addresses; - const void *data; - size_t data_len; - const char *string; - uint16_t mtu; - _cleanup_free_ sd_dhcp_route **routes = NULL; - char **search_domains; - usec_t t; - int r; - - assert(lease); - assert(lease_file); - - r = fopen_temporary(lease_file, &f, &temp_path); - if (r < 0) - return r; - - (void) fchmod(fileno(f), 0644); - - fprintf(f, - "# This is private data. Do not parse.\n"); - - r = sd_dhcp_lease_get_address(lease, &address); - if (r >= 0) - fprintf(f, "ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_netmask(lease, &address); - if (r >= 0) - fprintf(f, "NETMASK=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_router(lease, &addresses); - if (r > 0) { - fputs("ROUTER=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_server_identifier(lease, &address); - if (r >= 0) - fprintf(f, "SERVER_ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_next_server(lease, &address); - if (r >= 0) - fprintf(f, "NEXT_SERVER=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_broadcast(lease, &address); - if (r >= 0) - fprintf(f, "BROADCAST=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_mtu(lease, &mtu); - if (r >= 0) - fprintf(f, "MTU=%" PRIu16 "\n", mtu); - - r = sd_dhcp_lease_get_t1(lease, &t); - if (r >= 0) - fprintf(f, "T1=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_t2(lease, &t); - if (r >= 0) - fprintf(f, "T2=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_lifetime(lease, &t); - if (r >= 0) - fprintf(f, "LIFETIME=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_dns(lease, &addresses); - if (r > 0) { - fputs("DNS=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - sd_dns_resolver *resolvers; - r = sd_dhcp_lease_get_dnr(lease, &resolvers); - if (r > 0) { - fputs("DNR=", f); - serialize_dnr(f, resolvers, r, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_ntp(lease, &addresses); - if (r > 0) { - fputs("NTP=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_sip(lease, &addresses); - if (r > 0) { - fputs("SIP=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_domainname(lease, &string); - if (r >= 0) - fprintf(f, "DOMAINNAME=%s\n", string); - - r = sd_dhcp_lease_get_search_domains(lease, &search_domains); - if (r > 0) { - fputs("DOMAIN_SEARCH_LIST=", f); - fputstrv(f, search_domains, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_hostname(lease, &string); - if (r >= 0) - fprintf(f, "HOSTNAME=%s\n", string); - - r = sd_dhcp_lease_get_root_path(lease, &string); - if (r >= 0) - fprintf(f, "ROOT_PATH=%s\n", string); - - r = sd_dhcp_lease_get_static_routes(lease, &routes); - if (r > 0) - serialize_dhcp_routes(f, "STATIC_ROUTES", routes, r); - - routes = mfree(routes); - r = sd_dhcp_lease_get_classless_routes(lease, &routes); - if (r > 0) - serialize_dhcp_routes(f, "CLASSLESS_ROUTES", routes, r); - - r = sd_dhcp_lease_get_timezone(lease, &string); - if (r >= 0) - fprintf(f, "TIMEZONE=%s\n", string); - - if (sd_dhcp_client_id_is_set(&lease->client_id)) { - _cleanup_free_ char *client_id_hex = NULL; - - client_id_hex = hexmem(lease->client_id.raw, lease->client_id.size); - if (!client_id_hex) - return -ENOMEM; - fprintf(f, "CLIENTID=%s\n", client_id_hex); - } - - r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); - if (r >= 0) { - _cleanup_free_ char *option_hex = NULL; - - option_hex = hexmem(data, data_len); - if (!option_hex) - return -ENOMEM; - fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); - } - - LIST_FOREACH(options, option, lease->private_options) { - char key[STRLEN("OPTION_000")+1]; - - xsprintf(key, "OPTION_%" PRIu8, option->tag); - r = serialize_dhcp_option(f, key, option->data, option->length); - if (r < 0) - return r; - } - - r = fflush_and_check(f); - if (r < 0) - return r; - - r = conservative_rename(temp_path, lease_file); - if (r < 0) - return r; - - temp_path = mfree(temp_path); - - return 0; -} - -static char **private_options_free(char **options) { - if (!options) - return NULL; - - free_many_charp(options, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1); - - return mfree(options); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, private_options_free); - -int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - _cleanup_free_ char - *address = NULL, - *router = NULL, - *netmask = NULL, - *server_address = NULL, - *next_server = NULL, - *broadcast = NULL, - *dns = NULL, - *dnr = NULL, - *ntp = NULL, - *sip = NULL, - *pop3 = NULL, - *smtp = NULL, - *lpr = NULL, - *mtu = NULL, - *static_routes = NULL, - *classless_routes = NULL, - *domains = NULL, - *client_id_hex = NULL, - *vendor_specific_hex = NULL, - *lifetime = NULL, - *t1 = NULL, - *t2 = NULL; - _cleanup_(private_options_freep) char **options = NULL; - - int r, i; - - assert(lease_file); - assert(ret); - - r = dhcp_lease_new(&lease); - if (r < 0) - return r; - - options = new0(char*, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1); - if (!options) - return -ENOMEM; - - r = parse_env_file(NULL, lease_file, - "ADDRESS", &address, - "ROUTER", &router, - "NETMASK", &netmask, - "SERVER_ADDRESS", &server_address, - "NEXT_SERVER", &next_server, - "BROADCAST", &broadcast, - "DNS", &dns, - "DNR", &dnr, - "NTP", &ntp, - "SIP", &sip, - "POP3", &pop3, - "SMTP", &smtp, - "LPR", &lpr, - "MTU", &mtu, - "DOMAINNAME", &lease->domainname, - "HOSTNAME", &lease->hostname, - "DOMAIN_SEARCH_LIST", &domains, - "ROOT_PATH", &lease->root_path, - "STATIC_ROUTES", &static_routes, - "CLASSLESS_ROUTES", &classless_routes, - "CLIENTID", &client_id_hex, - "TIMEZONE", &lease->timezone, - "VENDOR_SPECIFIC", &vendor_specific_hex, - "LIFETIME", &lifetime, - "T1", &t1, - "T2", &t2, - "OPTION_224", &options[0], - "OPTION_225", &options[1], - "OPTION_226", &options[2], - "OPTION_227", &options[3], - "OPTION_228", &options[4], - "OPTION_229", &options[5], - "OPTION_230", &options[6], - "OPTION_231", &options[7], - "OPTION_232", &options[8], - "OPTION_233", &options[9], - "OPTION_234", &options[10], - "OPTION_235", &options[11], - "OPTION_236", &options[12], - "OPTION_237", &options[13], - "OPTION_238", &options[14], - "OPTION_239", &options[15], - "OPTION_240", &options[16], - "OPTION_241", &options[17], - "OPTION_242", &options[18], - "OPTION_243", &options[19], - "OPTION_244", &options[20], - "OPTION_245", &options[21], - "OPTION_246", &options[22], - "OPTION_247", &options[23], - "OPTION_248", &options[24], - "OPTION_249", &options[25], - "OPTION_250", &options[26], - "OPTION_251", &options[27], - "OPTION_252", &options[28], - "OPTION_253", &options[29], - "OPTION_254", &options[30]); - if (r < 0) - return r; - - if (address) { - r = inet_pton(AF_INET, address, &lease->address); - if (r <= 0) - log_debug("Failed to parse address %s, ignoring.", address); - } - - if (router) { - r = deserialize_in_addrs(&lease->router, router); - if (r < 0) - log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router); - else - lease->router_size = r; - } - - if (netmask) { - r = inet_pton(AF_INET, netmask, &lease->subnet_mask); - if (r <= 0) - log_debug("Failed to parse netmask %s, ignoring.", netmask); - } - - if (server_address) { - r = inet_pton(AF_INET, server_address, &lease->server_address); - if (r <= 0) - log_debug("Failed to parse server address %s, ignoring.", server_address); - } - - if (next_server) { - r = inet_pton(AF_INET, next_server, &lease->next_server); - if (r <= 0) - log_debug("Failed to parse next server %s, ignoring.", next_server); - } - - if (broadcast) { - r = inet_pton(AF_INET, broadcast, &lease->broadcast); - if (r <= 0) - log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); - } - - if (dns) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns); - if (r < 0) - log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns); - else - lease->servers[SD_DHCP_LEASE_DNS].size = r; - } - - if (dnr) { - r = deserialize_dnr(&lease->dnr, dnr); - if (r < 0) - log_debug_errno(r, "Failed to deserialize DNR servers %s, ignoring: %m", dnr); - else - lease->n_dnr = r; - } - - if (ntp) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp); - if (r < 0) - log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); - else - lease->servers[SD_DHCP_LEASE_NTP].size = r; - } - - if (sip) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip); - if (r < 0) - log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip); - else - lease->servers[SD_DHCP_LEASE_SIP].size = r; - } - - if (pop3) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3); - if (r < 0) - log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3); - else - lease->servers[SD_DHCP_LEASE_POP3].size = r; - } - - if (smtp) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp); - if (r < 0) - log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp); - else - lease->servers[SD_DHCP_LEASE_SMTP].size = r; - } - - if (lpr) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr); - if (r < 0) - log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr); - else - lease->servers[SD_DHCP_LEASE_LPR].size = r; - } - - if (mtu) { - r = safe_atou16(mtu, &lease->mtu); - if (r < 0) - log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu); - } - - if (domains) { - _cleanup_strv_free_ char **a = NULL; - a = strv_split(domains, " "); - if (!a) - return -ENOMEM; - - if (!strv_isempty(a)) - lease->search_domains = TAKE_PTR(a); - } - - if (static_routes) { - r = deserialize_dhcp_routes( - &lease->static_routes, - &lease->n_static_routes, - static_routes); - if (r < 0) - log_debug_errno(r, "Failed to parse DHCP static routes %s, ignoring: %m", static_routes); - } - - if (classless_routes) { - r = deserialize_dhcp_routes( - &lease->classless_routes, - &lease->n_classless_routes, - classless_routes); - if (r < 0) - log_debug_errno(r, "Failed to parse DHCP classless routes %s, ignoring: %m", classless_routes); - } - - if (lifetime) { - r = parse_sec(lifetime, &lease->lifetime); - if (r < 0) - log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); - } - - if (t1) { - r = parse_sec(t1, &lease->t1); - if (r < 0) - log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1); - } - - if (t2) { - r = parse_sec(t2, &lease->t2); - if (r < 0) - log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2); - } - - if (client_id_hex) { - _cleanup_free_ void *data = NULL; - size_t data_size; - - r = unhexmem(client_id_hex, &data, &data_size); - if (r < 0) - log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); - - r = sd_dhcp_client_id_set_raw(&lease->client_id, data, data_size); - if (r < 0) - log_debug_errno(r, "Failed to assign client ID, ignoring: %m"); - } - - if (vendor_specific_hex) { - r = unhexmem(vendor_specific_hex, &lease->vendor_specific, &lease->vendor_specific_len); - if (r < 0) - log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); - } - - for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { - _cleanup_free_ void *data = NULL; - size_t len; - - if (!options[i]) - continue; - - r = unhexmem(options[i], &data, &len); - if (r < 0) { - log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); - continue; - } - - r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); - if (r < 0) - return r; - } - - *ret = TAKE_PTR(lease); - - return 0; -} - int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { struct in_addr address, mask; int r; diff --git a/src/network/test-network.c b/src/network/test-network.c index c78c50e3a7573..ee0e5d13b5700 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -1,9 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" -#include "dhcp-route.h" #include "hashmap.h" #include "hostname-setup.h" #include "network-internal.h" @@ -42,51 +39,6 @@ TEST(deserialize_in_addr) { ASSERT_TRUE(in6_addr_equal(&f.in6, &addresses6[2])); } -TEST(deserialize_dhcp_routes) { - _cleanup_free_ struct sd_dhcp_route *routes = NULL; - size_t size; - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "")); - ASSERT_EQ(size, 0U); - ASSERT_NULL(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/16,192.168.0.1 10.1.2.0/24,10.1.2.1 0.0.0.0/0,10.0.1.1")); - ASSERT_EQ(size, 3U); - ASSERT_NOT_NULL(routes); - - ASSERT_EQ(routes[0].dst_addr.s_addr, inet_addr("192.168.0.0")); - ASSERT_EQ(routes[0].gw_addr.s_addr, inet_addr("192.168.0.1")); - ASSERT_EQ(routes[0].dst_prefixlen, 16U); - - ASSERT_EQ(routes[1].dst_addr.s_addr, inet_addr("10.1.2.0")); - ASSERT_EQ(routes[1].gw_addr.s_addr, inet_addr("10.1.2.1")); - ASSERT_EQ(routes[1].dst_prefixlen, 24U); - - ASSERT_EQ(routes[2].dst_addr.s_addr, inet_addr("0.0.0.0")); - ASSERT_EQ(routes[2].gw_addr.s_addr, inet_addr("10.0.1.1")); - ASSERT_EQ(routes[2].dst_prefixlen, 0U); - - routes = mfree(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/16,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.1")); - ASSERT_EQ(size, 2U); - ASSERT_NOT_NULL(routes); - - ASSERT_EQ(routes[0].dst_addr.s_addr, inet_addr("192.168.0.0")); - ASSERT_EQ(routes[0].gw_addr.s_addr, inet_addr("192.168.0.1")); - ASSERT_EQ(routes[0].dst_prefixlen, 16U); - - ASSERT_EQ(routes[1].dst_addr.s_addr, inet_addr("0.0.0.0")); - ASSERT_EQ(routes[1].gw_addr.s_addr, inet_addr("10.0.1.1")); - ASSERT_EQ(routes[1].dst_prefixlen, 0U); - - routes = mfree(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/55,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.X")); - ASSERT_EQ(size, 0U); - ASSERT_NULL(routes); -} - static void test_route_tables_one(Manager *manager, const char *name, uint32_t number) { _cleanup_free_ char *str = NULL, *expected = NULL, *num_str = NULL; uint32_t t; From 4d1779a7c59f7958b30ab3739ac2475b2ed53567 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 1 Apr 2026 23:44:39 +0900 Subject: [PATCH 1784/2155] sd-dhcp-lease: drop several more unused functions and conditions --- src/libsystemd-network/dhcp-lease-internal.h | 29 +- src/libsystemd-network/meson.build | 3 - src/libsystemd-network/sd-dhcp-lease.c | 936 +------------------ src/libsystemd-network/test-sd-dhcp-lease.c | 82 -- src/systemd/sd-dhcp-lease.h | 9 +- 5 files changed, 7 insertions(+), 1052 deletions(-) delete mode 100644 src/libsystemd-network/test-sd-dhcp-lease.c diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index b26de88351a8e..c00b8a6cd574f 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -11,15 +11,6 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-option.h" -#include "list.h" - -struct sd_dhcp_raw_option { - LIST_FIELDS(struct sd_dhcp_raw_option, options); - - uint8_t tag; - uint8_t length; - void *data; -}; struct sd_dhcp_lease { unsigned n_ref; @@ -36,15 +27,12 @@ struct sd_dhcp_lease { /* each 0 if unset */ be32_t address; be32_t server_address; - be32_t next_server; be32_t subnet_mask; be32_t broadcast; struct in_addr *router; size_t router_size; - bool rapid_commit; - DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; sd_dns_resolver *dnr; @@ -59,16 +47,9 @@ struct sd_dhcp_lease { char *domainname; char **search_domains; - char *hostname; /* SD_DHCP_OPTION_HOST_NAME (12) */ - char *fqdn; /* SD_DHCP_OPTION_FQDN (81) */ - char *root_path; + char *hostname; char *captive_portal; - sd_dhcp_client_id client_id; - - void *vendor_specific; - size_t vendor_specific_len; - char *timezone; uint8_t sixrd_ipv4masklen; @@ -76,18 +57,10 @@ struct sd_dhcp_lease { struct in6_addr sixrd_prefix; struct in_addr *sixrd_br_addresses; size_t sixrd_n_br_addresses; - - LIST_HEAD(struct sd_dhcp_raw_option, private_options); }; int dhcp_lease_new(sd_dhcp_lease **ret); -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata); -int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains); -int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len); - void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp); -int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 74e09fac84cb1..fed2c98c710dd 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -122,9 +122,6 @@ executables += [ 'sources' : files('test-ndisc-send.c'), 'type' : 'manual', }, - network_test_template + { - 'sources' : files('test-sd-dhcp-lease.c'), - }, network_test_template + { 'sources' : files('test-tlv-util.c'), }, diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index d74dbe791e122..641ee8561b7fc 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -11,20 +11,14 @@ #include "dhcp-client-internal.h" #include "dhcp-lease-internal.h" #include "dhcp-option.h" -#include "dhcp-route.h" -#include "dns-def.h" -#include "dns-domain.h" +#include "dhcp-route.h" /* IWYU pragma: keep */ #include "dns-resolver-internal.h" -#include "hostname-util.h" #include "in-addr-util.h" #include "ip-util.h" -#include "network-common.h" #include "set.h" -#include "sort-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "unaligned.h" void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp) { assert(lease); @@ -168,15 +162,6 @@ int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) { return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr); } -int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr); -} -int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr); -} -int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr); -} int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { assert_return(lease, -EINVAL); @@ -189,30 +174,14 @@ int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) return 0; } -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { - assert_return(lease, -EINVAL); - assert_return(hostname, -EINVAL); - - /* FQDN option (81) always takes precedence. */ - - if (lease->fqdn) - *hostname = lease->fqdn; - else if (lease->hostname) - *hostname = lease->hostname; - else - return -ENODATA; - - return 0; -} - -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) { +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(root_path, -EINVAL); - if (!lease->root_path) + if (!lease->hostname) return -ENODATA; - *root_path = lease->root_path; + if (ret) + *ret = lease->hostname; return 0; } @@ -299,17 +268,6 @@ int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ad return 0; } -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { - assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - - if (lease->next_server == 0) - return -ENODATA; - - addr->s_addr = lease->next_server; - return 0; -} - /* * The returned routes array must be freed by the caller. * Route objects have the same lifetime of the lease and must not be freed. @@ -394,36 +352,14 @@ int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease) { return lease && lease->sixrd_n_br_addresses > 0; } -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { - assert_return(lease, -EINVAL); - assert_return(data, -EINVAL); - assert_return(data_len, -EINVAL); - - if (lease->vendor_specific_len <= 0) - return -ENODATA; - - *data = lease->vendor_specific; - *data_len = lease->vendor_specific_len; - return 0; -} - static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { - struct sd_dhcp_raw_option *option; - assert(lease); sd_dhcp_message_unref(lease->message); - while ((option = LIST_POP(options, lease->private_options))) { - free(option->data); - free(option); - } - - free(lease->root_path); free(lease->router); free(lease->timezone); free(lease->hostname); - free(lease->fqdn); free(lease->domainname); free(lease->captive_portal); @@ -433,7 +369,6 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { dns_resolver_free_array(lease->dnr, lease->n_dnr); free(lease->static_routes); free(lease->classless_routes); - free(lease->vendor_specific); strv_free(lease->search_domains); free(lease->sixrd_br_addresses); return mfree(lease); @@ -441,820 +376,6 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free); -static int lease_parse_be32_seconds(const uint8_t *option, size_t len, bool max_as_infinity, usec_t *ret) { - assert(option); - assert(ret); - - if (len != 4) - return -EINVAL; - - *ret = unaligned_be32_sec_to_usec(option, max_as_infinity); - return 0; -} - -static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { - assert(option); - assert(ret); - - if (len != 2) - return -EINVAL; - - *ret = unaligned_read_be16((be16_t*) option); - if (*ret < min) - *ret = min; - - return 0; -} - -static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { - assert(option); - assert(ret); - - if (len != 4) - return -EINVAL; - - memcpy(ret, option, 4); - return 0; -} - -static int lease_parse_domain(const uint8_t *option, size_t len, char **domain) { - _cleanup_free_ char *name = NULL, *normalized = NULL; - int r; - - assert(option); - assert(domain); - - r = dhcp_option_parse_string(option, len, &name); - if (r < 0) - return r; - if (!name) { - *domain = mfree(*domain); - return 0; - } - - r = dns_name_normalize(name, 0, &normalized); - if (r < 0) - return r; - - if (is_localhost(normalized)) - return -EINVAL; - - if (dns_name_is_root(normalized)) - return -EINVAL; - - return free_and_replace(*domain, normalized); -} - -static int lease_parse_fqdn(const uint8_t *option, size_t len, char **fqdn) { - _cleanup_free_ char *name = NULL, *normalized = NULL; - int r; - - assert(option); - assert(fqdn); - - /* RFC 4702 Section 2 - * - * Byte 0: Flags (S: server should perform A RR updates, O: override existing A RR, - * E: encoding (0=ASCII, 1=Wire format), N: no server updates) - * Byte 1: RCODE1 (ignored on receipt) - * Byte 2: RCODE2 (ignored on receipt) - * Bytes 3+: Domain Name */ - - if (len <= 3) - return -EBADMSG; - - size_t data_len = len - 3; - const uint8_t *data = option + 3; - - /* In practice, many servers send DNS wire format regardless of the E flag, so ignore and try wire - * format first, then fall back to ASCII if that fails. */ - r = dns_name_from_wire_format(&data, &data_len, &name); - if (r < 0) { - if (FLAGS_SET(option[0], DHCP_FQDN_FLAG_E)) - return -EBADMSG; - - /* Wire format failed, try ASCII format */ - r = dhcp_option_parse_string(option + 3, len - 3, &name); - if (r < 0) - return r; - } - - if (!name) { - *fqdn = mfree(*fqdn); - return 0; - } - - r = dns_name_normalize(name, 0, &normalized); - if (r < 0) - return r; - - if (is_localhost(normalized)) - return -EINVAL; - - if (dns_name_is_root(normalized)) - return -EINVAL; - - return free_and_replace(*fqdn, normalized); -} - -static int lease_parse_captive_portal(const uint8_t *option, size_t len, char **uri) { - _cleanup_free_ char *s = NULL; - int r; - - assert(option); - assert(uri); - - r = dhcp_option_parse_string(option, len, &s); - if (r < 0) - return r; - if (s && !in_charset(s, URI_VALID)) - return -EINVAL; - - return free_and_replace(*uri, s); -} - -static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **addresses, size_t *n_addresses) { - assert(option || len == 0); - assert(addresses); - assert(n_addresses); - - if (len <= 0) { - *n_addresses = 0; - *addresses = mfree(*addresses); - return 0; - } - - if (len % 4 != 0) - return -EINVAL; - - size_t n = len / 4; - struct in_addr *a = newdup(struct in_addr, option, n); - if (!a) - return -ENOMEM; - - *n_addresses = n; - return free_and_replace(*addresses, a); -} - -static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **sips, size_t *n_sips) { - assert(option || len == 0); - assert(sips); - assert(n_sips); - - if (len <= 0) - return -EINVAL; - - /* The SIP record is like the other, regular server records, but prefixed with a single "encoding" - * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like - * the other fields */ - - if (option[0] != 1) { /* We only support IP address encoding for now */ - *sips = mfree(*sips); - *n_sips = 0; - return 0; - } - - return lease_parse_in_addrs(option + 1, len - 1, sips, n_sips); -} - -static int lease_parse_dns_name(const uint8_t *optval, size_t optlen, char **ret) { - _cleanup_free_ char *name = NULL; - int r; - - assert(optval); - assert(ret); - - r = dns_name_from_wire_format(&optval, &optlen, &name); - if (r < 0) - return r; - if (r == 0 || optlen != 0) - return -EBADMSG; - - *ret = TAKE_PTR(name); - return r; -} - -static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver **dnr, size_t *n_dnr) { - int r; - sd_dns_resolver *res_list = NULL; - size_t n_resolvers = 0; - CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_free_array); - - assert(option || len == 0); - assert(dnr); - assert(n_dnr); - - _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; - - size_t offset = 0; - while (offset < len) { - /* Instance Data length */ - if (offset + 2 > len) - return -EBADMSG; - size_t ilen = unaligned_read_be16(option + offset); - if (offset + ilen + 2 > len) - return -EBADMSG; - offset += 2; - size_t iend = offset + ilen; - - /* priority */ - if (offset + 2 > len) - return -EBADMSG; - res.priority = unaligned_read_be16(option + offset); - offset += 2; - - /* Authenticated Domain Name */ - if (offset + 1 > len) - return -EBADMSG; - ilen = option[offset++]; - if (offset + ilen > iend) - return -EBADMSG; - - r = lease_parse_dns_name(option + offset, ilen, &res.auth_name); - if (r < 0) - return r; - r = dns_name_is_valid_ldh(res.auth_name); - if (r < 0) - return r; - if (!r) - return -EBADMSG; - if (dns_name_is_root(res.auth_name)) - return -EBADMSG; - offset += ilen; - - /* RFC9463 § 3.1.6: In ADN-only mode, server omits everything after the ADN. - * We don't support these, but they are not invalid. */ - if (offset == iend) { - log_debug("Received ADN-only DNRv4 option, ignoring."); - sd_dns_resolver_done(&res); - continue; - } - - /* IPv4 addrs */ - if (offset + 1 > len) - return -EBADMSG; - ilen = option[offset++]; - if (offset + ilen > iend) - return -EBADMSG; - - size_t n_addrs; - _cleanup_free_ struct in_addr *addrs = NULL; - r = lease_parse_in_addrs(option + offset, ilen, &addrs, &n_addrs); - if (r < 0) - return r; - offset += ilen; - - /* RFC9463 § 3.1.8: option MUST include at least one valid IP addr */ - if (!n_addrs) - return -EBADMSG; - - res.addrs = new(union in_addr_union, n_addrs); - if (!res.addrs) - return -ENOMEM; - for (size_t i = 0; i < n_addrs; i++) { - union in_addr_union addr = {.in = addrs[i]}; - /* RFC9463 § 5.2 client MUST discard multicast and host loopback addresses */ - if (in_addr_is_multicast(AF_INET, &addr) || - in_addr_is_localhost(AF_INET, &addr)) - return -EBADMSG; - res.addrs[i] = addr; - } - res.n_addrs = n_addrs; - res.family = AF_INET; - - /* service params */ - r = dnr_parse_svc_params(option + offset, iend-offset, &res); - if (r < 0) - return r; - if (r == 0) { - /* We can't use this record, but it was not invalid. */ - log_debug("Received DNRv4 option with unsupported SvcParams, ignoring."); - sd_dns_resolver_done(&res); - continue; - } - offset = iend; - - /* Append the latest resolver */ - if (!GREEDY_REALLOC0(res_list, n_resolvers+1)) - return -ENOMEM; - - res_list[n_resolvers++] = TAKE_STRUCT(res); - } - - typesafe_qsort(res_list, n_resolvers, dns_resolver_prio_compare); - - dns_resolver_free_array(*dnr, *n_dnr); - *dnr = TAKE_PTR(res_list); - *n_dnr = n_resolvers; - - return n_resolvers; -} - -static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - int r; - - assert(lease); - assert(option || len <= 0); - - if (len % 8 != 0) - return -EINVAL; - - while (len >= 8) { - struct in_addr dst, gw; - uint8_t prefixlen; - - assert_se(lease_parse_be32(option, 4, &dst.s_addr) >= 0); - option += 4; - - assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0); - option += 4; - - len -= 8; - - r = in4_addr_default_prefixlen(&dst, &prefixlen); - if (r < 0) { - log_debug("sd-dhcp-lease: cannot determine class of received static route, ignoring."); - continue; - } - - (void) in4_addr_mask(&dst, prefixlen); - - if (!GREEDY_REALLOC(lease->static_routes, lease->n_static_routes + 1)) - return -ENOMEM; - - lease->static_routes[lease->n_static_routes++] = (struct sd_dhcp_route) { - .dst_addr = dst, - .gw_addr = gw, - .dst_prefixlen = prefixlen, - }; - } - - return 0; -} - -/* parses RFC3442 Classless Static Route Option */ -static int lease_parse_classless_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - assert(lease); - assert(option || len <= 0); - - /* option format: (subnet-mask-width significant-subnet-octets gateway-ip) */ - - while (len > 0) { - uint8_t prefixlen, dst_octets; - struct in_addr dst = {}, gw; - - prefixlen = *option; - option++; - len--; - - dst_octets = DIV_ROUND_UP(prefixlen, 8); - - /* can't have more than 4 octets in IPv4 */ - if (dst_octets > 4 || len < dst_octets) - return -EINVAL; - - memcpy(&dst, option, dst_octets); - option += dst_octets; - len -= dst_octets; - - if (len < 4) - return -EINVAL; - - assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0); - option += 4; - len -= 4; - - if (!GREEDY_REALLOC(lease->classless_routes, lease->n_classless_routes + 1)) - return -ENOMEM; - - lease->classless_routes[lease->n_classless_routes++] = (struct sd_dhcp_route) { - .dst_addr = dst, - .gw_addr = gw, - .dst_prefixlen = prefixlen, - }; - } - - return 0; -} - -static int lease_parse_6rd(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - uint8_t ipv4masklen, prefixlen; - struct in6_addr prefix; - _cleanup_free_ struct in_addr *br_addresses = NULL; - size_t n_br_addresses; - - assert(lease); - assert(option); - - /* See RFC 5969 Section 7.1.1 */ - - if (lease->sixrd_n_br_addresses > 0) - /* Multiple 6rd option?? */ - return -EINVAL; - - /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ - if (len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || - (len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) - return -EINVAL; - - /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses - * within a given 6rd domain. This may be any value between 0 and 32. Any value - * greater than 32 is invalid. */ - ipv4masklen = option[0]; - if (ipv4masklen > 32) - return -EINVAL; - - /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the - * purpose of bounds checking by DHCP option processing, the sum of - * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ - prefixlen = option[1]; - if (32 - ipv4masklen + prefixlen > 128) - return -EINVAL; - - /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. - * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and - * MUST be initialized to zero by the sender and ignored by the receiver. */ - memcpy(&prefix, option + 2, sizeof(struct in6_addr)); - (void) in6_addr_mask(&prefix, prefixlen); - - /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */ - n_br_addresses = (len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); - br_addresses = newdup(struct in_addr, option + 2 + sizeof(struct in6_addr), n_br_addresses); - if (!br_addresses) - return -ENOMEM; - - lease->sixrd_ipv4masklen = ipv4masklen; - lease->sixrd_prefixlen = prefixlen; - lease->sixrd_prefix = prefix; - lease->sixrd_br_addresses = TAKE_PTR(br_addresses); - lease->sixrd_n_br_addresses = n_br_addresses; - - return 0; -} - -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { - sd_dhcp_lease *lease = ASSERT_PTR(userdata); - int r; - - switch (code) { - - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->lifetime); - if (r < 0) - log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); - - break; - - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - r = lease_parse_be32(option, len, &lease->server_address); - if (r < 0) - log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); - - break; - - case SD_DHCP_OPTION_SUBNET_MASK: - r = lease_parse_be32(option, len, &lease->subnet_mask); - if (r < 0) - log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); - break; - - case SD_DHCP_OPTION_BROADCAST: - r = lease_parse_be32(option, len, &lease->broadcast); - if (r < 0) - log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); - break; - - case SD_DHCP_OPTION_ROUTER: - r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size); - if (r < 0) - log_debug_errno(r, "Failed to parse router addresses, ignoring: %m"); - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - if (len > 0) - log_debug("Invalid DHCP Rapid Commit option, ignoring."); - lease->rapid_commit = true; - break; - - case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size); - if (r < 0) - log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_NTP_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_SIP_SERVER: - r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse SIP server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_POP3_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size); - if (r < 0) - log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_SMTP_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_LPR_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size); - if (r < 0) - log_debug_errno(r, "Failed to parse LPR server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL: - r = lease_parse_captive_portal(option, len, &lease->captive_portal); - if (r < 0) - log_debug_errno(r, "Failed to parse captive portal, ignoring: %m"); - break; - - case SD_DHCP_OPTION_STATIC_ROUTE: - r = lease_parse_static_routes(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); - break; - - case SD_DHCP_OPTION_MTU_INTERFACE: - r = lease_parse_u16(option, len, &lease->mtu, 68); - if (r < 0) - log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); - if (lease->mtu < DHCP_MIN_PACKET_SIZE) { - log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_MIN_PACKET_SIZE); - lease->mtu = DHCP_MIN_PACKET_SIZE; - } - - break; - - case SD_DHCP_OPTION_DOMAIN_NAME: - r = lease_parse_domain(option, len, &lease->domainname); - if (r < 0) { - log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); - return 0; - } - - break; - - case SD_DHCP_OPTION_DOMAIN_SEARCH: - r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains); - if (r < 0) - log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m"); - break; - - case SD_DHCP_OPTION_HOST_NAME: - r = lease_parse_domain(option, len, &lease->hostname); - if (r < 0) { - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - return 0; - } - - break; - - case SD_DHCP_OPTION_FQDN: - r = lease_parse_fqdn(option, len, &lease->fqdn); - if (r < 0) { - log_debug_errno(r, "Failed to parse FQDN, ignoring: %m"); - return 0; - } - - break; - - case SD_DHCP_OPTION_ROOT_PATH: { - _cleanup_free_ char *p = NULL; - - r = dhcp_option_parse_string(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse root path, ignoring: %m"); - - free_and_replace(lease->root_path, p); - break; - } - case SD_DHCP_OPTION_RENEWAL_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->t1); - if (r < 0) - log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); - break; - - case SD_DHCP_OPTION_REBINDING_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->t2); - if (r < 0) - log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); - break; - - case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: - r = lease_parse_classless_routes(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); - break; - - case SD_DHCP_OPTION_TZDB_TIMEZONE: { - _cleanup_free_ char *tz = NULL; - - r = dhcp_option_parse_string(option, len, &tz); - if (r < 0) { - log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); - return 0; - } - - if (!timezone_is_valid(tz, LOG_DEBUG)) { - log_debug("Timezone is not valid, ignoring."); - return 0; - } - - free_and_replace(lease->timezone, tz); - - break; - } - - case SD_DHCP_OPTION_V4_DNR: - r = lease_parse_dnr(option, len, &lease->dnr, &lease->n_dnr); - if (r < 0) { - log_debug_errno(r, "Failed to parse network-designated resolvers, ignoring: %m"); - return 0; - } - - break; - - case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: - - if (len <= 0) - lease->vendor_specific = mfree(lease->vendor_specific); - else { - void *p; - - p = memdup(option, len); - if (!p) - return -ENOMEM; - - free_and_replace(lease->vendor_specific, p); - } - - lease->vendor_specific_len = len; - break; - - case SD_DHCP_OPTION_6RD: - r = lease_parse_6rd(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m"); - break; - - case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ false, &lease->ipv6_only_preferred_usec); - if (r < 0) - log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m"); - - else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC && - !network_test_mode_enabled()) - lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC; - break; - - case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: - r = dhcp_lease_insert_private_option(lease, code, option, len); - if (r < 0) - return r; - - break; - - default: - log_debug("Ignoring DHCP option %"PRIu8" while parsing.", code); - } - - return 0; -} - -/* Parses compressed domain names. */ -int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) { - _cleanup_strv_free_ char **names = NULL; - size_t pos = 0, cnt = 0; - int r; - - assert(domains); - assert(option || len == 0); - - if (len == 0) - return -EBADMSG; - - while (pos < len) { - _cleanup_free_ char *name = NULL; - size_t n = 0; - size_t jump_barrier = pos, next_chunk = 0; - bool first = true; - - for (;;) { - uint8_t c; - c = option[pos++]; - - if (c == 0) { - /* End of name */ - break; - } else if (c <= 63) { - const char *label; - - /* Literal label */ - label = (const char*) (option + pos); - pos += c; - if (pos >= len) - return -EBADMSG; - - if (!GREEDY_REALLOC(name, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (first) - first = false; - else - name[n++] = '.'; - - r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - } else if (FLAGS_SET(c, 0xc0)) { - /* Pointer */ - - uint8_t d; - uint16_t ptr; - - if (pos >= len) - return -EBADMSG; - - d = option[pos++]; - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - if (ptr >= jump_barrier) - return -EBADMSG; - jump_barrier = ptr; - - /* Save current location so we don't end up re-parsing what's parsed so far. */ - if (next_chunk == 0) - next_chunk = pos; - - pos = ptr; - } else - return -EBADMSG; - } - - if (!GREEDY_REALLOC(name, n + 1)) - return -ENOMEM; - name[n] = 0; - - r = strv_extend(&names, name); - if (r < 0) - return r; - - cnt++; - - if (next_chunk != 0) - pos = next_chunk; - } - - strv_free_and_replace(*domains, names); - - return cnt; -} - -int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) { - struct sd_dhcp_raw_option *option, *before = NULL; - - assert(lease); - - LIST_FOREACH(options, cur, lease->private_options) { - if (tag < cur->tag) { - before = cur; - break; - } - if (tag == cur->tag) { - log_debug("Ignoring duplicate option, tagged %i.", tag); - return 0; - } - } - - option = new(struct sd_dhcp_raw_option, 1); - if (!option) - return -ENOMEM; - - option->tag = tag; - option->length = len; - option->data = memdup(data, len); - if (!option->data) { - free(option); - return -ENOMEM; - } - - LIST_INSERT_BEFORE(options, lease->private_options, before, option); - return 0; -} - int dhcp_lease_new(sd_dhcp_lease **ret) { sd_dhcp_lease *lease; @@ -1270,53 +391,6 @@ int dhcp_lease_new(sd_dhcp_lease **ret) { return 0; } -int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { - struct in_addr address, mask; - int r; - - assert(lease); - - if (lease->subnet_mask != INADDR_ANY) - return 0; - - if (lease->address == 0) - return -ENODATA; - - address.s_addr = lease->address; - - /* fall back to the default subnet masks based on address class */ - r = in4_addr_default_subnet_mask(&address, &mask); - if (r < 0) - return r; - - lease->subnet_mask = mask.s_addr; - - return 0; -} - -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret) { - assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); - - if (!sd_dhcp_client_id_is_set(&lease->client_id)) - return -ENODATA; - - *ret = &lease->client_id; - - return 0; -} - -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id) { - assert_return(lease, -EINVAL); - - if (!sd_dhcp_client_id_is_set(client_id)) - return sd_dhcp_client_id_clear(&lease->client_id); - - lease->client_id = *client_id; - - return 0; -} - int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); assert_return(ret, -EINVAL); diff --git a/src/libsystemd-network/test-sd-dhcp-lease.c b/src/libsystemd-network/test-sd-dhcp-lease.c deleted file mode 100644 index 32f9a6b9a3465..0000000000000 --- a/src/libsystemd-network/test-sd-dhcp-lease.c +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "dhcp-lease-internal.h" -#include "strv.h" -#include "tests.h" - -/* According to RFC1035 section 4.1.4, a domain name in a message can be either: - * - a sequence of labels ending in a zero octet - * - a pointer - * - a sequence of labels ending with a pointer - */ -TEST(dhcp_lease_parse_search_domains_basic) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO.BAR")); - assert_se(streq(domains[1], "ABCD.EFG")); -} - -TEST(dhcp_lease_parse_search_domains_ptr) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x00, 0xC0, 0x00, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO")); - assert_se(streq(domains[1], "FOO")); -} - -TEST(dhcp_lease_parse_search_domains_labels_and_ptr) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x03, 'A', 'B', 'C', 0xC0, 0x04, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO.BAR")); - assert_se(streq(domains[1], "ABC.BAR")); -} - -/* Tests for exceptions. */ - -TEST(dhcp_lease_parse_search_domains_no_data) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[3] = {0, 0, 0}; - - assert_se(dhcp_lease_parse_search_domains(NULL, 0, &domains) == -EBADMSG); - assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -EBADMSG); -} - -TEST(dhcp_lease_parse_search_domains_loops) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x00, 0x03, 'B', 'A', 'R', 0xC0, 0x06, - }; - - assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains) == -EBADMSG); -} - -TEST(dhcp_lease_parse_search_domains_wrong_len) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00, - }; - - assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf) - 5, &domains) == -EBADMSG); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 2dbee5450d5eb..b4dd0cb1f61ae 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -56,26 +56,19 @@ int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, uint8_t *ret_prefixlen); int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **addr); int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr); int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers); int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_6rd( sd_dhcp_lease *lease, From 4095391bd618a343364e0537f3057a3daaf8556a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 2 Apr 2026 04:20:59 +0900 Subject: [PATCH 1785/2155] dhcp-packet: drop unused dhcp_packet_verify_headers() --- src/libsystemd-network/dhcp-packet.c | 4 ---- src/libsystemd-network/dhcp-packet.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 27b09a25bbb4f..3186d93b7ac2a 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -115,7 +115,3 @@ int dhcp_packet_append_ip_headers( packet->udp = udp; return 0; } - -int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { - return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL); -} diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 90ea2caac987a..fceeb6473bc04 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -31,5 +31,3 @@ int dhcp_packet_append_ip_headers( uint16_t destination, uint16_t len, int ip_service_type); - -int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port); From cf889036377092cbec6c5ec86fdf0dc1c9326032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 16 May 2026 18:32:14 +0200 Subject: [PATCH 1786/2155] tree-wide: get rid of most uses of ALIGN_PTR This bothered me for a while, but I didn't think too much about it and just copied the existing usage pattern. But it really doesn't make sense. We expect the compiler to align the section properly. But if it didn't align it, applying alignment after the fact would just cause our pointer to point to the middle of the structure. That'd be even worse than a misaligned pointer. Similarly, when doing pointer arithmetic, p++ should really result in a value with the appropriate alignment. This is the basic principle of C pointer addition. So we really shouldn't try to adjust the pointer ourselves. At most, we can assert that it is indeed aligned in tests. --- src/basic/static-destruct.c | 3 +-- src/basic/static-destruct.h | 2 ++ src/libsystemd/sd-bus/bus-error.c | 21 ++++++++------------- src/libsystemd/sd-bus/test-bus-error.c | 15 ++++----------- src/shared/options.h | 7 +++---- src/shared/tests.c | 6 ++---- src/shared/verbs.c | 8 ++++++++ src/shared/verbs.h | 4 ++-- src/systemctl/systemctl.c | 6 +++--- 9 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/basic/static-destruct.c b/src/basic/static-destruct.c index c39a1b381649a..442f5fe7ba4e8 100644 --- a/src/basic/static-destruct.c +++ b/src/basic/static-destruct.c @@ -1,13 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "memory-util.h" #include "static-destruct.h" void static_destruct_impl(const StaticDestructor *start, const StaticDestructor *end) { if (!start) return; - for (const StaticDestructor *d = ALIGN_PTR(start); d < end; d = ALIGN_PTR(d + 1)) + for (const StaticDestructor *d = start; d < end; d++) switch (d->type) { case STATIC_DESTRUCTOR_SIMPLE: d->simple.destroy(d->simple.data); diff --git a/src/basic/static-destruct.h b/src/basic/static-destruct.h index 00087ad779e0a..fdc4c7c41ec37 100644 --- a/src/basic/static-destruct.h +++ b/src/basic/static-destruct.h @@ -44,6 +44,8 @@ typedef struct StaticDestructor { }; } StaticDestructor; +assert_cc(sizeof(StaticDestructor) % sizeof(void*) == 0); + #define STATIC_DESTRUCTOR_REGISTER(variable, func) \ _STATIC_DESTRUCTOR_REGISTER(UNIQ, variable, func) diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index be64e4a6c8bc0..bee843e33806d 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -83,22 +83,17 @@ static int bus_error_name_to_errno(const char *name) { } } - const sd_bus_error_map *elf_map = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); - while (elf_map < __stop_SYSTEMD_BUS_ERROR_MAP) { + assert_cc(sizeof(sd_bus_error_map) % sizeof(void*) == 0); + + for (const sd_bus_error_map *m = __start_SYSTEMD_BUS_ERROR_MAP; m < __stop_SYSTEMD_BUS_ERROR_MAP; m++) { /* For magic ELF error maps, the end marker might appear in the middle of things, since - * multiple maps might appear in the same section. Hence, let's skip over it, but realign - * the pointer to the next 8 byte boundary, which is the selected alignment for the arrays. */ - if (elf_map->code == BUS_ERROR_MAP_END_MARKER) { - elf_map = ALIGN_PTR(elf_map + 1); - continue; - } + * multiple maps might appear in the same section. Skip over it. */ - if (streq(elf_map->name, name)) { - assert(elf_map->code > 0); - return elf_map->code; + if (m->code != BUS_ERROR_MAP_END_MARKER && + streq(m->name, name)) { + assert(m->code > 0); + return m->code; } - - elf_map++; } return EIO; diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index 3f89a907eb654..a91a014a38ca9 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -128,19 +128,12 @@ extern const sd_bus_error_map __start_SYSTEMD_BUS_ERROR_MAP[]; extern const sd_bus_error_map __stop_SYSTEMD_BUS_ERROR_MAP[]; static int dump_mapping_table(void) { - const sd_bus_error_map *m; - printf("----- errno mappings ------\n"); - m = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); - while (m < __stop_SYSTEMD_BUS_ERROR_MAP) { - - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN_PTR(m + 1); - continue; - } + for (const sd_bus_error_map *m = __start_SYSTEMD_BUS_ERROR_MAP; m < __stop_SYSTEMD_BUS_ERROR_MAP; m++) { + assert((uintptr_t) m % sizeof(void*) == 0); - printf("%s -> %i/%s\n", strna(m->name), m->code, ERRNO_NAME(m->code)); - m++; + if (m->code != BUS_ERROR_MAP_END_MARKER) + printf("%s -> %i/%s\n", strna(m->name), m->code, ERRNO_NAME(m->code)); } printf("---------------------------\n"); diff --git a/src/shared/options.h b/src/shared/options.h index c2283dc4063f1..fd3ab008d36f2 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "memory-util.h" #include "shared-forward.h" /* Option namespace/group explanation: @@ -240,10 +239,10 @@ int option_parse( /* Iterate over options. Don't forget to handle errors (negative c)! */ #define FOREACH_OPTION(c, state) \ - for (int c; (c = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, state)) != 0; ) + for (int c; (c = option_parse(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, state)) != 0; ) #define FOREACH_OPTION_OR_RETURN(c, state) \ - for (int c; (c = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, state)) != 0; ) \ + for (int c; (c = option_parse(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, state)) != 0; ) \ if (c < 0) \ return c; \ else @@ -268,7 +267,7 @@ int _option_parser_get_help_table_full( const char *group, Table **ret); #define option_parser_get_help_table_full(namespace, group, ret) \ - _option_parser_get_help_table_full(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, namespace, group, ret) + _option_parser_get_help_table_full(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, namespace, group, ret) #define option_parser_get_help_table_ns(ns, ret) \ option_parser_get_help_table_full(ns, /* group= */ NULL, ret) #define option_parser_get_help_table_group(group, ret) \ diff --git a/src/shared/tests.c b/src/shared/tests.c index 84945343a4650..176c7fb13f373 100644 --- a/src/shared/tests.c +++ b/src/shared/tests.c @@ -380,20 +380,18 @@ int run_test_table(const TestFunc *start, const TestFunc *end) { _cleanup_strv_free_ char **tests = NULL; int r = EXIT_SUCCESS; bool ran = false; - const char *e; if (!start) return r; - e = getenv("TESTFUNCS"); + const char *e = getenv("TESTFUNCS"); if (e) { r = strv_split_full(&tests, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return log_error_errno(r, "Failed to parse $TESTFUNCS: %m"); } - for (const TestFunc *t = ALIGN_PTR(start); t + 1 <= end; t = ALIGN_PTR(t + 1)) { - + for (const TestFunc *t = start; t + 1 <= end; t++) { if (tests && !strv_contains(tests, t->name)) continue; diff --git a/src/shared/verbs.c b/src/shared/verbs.c index aab24c068199a..bd665fc7fa244 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -66,6 +66,9 @@ static bool verb_is_metadata(const Verb *verb) { const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); + assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); + assert(verbs[0].verb); for (const Verb *verb = verbs; verb < verbs_end; verb++) { if (verb_is_metadata(verb)) @@ -84,6 +87,7 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e assert(verbs); assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); assert(verbs[0].verb); const char *name = args ? args[0] : NULL; @@ -150,6 +154,7 @@ 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((uintptr_t) verbs % sizeof(void*) == 0); assert(argc >= 0); assert(argv); assert(argc >= optind); @@ -234,6 +239,9 @@ int _verbs_get_help_table( Table **ret) { int r; + assert(verbs); + assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); assert(ret); _cleanup_(table_unrefp) Table *table = table_new("verb", "help"); diff --git a/src/shared/verbs.h b/src/shared/verbs.h index e071cc5d89aa9..a5d210c89311f 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -81,7 +81,7 @@ const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb ver 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) + _dispatch_verb_with_args(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata) int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); @@ -91,7 +91,7 @@ int _verbs_get_help_table( 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) + _verbs_get_help_table(__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, group, ret) #define verbs_get_help_table(ret) \ verbs_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d5473cdc0af4b..6ea4e50fb4a80 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1112,9 +1112,9 @@ VERB_SCOPE(, verb_start, "condrestart", NULL, 2, VERB_ANY, VERB_SCOPE(, verb_is_active, "check", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* deprecated alias of is-active */ int systemctl_main(char **args) { - const Verb *verb = verbs_find_verb(args[0], - ALIGN_PTR(__start_SYSTEMD_VERBS), - __stop_SYSTEMD_VERBS); + assert((uintptr_t) __start_SYSTEMD_VERBS % sizeof(void*) == 0); + const Verb *verb = verbs_find_verb(args[0], __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS); + 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 e7c550895aa80e6865b3ceeb63bf8a027756fab6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 10 Apr 2026 08:22:27 +0900 Subject: [PATCH 1787/2155] sd-dhcp-client: split out setting anonymize parameter No effective functional change. Just refactoring. --- src/libsystemd-network/fuzz-dhcp-client.c | 2 +- src/libsystemd-network/sd-dhcp-client.c | 51 +++++------------------ src/libsystemd-network/test-dhcp-client.c | 18 ++++---- src/network/networkd-dhcp4.c | 6 ++- src/systemd/sd-dhcp-client.h | 6 +-- 5 files changed, 28 insertions(+), 55 deletions(-) diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index bc6be37be80d6..074b52b7c0b38 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -54,7 +54,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ASSERT_NOT_NULL(e); _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 10161148a4d79..e6e844550ee7d 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -45,32 +45,6 @@ static const uint8_t default_req_opts[] = { SD_DHCP_OPTION_DOMAIN_NAME_SERVER, }; -/* RFC7844 section 3: - MAY contain the Parameter Request List option. - RFC7844 section 3.6: - The client intending to protect its privacy SHOULD only request a - minimal number of options in the PRL and SHOULD also randomly shuffle - the ordering of option codes in the PRL. If this random ordering - cannot be implemented, the client MAY order the option codes in the - PRL by option code number (lowest to highest). -*/ -/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */ -static const uint8_t default_req_opts_anonymize[] = { - SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ - SD_DHCP_OPTION_ROUTER, /* 3 */ - SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ - SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ - SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ - SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* 43 */ - SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ - SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ - SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ - SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ - SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ -}; - static void client_stop(sd_dhcp_client *client, int error); static int client_restart(sd_dhcp_client *client); @@ -100,6 +74,14 @@ int sd_dhcp_client_set_callback( return 0; } +int sd_dhcp_client_set_anonymize(sd_dhcp_client *client, int b) { + assert_return(client, -EINVAL); + assert_return(!sd_dhcp_client_is_running(client), -EBUSY); + + client->anonymize = !!b; + return 0; +} + int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); @@ -1427,9 +1409,7 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free); -int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { - const uint8_t *opts; - size_t n_opts; +int sd_dhcp_client_new(sd_dhcp_client **ret) { int r; assert_return(ret, -EINVAL); @@ -1444,21 +1424,12 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .ifindex = -1, .port = DHCP_PORT_CLIENT, .server_port = DHCP_PORT_SERVER, - .anonymize = !!anonymize, .max_discover_attempts = UINT64_MAX, .ip_service_type = -1, }; - /* NOTE: this could be moved to a function. */ - if (anonymize) { - n_opts = ELEMENTSOF(default_req_opts_anonymize); - opts = default_req_opts_anonymize; - } else { - n_opts = ELEMENTSOF(default_req_opts); - opts = default_req_opts; - } - for (size_t i = 0; i < n_opts; i++) { - r = sd_dhcp_client_set_request_option(client, opts[i]); + FOREACH_ELEMENT(opt, default_req_opts) { + r = sd_dhcp_client_set_request_option(client, *opt); if (r < 0) return r; } diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index c62f46222ab0e..97803a7faf184 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -48,7 +48,7 @@ static be32_t xid; TEST(dhcp_client_setters) { /* Initialize client without Anonymize settings. */ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); @@ -91,15 +91,15 @@ TEST(dhcp_client_setters) { ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 17)); } -TEST(dhcp_client_anonymize) { +TEST(dhcp_client_set_anonymize) { /* Initialize client with Anonymize settings. */ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ true)); + ASSERT_OK(sd_dhcp_client_new(&client)); + ASSERT_OK(sd_dhcp_client_set_anonymize(client, true)); ASSERT_NOT_NULL(client); - 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_OK_POSITIVE(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME)); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER)); + ASSERT_OK_ZERO(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 @@ -211,7 +211,7 @@ TEST(discover_message) { ASSERT_NOT_NULL(e); _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); @@ -400,7 +400,7 @@ TEST(addr_acq) { ASSERT_NOT_NULL(e); _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); @@ -575,7 +575,7 @@ static void test_bootp_one(void) { ASSERT_NOT_NULL(e); _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index b5210e9ddfc91..909f41563acb2 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1486,10 +1486,14 @@ static int dhcp4_configure(Link *link) { if (link->dhcp_client) return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "DHCPv4 client is already configured."); - r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize); + r = sd_dhcp_client_new(&link->dhcp_client); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to allocate DHCPv4 client: %m"); + r = sd_dhcp_client_set_anonymize(link->dhcp_client, link->network->dhcp_anonymize); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to anonymize requests: %m"); + r = sd_dhcp_client_set_bootp(link->dhcp_client, link->network->dhcp_use_bootp); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to %s BOOTP: %m", diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 4e7e79da65667..324cc34156cd5 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -50,7 +50,7 @@ int sd_dhcp_client_set_callback( sd_dhcp_client *client, sd_dhcp_client_callback_t cb, void *userdata); - +int sd_dhcp_client_set_anonymize(sd_dhcp_client *client, int b); int sd_dhcp_client_set_request_option( sd_dhcp_client *client, uint8_t option); @@ -156,9 +156,7 @@ int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client); _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client); -/* NOTE: anonymize parameter is used to initialize PRL memory with different - * options when using RFC7844 Anonymity Profiles */ -int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); +int sd_dhcp_client_new(sd_dhcp_client **ret); int sd_dhcp_client_attach_event( sd_dhcp_client *client, From ff32569f2a3aaec0308ac0bfee2b75718707b880 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 3 May 2026 02:50:37 +0900 Subject: [PATCH 1788/2155] sd-dhcp-client: set TOS and socket priority by default Also this renames sd_dhcp_client_set_service_type() to _set_ip_service_type(). --- src/libsystemd-network/dhcp-client-internal.h | 3 +-- src/libsystemd-network/dhcp-client-send.c | 2 +- src/libsystemd-network/sd-dhcp-client.c | 7 ++++--- src/network/networkd-dhcp4.c | 20 +++++++++++-------- src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 - src/systemd/sd-dhcp-client.h | 4 ++-- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index 7db0a91fa94a1..9e4d0dac8657f 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -78,9 +78,8 @@ struct sd_dhcp_client { void *state_userdata; sd_dhcp_lease *lease; usec_t start_delay; - int ip_service_type; + uint8_t ip_service_type; int socket_priority; - bool socket_priority_set; bool ipv6_acquired; bool bootp; bool send_release; diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index e8b91a3e7c0bd..a77f3c2ae70f9 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -93,7 +93,7 @@ static int client_send_raw( &client->bcast_addr, client->arp_type, client->port, - client->socket_priority_set, + /* so_priority_set= */ true, client->socket_priority); if (fd < 0) return fd; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index e6e844550ee7d..5b17a56a4d79b 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -22,6 +22,7 @@ #include "network-common.h" #include "random-util.h" #include "set.h" +#include "socket-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" @@ -515,7 +516,7 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { return 0; } -int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) { +int sd_dhcp_client_set_ip_service_type(sd_dhcp_client *client, uint8_t type) { assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); @@ -528,7 +529,6 @@ int sd_dhcp_client_set_socket_priority(sd_dhcp_client *client, int socket_priori assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - client->socket_priority_set = true; client->socket_priority = socket_priority; return 0; @@ -1425,7 +1425,8 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { .port = DHCP_PORT_CLIENT, .server_port = DHCP_PORT_SERVER, .max_discover_attempts = UINT64_MAX, - .ip_service_type = -1, + .ip_service_type = IPTOS_CLASS_CS6, /* Defaults to CS6 (Internetwork Control). */ + .socket_priority = tos_to_priority(IPTOS_CLASS_CS6), }; FOREACH_ELEMENT(opt, default_req_opts) { diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 909f41563acb2..468c3eb920b9a 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -1670,12 +1671,13 @@ static int dhcp4_configure(Link *link) { } if (link->network->dhcp_ip_service_type >= 0) { - r = sd_dhcp_client_set_service_type(link->dhcp_client, link->network->dhcp_ip_service_type); + assert(link->network->dhcp_ip_service_type <= UINT8_MAX); + r = sd_dhcp_client_set_ip_service_type(link->dhcp_client, link->network->dhcp_ip_service_type); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set IP service type: %m"); } - if (link->network->dhcp_socket_priority_set) { + if (link->network->dhcp_socket_priority >= 0) { r = sd_dhcp_client_set_socket_priority(link->dhcp_client, link->network->dhcp_socket_priority); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set socket priority: %m"); @@ -2005,14 +2007,13 @@ int config_parse_dhcp_socket_priority( void *data, void *userdata) { - Network *network = ASSERT_PTR(data); - int a, r; + int r, a, *priority = ASSERT_PTR(data); assert(lvalue); assert(rvalue); if (isempty(rvalue)) { - network->dhcp_socket_priority_set = false; + *priority = -1; return 0; } @@ -2022,10 +2023,13 @@ int config_parse_dhcp_socket_priority( "Failed to parse socket priority, ignoring: %s", rvalue); return 0; } + if (a < 0 || a > TC_PRIO_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid socket priority, must be in 0…%i, ignoring: %i", TC_PRIO_MAX, a); + return 0; + } - network->dhcp_socket_priority_set = true; - network->dhcp_socket_priority = a; - + *priority = a; return 0; } diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b925cd3a2b47d..a5afeed1cb43f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -292,7 +292,7 @@ DHCPv4.SendDecline, config_parse_bool, DHCPv4.DenyList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) DHCPv4.AllowList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_allow_listed_ip) DHCPv4.IPServiceType, config_parse_dhcp_ip_service_type, 0, offsetof(Network, dhcp_ip_service_type) -DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, 0 +DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, offsetof(Network, dhcp_socket_priority) DHCPv4.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_extra_options) DHCPv4.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_vendor_options) DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 5cd47ca0a81ee..17cf78db37af0 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -404,6 +404,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_client_identifier = _DHCP_CLIENT_ID_INVALID, .dhcp_route_table = RT_TABLE_MAIN, .dhcp_ip_service_type = -1, + .dhcp_socket_priority = -1, .dhcp_broadcast = -1, .dhcp_ipv6_only_mode = -1, .dhcp_6rd_prefix_route_type = RTN_UNREACHABLE, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c4020a4341af1..d1765a2bf46d3 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -140,7 +140,6 @@ typedef struct Network { int dhcp_critical; int dhcp_ip_service_type; int dhcp_socket_priority; - bool dhcp_socket_priority_set; bool dhcp_anonymize; bool dhcp_send_hostname; bool dhcp_send_hostname_set; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 324cc34156cd5..c0216d36386c0 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -132,9 +132,9 @@ int sd_dhcp_client_set_mud_url( int sd_dhcp_client_get_lease( sd_dhcp_client *client, sd_dhcp_lease **ret); -int sd_dhcp_client_set_service_type( +int sd_dhcp_client_set_ip_service_type( sd_dhcp_client *client, - int type); + uint8_t type); int sd_dhcp_client_set_socket_priority( sd_dhcp_client *client, int socket_priority); From 8bc7cf64d1a8bbcc4a1f0ee2e08134f47265c59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 00:51:33 +0200 Subject: [PATCH 1789/2155] udev: drop leftover includes of getopt.h Not sure why those were left and why clang-tidy didn't complain. --- src/udev/udev-builtin-path_id.c | 1 - src/udev/udev-builtin.c | 1 - src/udev/udevadm-util.c | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index a252ec99dd79b..6d6108ee01b6b 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -5,7 +5,6 @@ * Logic based on Hannes Reinecke's shell script. */ -#include #include #include #include diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index f3c8936cad810..80a8700a626f7 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 7e2420a77e8a9..8e90946960cef 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" From 59ce15c406ab9b2cbee93b4de37c65c16d7c0f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 14 May 2026 09:28:56 +0200 Subject: [PATCH 1790/2155] storagectl: convert run_as_mount_helper to OPTION macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the util-linux mount-helper interface (mount.storage), so all options stay hidden via help=NULL — they are not user-facing. The namespace "mount.storage" is distinct from the storagectl namespace used for the user-facing CLI. Co-developed-by: Claude Opus 4.7 --- src/storage/storagectl.c | 59 ++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index 1a10a0979446f..e33d00bb645c8 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -2,7 +2,6 @@ #include "sd-varlink.h" -#include #include #include #include @@ -59,7 +58,7 @@ static int help(void) { return r; _cleanup_(table_unrefp) Table *options = NULL; - r = option_parser_get_help_table(&options); + r = option_parser_get_help_table_ns("storagectl", &options); if (r < 0) return r; @@ -406,10 +405,12 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argv); assert(remaining_args); - OptionParser opts = { argc, argv }; + OptionParser opts = { argc, argv, .namespace = "storagectl" }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_NAMESPACE("storagectl"): {} + OPTION_COMMON_HELP: return help(); @@ -448,7 +449,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { } static int run_as_mount_helper(int argc, char *argv[]) { - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. * @@ -461,51 +462,55 @@ static int run_as_mount_helper(int argc, char *argv[]) { const char *fstype = NULL, *options = NULL; bool fake = false; - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.storage" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.storage"): {} + + OPTION_SHORT('f', NULL, /* help= */ NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", /* help= */ NULL): + options = opts.arg; break; - case 't': - fstype = startswith(optarg, "storage."); + OPTION_SHORT('t', "FSTYPE", /* help= */ NULL): + fstype = startswith(opts.arg, "storage."); if (fstype) { /* Paranoia: don't allow "storage.storage.storage.…" chains... */ if (startswith(fstype, "storage.") || streq(fstype, "storage")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing nested storage volumes."); - } else if (!streq(optarg, "storage")) - log_warning("Unexpected file system type '%s', ignoring.", optarg); + } else if (!streq(opts.arg, "storage")) + log_warning("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, /* help= */ NULL): {} /* sloppy mount options */ + OPTION_SHORT('n', NULL, /* help= */ NULL): {} /* aka --no-mtab */ + OPTION_SHORT('v', NULL, /* help= */ NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NS", /* help= */ NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", + opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a storage volume specification and target directory as only arguments."); - const char *colon = strchr(argv[optind], ':'); + const char *colon = strchr(args[0], ':'); if (!colon) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume specification, refusing: %s", argv[optind]); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume specification, refusing: %s", args[0]); - _cleanup_free_ char *provider = strndup(argv[optind], colon - argv[optind]); + _cleanup_free_ char *provider = strndup(args[0], colon - args[0]); if (!provider) return log_oom(); if (!storage_provider_name_is_valid(provider)) @@ -518,7 +523,7 @@ static int run_as_mount_helper(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume name: %s", name); _cleanup_free_ char *path = NULL; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &path); + r = parse_path_argument(args[1], /* suppress_root= */ false, &path); if (r < 0) return r; From 59db1f482a77860bbce73ee1b780dabf4ae2557b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 20:39:23 +0200 Subject: [PATCH 1791/2155] dissect: convert the other parser to option macros --- src/dissect/dissect.c | 55 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 280d1ada5fdbf..c79609f583c5a 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -132,11 +131,11 @@ static int help(void) { if (r < 0) return log_oom(); - r = option_parser_get_help_table(&options); + r = option_parser_get_help_table_ns("systemd-dissect", &options); if (r < 0) return r; - r = option_parser_get_help_table_group("Commands", &commands); + r = option_parser_get_help_table_full("systemd-dissect", "Commands", &commands); if (r < 0) return r; @@ -230,11 +229,13 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser opts = { argc, argv }; + OptionParser opts = { argc, argv, .namespace = "systemd-dissect" }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_NAMESPACE("systemd-dissect"): {} + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -745,41 +746,45 @@ static int parse_argv(int argc, char *argv[]) { static int parse_argv_as_mount_helper(int argc, char *argv[]) { const char *options = NULL; bool fake = false; - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.ddi" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.ddi"): {} + + OPTION_SHORT('f', NULL, /* help= */ NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", /* help= */ NULL): + options = opts.arg; break; - case 't': - if (!streq(optarg, "ddi")) - log_debug("Unexpected file system type '%s', ignoring.", optarg); + OPTION_SHORT('t', "FSTYPE", /* help= */ NULL): + if (!streq(opts.arg, "ddi")) + log_debug("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, /* help= */ NULL): {} /* sloppy mount options */ + OPTION_SHORT('n', NULL, /* help= */ NULL): {} /* aka --no-mtab */ + OPTION_SHORT('v', NULL, /* help= */ NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NS", /* help= */ NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", + opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and target directory as only argument."); @@ -808,11 +813,11 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_path_argument(args[0], /* suppress_root= */ false, &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; From 1bf6be01c09c475024b06f279e58a986af1c960b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:00:51 +0200 Subject: [PATCH 1792/2155] backlight: convert verbs to VERB macros Place VERB() declarations directly above each dispatch function and use verbs_get_help_table() in help() so the command listing stays in sync. run() switches to dispatch_verb_with_args(); the argv_looks_like_help() shortcut is preserved since this is an internal tool with no proper option parsing. Co-developed-by: Claude Opus 4.7 (1M context) --- src/backlight/backlight.c | 40 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 69bd42ebcc828..3c2a601403fd9 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -11,10 +11,11 @@ #include "device-util.h" #include "escape.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" #include "parse-util.h" #include "percent-util.h" -#include "pretty-print.h" #include "reboot-util.h" #include "string-util.h" #include "strv.h" @@ -23,25 +24,22 @@ #define PCI_CLASS_GRAPHICS_CARD 0x30000 static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-backlight", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + help_cmdline("COMMAND [backlight|leds]:DEVICE"); + help_abstract("Save and restore backlight brightness at shutdown and boot."); - printf("%s save [backlight|leds]:DEVICE\n" - "%s load [backlight|leds]:DEVICE\n" - "\n%sSave and restore backlight brightness at shutdown and boot.%s\n\n" - " save Save current brightness\n" - " load Set brightness to be the previously saved value\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_man_page_reference("systemd-backlight", "8"); return 0; } @@ -555,6 +553,8 @@ static int device_new_from_arg(const char *s, sd_device **ret) { return 1; /* Found. */ } +VERB(verb_load, "load", "[backlight|leds]:DEVICE", 2, 2, VERB_ONLINE_ONLY, + "Set brightness to be the previously saved value"); 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; @@ -605,6 +605,8 @@ static int verb_load(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_save, "save", "[backlight|leds]:DEVICE", 2, 2, VERB_ONLINE_ONLY, + "Save current brightness"); 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; @@ -647,12 +649,6 @@ static int verb_save(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "load", 2, 2, VERB_ONLINE_ONLY, verb_load }, - { "save", 2, 2, VERB_ONLINE_ONLY, verb_save }, - {} - }; - log_setup(); if (argv_looks_like_help(argc, argv)) @@ -660,7 +656,7 @@ static int run(int argc, char *argv[]) { umask(0022); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From 062f6892b3777d1714784b3a4c4f0fa9f82d16d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:11:40 +0200 Subject: [PATCH 1793/2155] integritysetup: convert verbs to VERB macros Place VERB() declarations above each dispatch function and use verbs_get_help_table() in help() so the command listing stays in sync. run() switches to dispatch_verb_with_args(); the argv_looks_like_help() shortcut is preserved since this is an internal tool with no proper option parsing. Co-developed-by: Claude Opus 4.7 (1M context) --- src/integritysetup/integritysetup.c | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 9a373e124be3e..7126db6d66e64 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -7,13 +7,15 @@ #include "argv-util.h" #include "cryptsetup-util.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "integrity-util.h" #include "log.h" #include "main-func.h" #include "path-util.h" -#include "pretty-print.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "verbs.h" @@ -41,21 +43,22 @@ static const char* const dm_integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dm_integrity_algorithm, IntegrityAlgorithm); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-integritysetup@.service", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s attach VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attach or detach an integrity protected block device.\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - link); + help_cmdline("COMMAND ..."); + help_abstract("Attach or detach an integrity protected block device."); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_man_page_reference("systemd-integritysetup@.service", "8"); return 0; } @@ -94,6 +97,8 @@ static const char *integrity_algorithm_select(const void *key_file_buf) { return dm_integrity_algorithm_to_string(a); } +VERB(verb_attach, "attach", "VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]", 3, 5, 0, + "Attach an integrity protected block device"); 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; @@ -161,6 +166,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 integrity protected block device"); static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; @@ -203,13 +210,7 @@ static int run(int argc, char *argv[]) { umask(0022); - 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(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From 65f83b0352d495ecccc914188c95f1bf17d4ab52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:19:28 +0200 Subject: [PATCH 1794/2155] update-utmp: convert verbs to VERB macros There is no --help implemented, so both verbs don't get help strings. We should probably add --help + --version, and a proper description of the program, but I'm leaving that for later. Co-developed-by: Claude Opus 4.7 (1M context) --- src/update-utmp/update-utmp.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 875ae9a09bcad..bd10f42ea5d85 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -10,6 +10,7 @@ #include "libaudit-util.h" #include "log.h" #include "main-func.h" +#include "strv.h" #include "time-util.h" #include "utmp-wtmp.h" #include "verbs.h" @@ -51,6 +52,7 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } +VERB_NOARG(verb_on_reboot, "reboot", /* help= */ NULL); static int verb_on_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { Context *c = ASSERT_PTR(userdata); usec_t t = 0, boottime; @@ -80,6 +82,7 @@ static int verb_on_reboot(int argc, char *argv[], uintptr_t _data, void *userdat return q; } +VERB_NOARG(verb_on_shutdown, "shutdown", /* help= */ NULL); static int verb_on_shutdown(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, q = 0; @@ -102,12 +105,6 @@ static int verb_on_shutdown(int argc, char *argv[], uintptr_t _data, void *userd } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "reboot", 1, 1, 0, verb_on_reboot }, - { "shutdown", 1, 1, 0, verb_on_shutdown }, - {} - }; - _cleanup_(context_clear) Context c = { .audit_fd = -EBADF, }; @@ -118,7 +115,7 @@ static int run(int argc, char *argv[]) { c.audit_fd = open_audit_fd_or_warn(); - return dispatch_verb(argc, argv, verbs, &c); + return dispatch_verb_with_args(strv_skip(argv, 1), &c); } DEFINE_MAIN_FUNCTION(run); From e934c6209ac3d4dc9c273b6bc814d0d8b59be5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:24:34 +0200 Subject: [PATCH 1795/2155] veritysetup: convert verbs to VERB macros Place VERB() declarations above each dispatch function and use verbs_get_help_table() in help(). run() switches to dispatch_verb_with_args(); the argv_looks_like_help() shortcut is preserved since this is an internal tool with no proper option parsing. Co-developed-by: Claude Opus 4.7 (1M context) --- src/veritysetup/veritysetup.c | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index b5d57fd1fe80c..5f413cb83fc6b 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -10,14 +10,15 @@ #include "cryptsetup-util.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "fstab-util.h" +#include "help-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "parse-util.h" #include "path-util.h" #include "pcrextend-util.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "tpm2-util.h" @@ -50,21 +51,22 @@ STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-veritysetup@.service", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attach or detach a verity protected block device.\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - link); + help_cmdline("COMMAND ..."); + help_abstract("Attach or detach a verity protected block device."); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_man_page_reference("systemd-veritysetup@.service", "8"); return 0; } @@ -315,6 +317,8 @@ static int parse_options(const char *options) { return r; } +VERB(verb_attach, "attach", "VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]", 5, 6, 0, + "Attach a verity protected block device"); 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; @@ -449,6 +453,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 a verity protected block device"); static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; @@ -491,13 +497,7 @@ static int run(int argc, char *argv[]) { umask(0022); - static const Verb verbs[] = { - { "attach", 5, 6, 0, verb_attach }, - { "detach", 2, 2, 0, verb_detach }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From 40fed88f8e0210815175dd01e9e50a98682d5754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:29:37 +0200 Subject: [PATCH 1796/2155] test-verbs: dispatch via _dispatch_verb_with_args() directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the global-optind dependency from the test helper. Verb fixtures stay inline as static const Verb[] — the section-based VERB() macro would force unique verb names across the three test cases, which they deliberately share to exercise overlap. Co-developed-by: Claude Opus 4.7 (1M context) --- src/test/test-verbs.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 41ae5a87f3766..cfff6ec728490 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "strv.h" #include "tests.h" #include "verbs.h" @@ -11,8 +9,7 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda } #define test_dispatch_one(argv, verbs, expected) \ - optind = 0; \ - assert_se(dispatch_verb(strv_length(argv), argv, verbs, NULL) == expected); + assert_se(_dispatch_verb_with_args(argv, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL) == expected); TEST(verbs) { static const Verb verbs[] = { From 39a36475394fc88da405d435c6bc94db935948c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 21:37:04 +0200 Subject: [PATCH 1797/2155] tree-wide: get rid of old dispatch_verb Also rename dispatch_verb_with_args to dispatch_verb. --- src/analyze/analyze.c | 2 +- src/backlight/backlight.c | 2 +- src/bless-boot/bless-boot.c | 2 +- src/bootctl/bootctl.c | 2 +- src/busctl/busctl.c | 2 +- src/coredump/coredumpctl.c | 2 +- src/creds/creds.c | 2 +- src/cryptsetup/cryptsetup.c | 2 +- src/factory-reset/factory-reset-tool.c | 2 +- src/home/homectl.c | 2 +- src/home/test-homectl-prompts.c | 2 +- src/hostname/hostnamectl.c | 2 +- src/hwdb/hwdb.c | 2 +- src/id128/id128.c | 2 +- src/import/export.c | 2 +- src/import/import-fs.c | 2 +- src/import/import.c | 2 +- src/import/importctl.c | 2 +- src/import/pull.c | 2 +- src/integritysetup/integritysetup.c | 2 +- src/kernel-install/kernel-install.c | 2 +- src/keyutil/keyutil.c | 2 +- src/locale/localectl.c | 2 +- src/login/loginctl.c | 2 +- src/machine/machinectl.c | 2 +- src/measure/measure-tool.c | 2 +- src/network/networkctl.c | 2 +- src/oom/oomctl.c | 2 +- src/pcrlock/pcrlock.c | 2 +- src/portable/portablectl.c | 2 +- src/random-seed/random-seed-tool.c | 2 +- src/report/report.c | 2 +- src/resolve/resolvectl.c | 4 ++-- src/sbsign/sbsign.c | 2 +- src/shared/verbs.c | 18 +----------------- src/shared/verbs.h | 8 +++----- src/sleep/sleep.c | 2 +- src/ssh-generator/ssh-issue.c | 2 +- src/storage/storagectl.c | 2 +- src/sysext/sysext.c | 2 +- src/systemctl/systemctl.c | 2 +- src/sysupdate/sysupdate.c | 2 +- src/sysupdate/updatectl.c | 2 +- src/test/test-verbs.c | 2 +- src/timedate/timedatectl.c | 2 +- src/udev/iocost/iocost.c | 2 +- src/udev/udevadm.c | 2 +- src/update-utmp/update-utmp.c | 2 +- src/userdb/userdbctl.c | 2 +- src/varlinkctl/varlinkctl.c | 2 +- src/veritysetup/veritysetup.c | 2 +- 51 files changed, 54 insertions(+), 72 deletions(-) diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 759c5600ad276..67a9135767503 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -716,7 +716,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 3c2a601403fd9..7e296c9ffcb28 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -656,7 +656,7 @@ static int run(int argc, char *argv[]) { umask(0022); - return dispatch_verb_with_args(strv_skip(argv, 1), /* userdata= */ NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 3680dfdc1883e..43fb72cddb72b 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -580,7 +580,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_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 4bb9742a4703d..b5cca6d222c22 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -917,7 +917,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 9bf990d3dbfdd..edba5748e86e2 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -2324,7 +2324,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 7bb1fd175213c..3b31471ab4dc7 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1501,7 +1501,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - r = dispatch_verb_with_args(args, NULL); + r = dispatch_verb(args, NULL); if (units_active > 0) printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n", diff --git a/src/creds/creds.c b/src/creds/creds.c index a4087fc2be650..77f7442ab57aa 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -1478,7 +1478,7 @@ static int run(int argc, char *argv[]) { if (arg_varlink) return vl_server(); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index fffecd35738cb..b6ee120e5ec56 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2883,7 +2883,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index c21e84c2ef7af..d3019396c3c1f 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -365,7 +365,7 @@ static int run(int argc, char *argv[]) { if (arg_varlink) return varlink_service(); - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/home/homectl.c b/src/home/homectl.c index 75339d93c8302..ab81efbed6dd1 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -5165,7 +5165,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/home/test-homectl-prompts.c b/src/home/test-homectl-prompts.c index aaa81cc78a89d..59bff32c44387 100644 --- a/src/home/test-homectl-prompts.c +++ b/src/home/test-homectl-prompts.c @@ -100,7 +100,7 @@ static int run(int argc, char **argv) { if (r <= 0) return r; - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index ab21fcca1f070..282a16d83cb37 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -833,7 +833,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 5ad3bac3211ee..4d2d6fe8c48d9 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -125,7 +125,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/id128/id128.c b/src/id128/id128.c index ceac8a832e5c1..e66acd33e5eed 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -304,7 +304,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/export.c b/src/import/export.c index 8b64b9cad901e..0b6d27b9ffa8d 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -292,7 +292,7 @@ static int run(int argc, char *argv[]) { (void) ignore_signals(SIGPIPE); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index cc6e14c995a8a..1e6d8beff274c 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -400,7 +400,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/import.c b/src/import/import.c index 9ddc4469c1fda..18276fa72d243 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -481,7 +481,7 @@ static int run(int argc, char *argv[]) { (void) ignore_signals(SIGPIPE); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/importctl.c b/src/import/importctl.c index 0140d0f67de48..58d996e8d74ef 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1260,7 +1260,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/pull.c b/src/import/pull.c index ac9492f177565..09294bc6fc573 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -620,7 +620,7 @@ static int run(int argc, char *argv[]) { (void) ignore_signals(SIGPIPE); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 7126db6d66e64..bdf1b25473bd4 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -210,7 +210,7 @@ static int run(int argc, char *argv[]) { umask(0022); - return dispatch_verb_with_args(strv_skip(argv, 1), /* userdata= */ NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 27e1a7426e4d1..ff93a80c0867a 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1719,7 +1719,7 @@ static int run(int argc, char* argv[]) { if (invoked_as(argv, "installkernel")) return run_as_installkernel(args); - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index abdec1591d01c..ab8e3eee98233 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -415,7 +415,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/locale/localectl.c b/src/locale/localectl.c index f1f9ffbce0c05..ff7afa6c841dd 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -519,7 +519,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 39c048e70891c..63eafdceb4aed 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1680,7 +1680,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index c0f9fd51e07c5..b0efb6ec029cf 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2656,7 +2656,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index b8acd6d4b0922..e7a3be6d643b1 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1126,7 +1126,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index b64c8b17fc7d0..bdf487bf2e803 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -213,7 +213,7 @@ static int run(int argc, char* argv[]) { journal_browse_prepare(); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index f6d451612b725..8b5051ebd09c2 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -120,7 +120,7 @@ static int run(int argc, char* argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 8315384b4d10e..9ce991262e0db 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5505,7 +5505,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 763acfc258656..9286a6fbb3664 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1455,7 +1455,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index f573e84412ffb..514176ef5d8ea 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -362,7 +362,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); } - r = dispatch_verb_with_args(option_parser_get_args(&opts), NULL); + r = dispatch_verb(option_parser_get_args(&opts), NULL); if (r < 0) return r; diff --git a/src/report/report.c b/src/report/report.c index 4031d915431e3..9cb0e053e0706 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -886,7 +886,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index e8ae7c5412acc..67be81195a843 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3761,7 +3761,7 @@ static int translate(const char *verb, const char *single_arg, char **args) { STRV_FOREACH(a, args) *p++ = *a; - return dispatch_verb_with_args(fake, /* userdata= */ NULL); + return dispatch_verb(fake, /* userdata= */ NULL); } static int compat_main(char **args) { @@ -3883,7 +3883,7 @@ static int run(int argc, char **argv) { if (compat) return compat_main(args); - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index 9d163624a4997..f845bcdf424b7 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -730,7 +730,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index bd665fc7fa244..e9e78c5b72934 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -82,7 +82,7 @@ const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb ver return NULL; } -int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) { +int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) { int r; assert(verbs); @@ -150,22 +150,6 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e 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((uintptr_t) verbs % sizeof(void*) == 0); - assert(argc >= 0); - assert(argv); - assert(argc >= optind); - - size_t n = 0; - while (verbs[n].verb) - n++; - - return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); -} - #define VERB_SYNOPSIS_WIDTH_SANE 25 static const char* find_point_to_break(const char *s, size_t max_width) { diff --git a/src/shared/verbs.h b/src/shared/verbs.h index a5d210c89311f..2a9df84e0c33b 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -79,11 +79,9 @@ bool should_bypass(const char *env_prefix); 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, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata) - -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); +int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata); +#define dispatch_verb(args, userdata) \ + _dispatch_verb(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata) int _verbs_get_help_table( const Verb verbs[], diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 53f306a8faefc..24cd632111696 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -760,7 +760,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, sleep_config); + return dispatch_verb(args, sleep_config); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index 2028d3f942393..dd2b2370b4b7f 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -224,7 +224,7 @@ static int run(int argc, char* argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index e33d00bb645c8..7e366c11a912b 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -740,7 +740,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index ff00196cc654d..a3b1ba6221b66 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -3048,7 +3048,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 6ea4e50fb4a80..99244de7674c2 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1120,5 +1120,5 @@ int systemctl_main(char **args) { "Verb '%s' cannot be used with --root= or --image=.", args[0] ?: verb->verb); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index c64c5a9b95f76..425460a319192 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -2010,7 +2010,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index a256dc19c8b68..cf574a86e9063 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1757,7 +1757,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, true); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index cfff6ec728490..56062b0bd6bdc 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -9,7 +9,7 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda } #define test_dispatch_one(argv, verbs, expected) \ - assert_se(_dispatch_verb_with_args(argv, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL) == expected); + assert_se(_dispatch_verb(argv, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL) == expected); TEST(verbs) { static const Verb verbs[] = { diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 7607b1ac4b1fe..2c9ab9b6fd777 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -1028,7 +1028,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return dispatch_verb_with_args(args, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index 1efd4a5365e1b..a591ba40c0cca 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -308,7 +308,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index cdc10802749ea..9b7b09370a97f 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -115,7 +115,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index bd10f42ea5d85..d27616c2cd37e 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -115,7 +115,7 @@ static int run(int argc, char *argv[]) { c.audit_fd = open_audit_fd_or_warn(); - return dispatch_verb_with_args(strv_skip(argv, 1), &c); + return dispatch_verb(strv_skip(argv, 1), &c); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index dd6fc0f5c12f1..fd349b282677b 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1874,7 +1874,7 @@ static int run(int argc, char *argv[]) { } else assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0); - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index fbd5e2499a5d5..94a4bc40b35c7 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1318,7 +1318,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - return dispatch_verb_with_args(args, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 5f413cb83fc6b..e9ce2bdfecb69 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -497,7 +497,7 @@ static int run(int argc, char *argv[]) { umask(0022); - return dispatch_verb_with_args(strv_skip(argv, 1), /* userdata= */ NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From d512d3c6d30be6697f36bf042f95816cb6ad36c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 May 2026 20:32:29 +0200 Subject: [PATCH 1798/2155] treewide: get rid of remaing getopt/getopt_long stuff --- .clang-tidy | 1 - src/basic/argv-util.c | 2 +- src/include/musl/getopt.h | 29 -- src/include/musl/unistd.h | 8 - src/libc/musl/getopt.c | 100 ------- src/libc/musl/meson.build | 1 - src/shared/parse-argument.c | 10 +- src/shared/verbs.c | 2 - src/test/meson.build | 1 - src/test/test-getopt.c | 516 ------------------------------------ src/udev/udev-builtin.c | 2 - 11 files changed, 6 insertions(+), 666 deletions(-) delete mode 100644 src/include/musl/getopt.h delete mode 100644 src/libc/musl/getopt.c delete mode 100644 src/test/test-getopt.c diff --git a/.clang-tidy b/.clang-tidy index 8d69a69cbf971..c784c9532b1c1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -38,7 +38,6 @@ CheckOptions: # of them related to musl). misc-include-cleaner.IgnoreHeaders: ' endian\.h; - getopt\.h; sys/stat\.h; sys/statvfs\.h; sys/syscall\.h; diff --git a/src/basic/argv-util.c b/src/basic/argv-util.c index 0ff2fd9bcbaaf..10f8cd86f4aad 100644 --- a/src/basic/argv-util.c +++ b/src/basic/argv-util.c @@ -71,7 +71,7 @@ bool argv_looks_like_help(int argc, char **argv) { char **l; /* Scans the command line for indications the user asks for help. This is supposed to be called by - * tools that do not implement getopt() style command line parsing because they are not primarily + * tools that do not implement getopt()-style command line parsing because they are not primarily * user-facing. Detects four ways of asking for help: * * 1. Passing zero arguments diff --git a/src/include/musl/getopt.h b/src/include/musl/getopt.h deleted file mode 100644 index 6aed1dfd262ad..0000000000000 --- a/src/include/musl/getopt.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* getopt() is provided both in getopt.h and unistd.h. Hence, we need to tentatively undefine it. */ -#undef getopt - -#include_next - -/* musl's getopt() always behaves POSIXLY_CORRECT mode, and stops parsing arguments when a non-option string - * found. Let's always use getopt_long(). */ -int getopt_fix(int argc, char * const *argv, const char *optstring); -#define getopt(argc, argv, optstring) getopt_fix(argc, argv, optstring) - -/* musl's getopt_long() behaves something different in handling optional arguments. - * ======== - * $ journalctl _PID=1 _COMM=systemd --since 19:19:01 -n all --follow - * Failed to add match 'all': Invalid argument - * ======== - * Here, we introduce getopt_long_fix() that reorders the passed arguments to make getopt_long() provided by - * musl works as what we expect. */ -int getopt_long_fix( - int argc, - char * const *argv, - const char *optstring, - const struct option *longopts, - int *longindex); - -#define getopt_long(argc, argv, optstring, longopts, longindex) \ - getopt_long_fix(argc, argv, optstring, longopts, longindex) diff --git a/src/include/musl/unistd.h b/src/include/musl/unistd.h index e58fee356af91..175661442dee4 100644 --- a/src/include/musl/unistd.h +++ b/src/include/musl/unistd.h @@ -1,16 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -/* getopt() is provided both in getopt.h and unistd.h. Hence, we need to tentatively undefine it. */ -#undef getopt - #include_next -/* musl's getopt() always behaves POSIXLY_CORRECT mode, and stops parsing arguments when a non-option string - * found. Let's always use getopt_long(). */ -int getopt_fix(int argc, char * const *argv, const char *optstring); -#define getopt(argc, argv, optstring) getopt_fix(argc, argv, optstring) - int missing_close_range(unsigned first_fd, unsigned end_fd, unsigned flags); #define close_range missing_close_range diff --git a/src/libc/musl/getopt.c b/src/libc/musl/getopt.c deleted file mode 100644 index 15c3afa49bdbb..0000000000000 --- a/src/libc/musl/getopt.c +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -static int first_non_opt = 0, last_non_opt = 0; -static bool non_opt_found = false, dash_dash = false; - -static void shift(char * const *argv, int start, int end) { - char **av = (char**) argv; - char *saved = av[end]; - - for (int i = end; i > start; i--) - av[i] = av[i - 1]; - - av[start] = saved; -} - -static void exchange(int argc, char * const *argv) { - /* input: - * - * first_non_opt last_non_opt optind - * | | | - * v v v - * aaaaa bbbbb ccccc --prev-opt prev-opt-arg ddddd --next-opt - * - * output: - * first_non_opt last_non_opt optind - * | | | - * v v v - * --prev-opt prev-opt-arg aaaaa bbbbb ccccc ddddd --next-opt - */ - - /* First, move previous arguments. */ - int c = optind - 1 - last_non_opt; - if (c > 0) { - for (int i = 0; i < c; i++) - shift(argv, first_non_opt, optind - 1); - first_non_opt += c; - last_non_opt += c; - } - - /* Then, skip entries that do not start with '-'. */ - while (optind < argc && (argv[optind][0] != '-' || argv[optind][1] == '\0')) { - if (!non_opt_found) { - first_non_opt = optind; - non_opt_found = true; - } - last_non_opt = optind; - optind++; - } -} - -int getopt_long_fix( - int argc, - char * const *argv, - const char *optstring, - const struct option *longopts, - int *longindex) { - - int r; - - if (optind == 0 || first_non_opt == 0 || last_non_opt == 0) { - /* initialize musl's internal variables. */ - (void) (getopt_long)(/* argc= */ -1, /* argv= */ NULL, /* optstring= */ NULL, /* longopts= */ NULL, /* longindex= */ NULL); - first_non_opt = last_non_opt = 1; - non_opt_found = dash_dash = false; - } - - if (first_non_opt >= argc || last_non_opt >= argc || optind > argc || dash_dash) - return -1; - - /* Do not shuffle arguments when optstring starts with '+' or '-'. */ - if (!optstring || optstring[0] == '+' || optstring[0] == '-') - return (getopt_long)(argc, argv, optstring, longopts, longindex); - - exchange(argc, argv); - - if (optind < argc && strcmp(argv[optind], "--") == 0) { - if (first_non_opt < optind) - shift(argv, first_non_opt, optind); - first_non_opt++; - optind++; - dash_dash = true; - if (non_opt_found) - optind = first_non_opt; - return -1; - } - - r = (getopt_long)(argc, argv, optstring, longopts, longindex); - if (r < 0 && non_opt_found) - optind = first_non_opt; - - return r; -} - -int getopt_fix(int argc, char * const *argv, const char *optstring) { - return getopt_long_fix(argc, argv, optstring, /* longopts= */ NULL, /* longindex= */ NULL); -} diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build index acda973b8d9b2..a06216f063445 100644 --- a/src/libc/musl/meson.build +++ b/src/libc/musl/meson.build @@ -6,7 +6,6 @@ endif libc_wrapper_sources += files( 'crypt.c', - 'getopt.c', 'printf.c', 'stdio.c', 'stdlib.c', diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 0d4bfa4c91e69..bfd21a910e0e5 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -31,7 +31,7 @@ int parse_boolean_argument(const char *optname, const char *s, bool *ret) { *ret = r; return r; } else { - /* s may be NULL. This is controlled by getopt_long() parameters. */ + /* s may be NULL. This is controlled by OPTION flags. */ if (ret) *ret = true; return true; @@ -42,10 +42,10 @@ int parse_tristate_argument_with_auto(const char *optname, const char *s, int *r int r; assert(optname); - assert(s); /* We refuse NULL optarg here, since that would be ambiguous on cmdline: - for --enable-a[=BOOL], --enable-a is intuitively interpreted as true rather than "auto" - (parse_boolean_argument() does exactly that). IOW, tristate options should require - arguments. */ + assert(s); /* We refuse NULL arg here, since that would be ambiguous on cmdline: for + * --enable-a[=BOOL], --enable-a is intuitively interpreted as true rather than "auto" + * (parse_boolean_argument() does exactly that). IOW, tristate options should require + * arguments. */ r = parse_tristate_full(s, "auto", ret); if (r < 0) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index e9e78c5b72934..34e8a897d6a2f 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "env-util.h" #include "format-table.h" #include "log.h" diff --git a/src/test/meson.build b/src/test/meson.build index 20960d0b9587f..4871c5517f376 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -111,7 +111,6 @@ simple_tests += files( 'test-format-util.c', 'test-fs-util.c', 'test-fstab-util.c', - 'test-getopt.c', 'test-glob-util.c', 'test-gpt.c', 'test-gunicode.c', diff --git a/src/test/test-getopt.c b/src/test/test-getopt.c deleted file mode 100644 index 0ba1c678f241d..0000000000000 --- a/src/test/test-getopt.c +++ /dev/null @@ -1,516 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "strv.h" -#include "tests.h" - -typedef struct Entry { - int opt; - const char *argument; - const char *nextarg; -} Entry; - -static void test_getopt_long_one( - char **argv, - const char *optstring, - const struct option *longopts, - 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 c, argc = strv_length(argv); - size_t i = 0, n_entries = 0; - - for (const Entry *e = entries; e && e->opt != 0; e++) - n_entries++; - - optind = 0; - while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) >= 0) { - if (c < 0x100) - log_debug("%c: %s", c, strna(optarg)); - else - log_debug("0x%x: %s", (unsigned) c, strna(optarg)); - - ASSERT_LT(i, n_entries); - ASSERT_EQ(c, entries[i].opt); - ASSERT_STREQ(optarg, entries[i].argument); - if (entries[i].nextarg) - ASSERT_STREQ(argv[optind], entries[i].nextarg); - i++; - } - - ASSERT_EQ(i, n_entries); - ASSERT_LE(optind, argc); - ASSERT_EQ(argc - optind, (int) strv_length(remaining)); - for (int j = optind; j < argc; j++) - ASSERT_STREQ(argv[j], remaining[j - optind]); - ASSERT_STREQ(argv[0], saved_argv0); -} - -TEST(getopt_long) { - enum { - ARG_VERSION = 0x100, - ARG_REQUIRED, - ARG_OPTIONAL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "required1", required_argument, NULL, 'r' }, - { "required2", required_argument, NULL, ARG_REQUIRED }, - { "optional1", optional_argument, NULL, 'o' }, - { "optional2", optional_argument, NULL, ARG_OPTIONAL }, - {}, - }; - - test_getopt_long_one(STRV_MAKE("arg0"), - "hr:o::", options, - NULL, - NULL); - - 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", - "--", - "string1", - "--help", - "-h", - "string4"), - "hr:o::", options, - NULL, - STRV_MAKE("string1", - "--help", - "-h", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "--", - "--", - "string4"), - "hr:o::", options, - NULL, - STRV_MAKE("string1", - "string2", - "--", - "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", - "--help"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-h"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "--help", - "string1", - "string2", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "-h", - "string1", - "string2", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "--help", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "-h", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "string4", - "--help"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "string4", - "-h"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "--required1", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-r", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "-r", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - STRV_MAKE("string1", - "string2")); - - test_getopt_long_one(STRV_MAKE("arg0", - "--optional1=optarg1"), - "hr:o::", options, - (Entry[]) { - { 'o', "optarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "--optional1", "string1"), - "hr:o::", options, - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_long_one(STRV_MAKE("arg0", - "-ooptarg1"), - "hr:o::", options, - (Entry[]) { - { 'o', "optarg1" }, - {} - }, NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-o", "string1"), - "hr:o::", options, - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_long_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"), - "hr:o::", options, - (Entry[]) { - { 'h' }, - { ARG_VERSION }, - { 'r', "reqarg1" }, - { ARG_REQUIRED, "reqarg2" }, - { 'r', "reqarg3" }, - { ARG_REQUIRED, "reqarg4" }, - { 'o', NULL, "string4" }, - { ARG_OPTIONAL, NULL, "string5" }, - { 'o', "optarg1" }, - { ARG_OPTIONAL, "optarg2" }, - { 'h' }, - { 'r', "reqarg5" }, - { 'r', "reqarg6" }, - { 'o', "optarg3" }, - { 'o', NULL, "string6" }, - { 'o', NULL, "-h" }, - { 'h' }, - { 'o', NULL, "--help" }, - { 'h' }, - { 'h' }, - { 'o', "optarg4" }, - { 'h' }, - { 'r', "reqarg6" }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4", - "string5", - "string6", - "string7", - "--help", - "--required1", - "--optional1")); -} -static void test_getopt_one( - char **argv, - const char *optstring, - 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 c, argc = strv_length(argv); - size_t i = 0, n_entries = 0; - - for (const Entry *e = entries; e && e->opt != 0; e++) - n_entries++; - - optind = 0; - while ((c = getopt(argc, argv, optstring)) >= 0) { - log_debug("%c: %s", c, strna(optarg)); - - ASSERT_LT(i, n_entries); - ASSERT_EQ(c, entries[i].opt); - ASSERT_STREQ(optarg, entries[i].argument); - if (entries[i].nextarg) - ASSERT_STREQ(argv[optind], entries[i].nextarg); - i++; - } - - ASSERT_EQ(i, n_entries); - ASSERT_LE(optind, argc); - ASSERT_EQ(argc - optind, (int) strv_length(remaining)); - for (int j = optind; j < argc; j++) - ASSERT_STREQ(argv[j], remaining[j - optind]); - ASSERT_STREQ(argv[0], saved_argv0); -} - -TEST(getopt) { - test_getopt_one(STRV_MAKE("arg0"), - "hr:o::", - NULL, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2"), - "hr:o::", - NULL, - STRV_MAKE("string1", - "string2")); - - test_getopt_one(STRV_MAKE("arg0", - "-h"), - "hr:o::", - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "-r", "reqarg1"), - "hr:o::", - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2", - "-r", "reqarg1"), - "hr:o::", - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - STRV_MAKE("string1", - "string2")); - - test_getopt_one(STRV_MAKE("arg0", - "-ooptarg1"), - "hr:o::", - (Entry[]) { - { 'o', "optarg1" }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "-o", "string1"), - "hr:o::", - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "-h", - "-r", "reqarg5", - "-rreqarg6", - "-ooptarg3", - "-o", - "string6", - "-o", - "-h", - "-o", - "string7", - "-hooptarg4", - "-hrreqarg6"), - "hr:o::", - (Entry[]) { - { 'h' }, - { 'r', "reqarg5" }, - { 'r', "reqarg6" }, - { 'o', "optarg3" }, - { 'o', NULL, "string6" }, - { 'o', NULL, "-h" }, - { 'h' }, - { 'o', NULL, "string7" }, - { 'h' }, - { 'o', "optarg4" }, - { 'h' }, - { 'r', "reqarg6" }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string6", - "string7")); -} - -DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 80a8700a626f7..fbed496c4ec40 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -127,8 +127,6 @@ int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *comma if (r < 0) return r; - /* we need '0' here to reset the internal state */ - optind = 0; return builtins[cmd]->cmd(event, strv_length(argv), argv); } From f2169cfe8e1509305da275b6c06798e3f9674cba Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 11 Apr 2026 03:17:28 +0900 Subject: [PATCH 1799/2155] sd-dhcp-lease: coding style cleanups Rename function arguments for storing results, and accept NULL for them. Also, adjust the position of asterisk. --- src/libsystemd-network/sd-dhcp-lease.c | 118 ++++++++++++------------- src/systemd/sd-dhcp-lease.h | 26 +++--- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 641ee8561b7fc..e6580011f87ce 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -33,67 +33,67 @@ int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t assert_return(lease, -EINVAL); assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); assert_return(clock_supported(clock), -EOPNOTSUPP); - assert_return(ret, -EINVAL); if (!triple_timestamp_is_set(&lease->timestamp)) return -ENODATA; - *ret = triple_timestamp_by_clock(&lease->timestamp, clock); + if (ret) + *ret = triple_timestamp_by_clock(&lease->timestamp, clock); return 0; } -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (lease->address == 0) + if (lease->address == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->address; + if (ret) + ret->s_addr = lease->address; return 0; } -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); if (lease->broadcast == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->broadcast; + if (ret) + ret->s_addr = lease->broadcast; return 0; } int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->lifetime <= 0) return -ENODATA; - *ret = lease->lifetime; + if (ret) + *ret = lease->lifetime; return 0; } int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->t1 <= 0) return -ENODATA; - *ret = lease->t1; + if (ret) + *ret = lease->t1; return 0; } int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->t2 <= 0) return -ENODATA; - *ret = lease->t2; + if (ret) + *ret = lease->t2; return 0; } @@ -106,7 +106,7 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { usec_t t, timestamp; \ int r; \ \ - assert_return(ret, -EINVAL); \ + assert_return(lease, -EINVAL); \ \ r = sd_dhcp_lease_get_##name(lease, &t); \ if (r < 0) \ @@ -116,7 +116,8 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { if (r < 0) \ return r; \ \ - *ret = usec_add(t, timestamp); \ + if (ret) \ + *ret = usec_add(t, timestamp); \ return 0; \ } @@ -124,21 +125,21 @@ DEFINE_GET_TIMESTAMP(lifetime); DEFINE_GET_TIMESTAMP(t1); DEFINE_GET_TIMESTAMP(t2); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *ret) { assert_return(lease, -EINVAL); - assert_return(mtu, -EINVAL); if (lease->mtu <= 0) return -ENODATA; - *mtu = lease->mtu; + if (ret) + *ret = lease->mtu; return 0; } int sd_dhcp_lease_get_servers( sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, - const struct in_addr **addr) { + const struct in_addr **ret) { assert_return(lease, -EINVAL); assert_return(what >= 0, -EINVAL); @@ -147,30 +148,30 @@ int sd_dhcp_lease_get_servers( if (lease->servers[what].size <= 0) return -ENODATA; - if (addr) - *addr = lease->servers[what].addr; + if (ret) + *ret = lease->servers[what].addr; return (int) lease->servers[what].size; } -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, ret); } -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, ret); } -int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, ret); } -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(domainname, -EINVAL); if (!lease->domainname) return -ENODATA; - *domainname = lease->domainname; + if (ret) + *ret = lease->domainname; return 0; } @@ -187,45 +188,45 @@ int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret) { int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (!lease->captive_portal) return -ENODATA; - *ret = lease->captive_portal; + if (ret) + *ret = lease->captive_portal; return 0; } -int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers) { +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret) { assert_return(lease, -EINVAL); - assert_return(ret_resolvers, -EINVAL); if (!lease->dnr) return -ENODATA; - *ret_resolvers = lease->dnr; + if (ret) + *ret = lease->dnr; return lease->n_dnr; } -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); if (lease->router_size <= 0) return -ENODATA; - *addr = lease->router; + if (ret) + *ret = lease->router; return (int) lease->router_size; } -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); if (lease->subnet_mask == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->subnet_mask; + if (ret) + ret->s_addr = lease->subnet_mask; return 0; } @@ -257,14 +258,14 @@ int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, u return 0; } -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (lease->server_address == 0) + if (lease->server_address == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->server_address; + if (ret) + ret->s_addr = lease->server_address; return 0; } @@ -306,19 +307,16 @@ int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***re return dhcp_lease_get_routes(lease->classless_routes, lease->n_classless_routes, ret); } -int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) { - size_t r; - +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***ret) { assert_return(lease, -EINVAL); - assert_return(domains, -EINVAL); - r = strv_length(lease->search_domains); - if (r > 0) { - *domains = lease->search_domains; - return (int) r; - } + size_t n = strv_length(lease->search_domains); + if (n == 0) + return -ENODATA; - return -ENODATA; + if (ret) + *ret = lease->search_domains; + return (int) n; } int sd_dhcp_lease_get_6rd( @@ -352,7 +350,7 @@ int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease) { return lease && lease->sixrd_n_br_addresses > 0; } -static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { +static sd_dhcp_lease* dhcp_lease_free(sd_dhcp_lease *lease) { assert(lease); sd_dhcp_message_unref(lease->message); @@ -393,12 +391,12 @@ int dhcp_lease_new(sd_dhcp_lease **ret) { int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (!lease->timezone) return -ENODATA; - *ret = lease->timezone; + if (ret) + *ret = lease->timezone; return 0; } diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index b4dd0cb1f61ae..a912ecb655022 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -44,7 +44,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_dhcp_lease_server_type_t) { _SD_ENUM_FORCE_S64(DHCP_LEASE_SERVER_TYPE) } sd_dhcp_lease_server_type_t; -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *ret); int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint64_t *ret); int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint64_t *ret); @@ -52,21 +52,21 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret); int sd_dhcp_lease_get_lifetime_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_t1_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_t2_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *ret); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *ret); int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, uint8_t *ret_prefixlen); -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **addr); -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); -int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ret); +int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **ret); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *ret); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **ret); +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***ret); int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret); -int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers); +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret); int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret); From 33c6d10749233718183a6a5c7d6de2153fc7f555 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 14:31:10 +0900 Subject: [PATCH 1800/2155] sd-dhcp-client: allow to set socket fd externally It will be used by the unit test and fuzzer for the DHCP client. Preparation for later changes. --- src/libsystemd-network/dhcp-client-internal.h | 2 ++ src/libsystemd-network/dhcp-client-send.c | 25 +++++++++++++++---- src/libsystemd-network/sd-dhcp-client.c | 4 +++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index 9e4d0dac8657f..ccae831284326 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -30,6 +30,8 @@ DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); struct sd_dhcp_client { unsigned n_ref; + int socket_fd; /* socket fd set externally, used by unit tests */ + DHCPState state; sd_event *event; int event_priority; diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index a77f3c2ae70f9..5fe804db60fc9 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -20,6 +20,10 @@ static int client_get_socket(sd_dhcp_client *client, int domain) { assert(client); assert(IN_SET(domain, AF_PACKET, AF_INET)); + /* When a socket fd is given externally, unconditionally use it. */ + if (client->socket_fd >= 0) + return client->socket_fd; + if (!client->receive_message) return -EBADF; @@ -50,6 +54,12 @@ static int client_setup_io_event( assert(callback); assert(description); + /* When the socket fd is given externally, the fd is used for both UDP and RAW packet operations. + * Hence, first we need to disable the previous event source, otherwise sd_event_add_io() will fail + * with -EEXIST. */ + if (fd == client->socket_fd) + client->receive_message = sd_event_source_disable_unref(client->receive_message); + _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) @@ -63,9 +73,14 @@ static int client_setup_io_event( if (r < 0) return r; - r = sd_event_source_set_io_fd_own(s, true); - if (r < 0) - return r; + /* When the socket fd is given externally, do not close it while we are running. The IO event source + * is freed when not necessary, hence the lifetime of the socket fd should not be tied to the one of + * the event source in that case. */ + if (fd != client->socket_fd) { + 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); @@ -143,7 +158,7 @@ static int client_send_raw( return 0; } - if (fd_close < 0) + if (fd_close < 0 && fd != client->socket_fd) 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"); @@ -200,7 +215,7 @@ static int client_send_udp( return 0; } - if (fd_close < 0) + if (fd_close < 0 && fd != client->socket_fd) 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"); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 5b17a56a4d79b..d94e6057d5a43 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -15,6 +15,7 @@ #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 "iovec-wrapper.h" @@ -1387,6 +1388,8 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { client_initialize(client); + safe_close(client->socket_fd); + sd_event_source_unref(client->timeout_resend); sd_event_source_unref(client->timeout_t1); sd_event_source_unref(client->timeout_t2); @@ -1420,6 +1423,7 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { *client = (sd_dhcp_client) { .n_ref = 1, + .socket_fd = -EBADF, .state = DHCP_STATE_STOPPED, .ifindex = -1, .port = DHCP_PORT_CLIENT, From 385cd74fca248144d8de4816b827e732b0190b6c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 11 Apr 2026 03:17:43 +0900 Subject: [PATCH 1801/2155] test-dhcp-client: rewrite the test by using sd_dhcp_message More test cases will be added later. --- src/libsystemd-network/test-dhcp-client.c | 993 +++++++++++----------- 1 file changed, 488 insertions(+), 505 deletions(-) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 97803a7faf184..7a6c163e28fb5 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -4,7 +4,6 @@ ***/ #include -#include #include #include #include @@ -13,602 +12,586 @@ #include "sd-dhcp-lease.h" #include "sd-event.h" -#include "dhcp-duid-internal.h" -#include "dhcp-network.h" -#include "dhcp-option.h" +#include "dhcp-client-internal.h" +#include "dhcp-message.h" +#include "dhcp-protocol.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" #include "log.h" +#include "set.h" +#include "strv.h" #include "tests.h" -static struct hw_addr_data hw_addr = { +static const struct hw_addr_data hw_addr = { .length = ETH_ALEN, .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, }, bcast_addr = { .length = ETH_ALEN, .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}, }; -typedef void (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); -struct bootp_addr_data { - uint8_t *offer_buf; - size_t offer_len; - int netmask_offset; - int ip_offset; +static const uint16_t server_port = 1067; +static const uint16_t client_port = 1068; +static const int ip_service_type = IPTOS_CLASS_CS3; + +static const union in_addr_union prefix = { + .bytes = { 198, 51, 100, 0 }, +}, server_address = { + .bytes = { 198, 51, 100, 1 }, +}, client_address = { + .bytes = { 198, 51, 100, 100 }, +}, broadcast = { + .bytes = { 198, 51, 100, 255 }, +}, netmask = { + .bytes = { 255, 255, 255, 0 }, }; -static struct bootp_addr_data *bootp_test_context; -static int test_fd[2]; -static test_callback_recv_t callback_recv; -static be32_t xid; +static const usec_t lifetime = USEC_PER_DAY; -TEST(dhcp_client_setters) { - /* Initialize client without Anonymize settings. */ - _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client)); - 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_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_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_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 const sd_dhcp_client_id client_id_generic = { + .size = 10, + .id.type = 0, + .id.data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, }, +}; + +/* options sent by client */ +static const char * const *user_class_strv = STRV_MAKE_CONST("user-class-hoge", "user-class-foo"); +static const char * const *vendor_specific_strv = STRV_MAKE_CONST("vendor-specific-hoge", "vendor-specific-foo"); +static const char *vendor_class = "vendor-class"; +static const char *mud_url = "https://mud-url.example.com"; +static const char *hostname = "hogehoge.example.com"; +static const uint32_t mtu = 3000; +static const char *extra_option_163 = "private_option_163"; +static const char *extra_option_164 = "private_option_164"; + +static void setup(sd_event_io_handler_t io_handler, sd_dhcp_client_callback_t client_handler, sd_dhcp_client **ret) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); + + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); -TEST(dhcp_client_set_anonymize) { - /* Initialize client with Anonymize settings. */ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; ASSERT_OK(sd_dhcp_client_new(&client)); - ASSERT_OK(sd_dhcp_client_set_anonymize(client, true)); - ASSERT_NOT_NULL(client); - ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER)); - ASSERT_OK_ZERO(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); + client->socket_fd = TAKE_FD(socket_fd[0]); - /* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the - * default PRL when using Anonymize, */ - ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 101)); - ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); -} + /* Set a fake socket address, as the client will never call dhcp_network_bind_raw_socket() when + * socket_fd is set. */ + client->link.ll = (struct sockaddr_ll) { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_IP), + .sll_ifindex = 42, + .sll_hatype = ARPHRD_ETHER, + .sll_halen = bcast_addr.length, + }; + memcpy(client->link.ll.sll_addr, bcast_addr.bytes, bcast_addr.length); -static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { - switch (code) { - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { - sd_dhcp_duid duid; - uint32_t iaid; + ASSERT_OK(sd_dhcp_client_attach_event(client, e, SD_EVENT_PRIORITY_NORMAL)); + 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, client_handler, e)); + ASSERT_OK(sd_dhcp_client_set_port(client, server_port)); + ASSERT_OK(sd_dhcp_client_set_client_port(client, client_port)); + ASSERT_OK(sd_dhcp_client_set_ip_service_type(client, ip_service_type)); + + /* options */ + for (uint8_t i = 178; i <= 207; i++) /* These are currently unassigned. See sd-dhcp-protocol.h. */ + ASSERT_OK(sd_dhcp_client_set_request_option(client, i)); + + ASSERT_OK(sd_dhcp_client_set_client_id(client, client_id_generic.id.type, client_id_generic.id.data, client_id_generic.size - 1)); + ASSERT_OK(sd_dhcp_client_set_mtu(client, mtu)); + ASSERT_OK(sd_dhcp_client_set_mud_url(client, mud_url)); + ASSERT_OK(sd_dhcp_client_set_hostname(client, hostname)); + ASSERT_OK(sd_dhcp_client_set_vendor_class_identifier(client, vendor_class)); + + _cleanup_(tlv_unrefp) TLV *vendor_specific = + ASSERT_NOT_NULL(tlv_new(TLV_DHCP4_SUBOPTION)); + uint8_t c = 0; + STRV_FOREACH(s, vendor_specific_strv) + ASSERT_OK(tlv_append(vendor_specific, ++c, strlen(*s), *s)); + ASSERT_OK(dhcp_client_set_vendor_options(client, vendor_specific)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + STRV_FOREACH(s, user_class_strv) + ASSERT_OK(iovw_put(&iovw, (void*) *s, strlen(*s))); + ASSERT_OK(dhcp_client_set_user_class(client, &iovw)); + + _cleanup_(tlv_unrefp) TLV *extra_options = ASSERT_NOT_NULL(tlv_new(TLV_DHCP4)); + ASSERT_OK(tlv_append(extra_options, 163, strlen(extra_option_163), extra_option_163)); + ASSERT_OK(tlv_append(extra_options, 164, strlen(extra_option_164), extra_option_164)); + ASSERT_OK(dhcp_client_set_extra_options(client, extra_options)); + + /* IO event source for the fake server side */ + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + ASSERT_OK(sd_event_add_io(e, &s, socket_fd[1], EPOLLIN, io_handler, client)); + ASSERT_OK(sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IMPORTANT)); + ASSERT_OK(sd_event_source_set_description(s, "fake-server-io-event-source")); + ASSERT_OK(sd_event_source_set_io_fd_own(s, true)); + TAKE_FD(socket_fd[1]); + ASSERT_OK(sd_event_source_set_floating(s, true)); + + *ret = TAKE_PTR(client); +} - ASSERT_OK(sd_dhcp_duid_set_en(&duid)); - ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid)); +static void receive_message(int fd, bool raw, bool check_xid, sd_dhcp_client *client, sd_dhcp_message **ret) { + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) + ASSERT_OK(udp_packet_verify( + &payload, + client->server_port, + /* checksum= */ true, + &payload)); + + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREQUEST, + check_xid ? &client->xid : NULL, + ARPHRD_ETHER, + &hw_addr, + ret)); +} - ASSERT_EQ(len, 19u); - ASSERT_EQ(len, sizeof(uint8_t) + sizeof(uint32_t) + duid.size); - ASSERT_EQ(((uint8_t*) option)[0], 0xff); +static void iovw_send(int fd, struct iovec_wrapper *iovw) { + struct msghdr mh = { + .msg_iov = iovw->iovec, + .msg_iovlen = iovw->count, + }; + ASSERT_OK_ERRNO(sendmsg(fd, &mh, MSG_NOSIGNAL)); +} - ASSERT_EQ(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)), 0); - ASSERT_EQ(memcmp((uint8_t*) option + 5, &duid.duid, duid.size), 0); - break; - } +static void send_message(int fd, bool raw, sd_dhcp_client *client, sd_dhcp_message *m) { + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + ASSERT_OK(dhcp_message_build(m, &payload)); - default: - ; + if (!raw) { + iovw_send(fd, &payload); + return; } - return 0; + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + server_address.in.s_addr, + client->server_port, + m->header.yiaddr, + client->port, + client->ip_service_type, + &payload, + &ip, + &udp)); + + _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)); + iovw_send(fd, &iovw); } -int dhcp_network_send_raw_socket(int fd, const union sockaddr_union *link, const struct iovec_wrapper *iovw) { - uint16_t ip_check, udp_check; - - ASSERT_OK(fd); - ASSERT_NOT_NULL(iovw); - - _cleanup_(iovec_done) struct iovec iov = {}; - ASSERT_OK(iovw_concat(iovw, &iov)); - - size_t len = iov.iov_len; - ASSERT_GT(len, sizeof(DHCPPacket)); - - DHCPPacket *discover = ASSERT_NOT_NULL(iov.iov_base); - - 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; - - discover->ip.ttl = 0; - discover->ip.check = discover->udp.len; - - 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 = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); - ASSERT_EQ(ip_check, 0xffff); +static void create_reply(sd_dhcp_client *client, sd_dhcp_message *request, uint8_t type, sd_dhcp_message **ret) { + assert(ret); + + struct hw_addr_data hw; + ASSERT_OK(dhcp_message_get_hw_addr(request, &hw)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREPLY, + be32toh(request->header.xid), + request->header.htype, + &hw)); + + if (client->bootp) { + m->header.yiaddr = client_address.in.s_addr; + m->header.siaddr = server_address.in.s_addr; + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SUBNET_MASK, &netmask.in)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_BROADCAST, &broadcast.in)); + + *ret = TAKE_PTR(m); + return; + } - ASSERT_NE(discover->dhcp.xid, 0u); - ASSERT_EQ(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length), 0); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, type)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, &server_address.in)); + + switch (type) { + case DHCP_OFFER: + case DHCP_ACK: + m->header.yiaddr = client_address.in.s_addr; + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SUBNET_MASK, &netmask.in)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_BROADCAST, &broadcast.in)); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, usec_to_be32_sec(lifetime))); + /* The following two options are intentionally set with spurious values, to test the adjusting logic. */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REBINDING_TIME, usec_to_be32_sec(lifetime + USEC_PER_SEC))); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_RENEWAL_TIME, usec_to_be32_sec(lifetime - USEC_PER_SEC))); + break; - ASSERT_NOT_NULL(callback_recv); - callback_recv(len - sizeof(struct iphdr) - sizeof(struct udphdr), &discover->dhcp); + case DHCP_NAK: + ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_ERROR_MESSAGE, "test error message")); + break; - return 0; -} + default: + ; + } -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t id, - const struct hw_addr_data *_hw_addr, - const struct hw_addr_data *_bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd)); - return test_fd[0]; + *ret = TAKE_PTR(m); } -int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); +static void verify_header(sd_dhcp_message *m) { + ASSERT_EQ(m->header.op, BOOTREQUEST); + ASSERT_EQ(m->header.htype, ARPHRD_ETHER); + ASSERT_EQ(memcmp_nn(m->header.chaddr, m->header.hlen, hw_addr.bytes, hw_addr.length), 0); } -int dhcp_network_send_udp_socket(int fd, be32_t address, uint16_t port, const struct iovec_wrapper *iovw) { - return 0; -} +static void verify_basic_options(sd_dhcp_message *m, uint8_t type) { + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + ASSERT_EQ(t, type); -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)); + sd_dhcp_client_id id; + ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); + ASSERT_EQ(client_id_compare_func(&id, &client_id_generic), 0); } -TEST(discover_message) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - ASSERT_OK(sd_event_new(&e)); - ASSERT_NOT_NULL(e); - - _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client)); - ASSERT_NOT_NULL(client); - - ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 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_OK(sd_dhcp_client_set_request_option(client, 248)); +static void verify_request(sd_dhcp_message *m, uint8_t type) { + verify_header(m); + verify_basic_options(m, type); + + _cleanup_set_free_ Set *prl = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &prl)); + + for (uint8_t i = 178; i <= 207; i++) + ASSERT_TRUE(set_contains(prl, UINT_TO_PTR(i))); + + uint16_t sz; + ASSERT_OK(dhcp_message_get_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz)); + ASSERT_EQ(sz, mtu); + + _cleanup_free_ char *str = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_MUD_URL, &str)); + ASSERT_STREQ(str, mud_url); + + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_hostname(m, &str)); + ASSERT_STREQ(str, hostname); + + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, &str)); + ASSERT_STREQ(str, vendor_class); + + _cleanup_(tlv_unrefp) TLV *vendor_specific = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + TLV_DHCP4_SUBOPTION, + &vendor_specific)); + ASSERT_EQ(hashmap_size(vendor_specific->entries), strv_length((char**) vendor_specific_strv)); + uint8_t c = 0; + STRV_FOREACH(s, vendor_specific_strv) { + struct iovec v; + ASSERT_OK(tlv_get(vendor_specific, ++c, &v)); + ASSERT_EQ(memcmp_nn(v.iov_base, v.iov_len, *s, strlen(*s)), 0); + } - callback_recv = test_discover_message_verify; + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + STRV_FOREACH(s, user_class_strv) + ASSERT_OK(iovw_put(&iovw, (void*) *s, strlen(*s))); + _cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}; + ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, /* length_size= */ 1, &user_class)); + ASSERT_TRUE(iovw_equal(&user_class, &iovw)); - 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)); + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, 163, &str)); + ASSERT_STREQ(str, extra_option_163); - test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, 164, &str)); + ASSERT_STREQ(str, extra_option_164); } -static uint8_t test_addr_acq_offer[] = { - 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, - 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, - 0x6f, 0x95, 0x2f, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, - 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, - 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, - 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, - 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, - 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; +static void verify_request_server_address(sd_dhcp_message *m) { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &server_address.in)); +} -static uint8_t test_addr_acq_ack[] = { - 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, - 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, - 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36, - 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, - 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, - 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, - 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; +static void verify_request_client_address(sd_dhcp_message *m, bool header) { + if (header) + ASSERT_EQ(m->header.ciaddr, client_address.in.s_addr); + else { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &client_address.in)); + } +} -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)); +static void verify_reply(sd_dhcp_client *client, DHCPState state) { + ASSERT_EQ(client->state, state); 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, &test_addr_acq_ack[44], sizeof(addr.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); - - 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); - - log_info(" DHCP address acquired"); - - sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); - return ASSERT_OK(sd_event_exit(e, 0)); + struct in_addr a; + ASSERT_OK(sd_dhcp_lease_get_address(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &client_address.in)); + + ASSERT_OK(sd_dhcp_lease_get_server_identifier(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &server_address.in)); + + ASSERT_OK(sd_dhcp_lease_get_broadcast(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &broadcast.in)); + + ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &netmask.in)); + + uint8_t prefixlen; + ASSERT_OK(sd_dhcp_lease_get_prefix(lease, &a, &prefixlen)); + ASSERT_TRUE(in4_addr_equal(&a, &prefix.in)); + ASSERT_EQ(prefixlen, 24u); + + if (client->bootp) { + usec_t t; + ASSERT_OK(sd_dhcp_lease_get_lifetime(lease, &t)); + ASSERT_EQ(t, USEC_INFINITY); + } else { + usec_t t; + ASSERT_OK(sd_dhcp_lease_get_lifetime(lease, &t)); + ASSERT_EQ(t, lifetime); + ASSERT_OK(sd_dhcp_lease_get_t1(lease, &t)); + ASSERT_EQ(t, lifetime / 2); + ASSERT_OK(sd_dhcp_lease_get_t2(lease, &t)); + ASSERT_EQ(t, lifetime * 7 / 8); + } } -static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) { - uint16_t udp_check = 0; - uint8_t *msg_bytes = (uint8_t *)request; - - ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST); - ASSERT_EQ(request->xid, xid); - - uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); - ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); - - 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)); - memcpy(&test_addr_acq_ack[56], hw_addr.bytes, hw_addr.length); - - callback_recv = NULL; - - ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_ack, sizeof(test_addr_acq_ack)), - (ssize_t) sizeof(test_addr_acq_ack)); - - log_info(" send DHCP Ack"); -}; +static int basic_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; -static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { - uint16_t udp_check = 0; - uint8_t *msg_bytes = (uint8_t *)discover; + count++; + log_debug("%s: count=%u", __func__, count); - ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER); + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size)); - ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1)); + verify_request(request, DHCP_DISCOVER); - xid = discover->xid; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); - log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 2: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check)); - memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid)); - memcpy(&test_addr_acq_offer[56], hw_addr.bytes, hw_addr.length); + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); - callback_recv = test_addr_acq_recv_request; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_offer, sizeof(test_addr_acq_offer)), - (ssize_t) sizeof(test_addr_acq_offer)); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 3: { + /* In this stage, client is already restarted and a new xid is picked. */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ false, client, &request); + + verify_header(request); + verify_basic_options(request, DHCP_DECLINE); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + break; + } + case 4: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - log_info(" sent DHCP Offer"); -} + verify_request(request, DHCP_DISCOVER); -TEST(addr_acq) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - ASSERT_OK(sd_event_new(&e)); - ASSERT_NOT_NULL(e); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); - _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client)); - ASSERT_NOT_NULL(client); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 5: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); - 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)); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - ASSERT_OK(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, NULL)); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 6: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ true, client, &request); - callback_recv = test_addr_acq_recv_discover; + /* REQUEST (renewing) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); - ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 30 * USEC_PER_SEC, 0, - NULL, INT_TO_PTR(-ETIMEDOUT))); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - 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; -} + send_message(fd, /* raw= */ false, client, reply); + break; + } + case 7: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ true, client, &request); -static uint8_t test_addr_bootp_reply[] = { - 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, - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x46, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x50, 0x2d, 0xf4, 0x1f, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0x00, - 0x00, 0x00, 0x36, 0x04, 0x0a, 0x00, 0x00, 0x02, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}; + /* REQUEST (renewing) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); -static uint8_t test_addr_bootp_reply_bootpd[] = { - 0x45, 0x00, 0x01, 0x48, 0xbe, 0xad, 0x40, 0x00, - 0x40, 0x11, 0x73, 0x43, 0xc0, 0xa8, 0x43, 0x31, - 0xc0, 0xa8, 0x43, 0x32, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x08, 0xfa, 0x02, 0x01, 0x06, 0x00, - 0x82, 0x57, 0xda, 0xf1, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x43, 0x32, - 0xc0, 0xa8, 0x43, 0x31, 0x00, 0x00, 0x00, 0x00, - 0xc2, 0x3e, 0xa5, 0x53, 0x57, 0x72, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x64, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0xff, - 0xff, 0xf0, 0x03, 0x04, 0xc0, 0xa8, 0x43, 0x31, - 0x06, 0x04, 0x0a, 0x00, 0x01, 0x01, 0x0c, 0x15, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x74, - 0x72, 0x69, 0x78, 0x69, 0x65, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); -static struct bootp_addr_data bootp_addr_data[] = { - { - .offer_buf = test_addr_bootp_reply, - .offer_len = sizeof(test_addr_bootp_reply), - .netmask_offset = 270, - .ip_offset = 44, - }, - { - .offer_buf = test_addr_bootp_reply_bootpd, - .offer_len = sizeof(test_addr_bootp_reply_bootpd), - .netmask_offset = 270, - .ip_offset = 44, - }, -}; + send_message(fd, /* raw= */ false, client, reply); + break; + } + case 8: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); -static int test_bootp_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)); + /* REQUEST (rebinding) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); - sd_dhcp_lease *lease; - ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); - ASSERT_NOT_NULL(lease); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - 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); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 9: { + /* In this stage, client is already stopped and the xid has been cleared. */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ false, client, &request); - 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); + verify_header(request); + verify_basic_options(request, DHCP_RELEASE); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ true); - log_info(" BOOTP address acquired"); + ASSERT_OK(sd_event_exit(sd_dhcp_client_get_event(client), 0)); + break; + } + default: + assert_not_reached(); + } - sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); - return ASSERT_OK(sd_event_exit(e, 0)); + return 0; } -static void test_bootp_recv_request(size_t size, DHCPMessage *request) { - uint16_t udp_check = 0; - - xid = request->xid; - - log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); +static int restart_now_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); - callback_recv = NULL; + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + return 0; +} - memcpy(&bootp_test_context->offer_buf[26], &udp_check, sizeof(udp_check)); - memcpy(&bootp_test_context->offer_buf[32], &xid, sizeof(xid)); - memcpy(&bootp_test_context->offer_buf[56], hw_addr.bytes, hw_addr.length); +static int basic_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; - 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); + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - log_info(" sent BOOTP Reply"); -}; + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + /* decline the bound lease, and restart the cycle. */ + ASSERT_OK(sd_dhcp_client_send_decline(client)); + break; + case 3: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_EXPIRED); + verify_reply(client, DHCP_STATE_BOUND); + /* on decline, the client will be restarted with a delay. Let's boost the restart timer. */ + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, restart_now_defer_handler, client)); + break; + case 4: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 5: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + /* renew the lease manually */ + ASSERT_OK(sd_dhcp_client_send_renew(client)); + break; + case 6: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* renew the lease by timer, triggering the corresponding timer event source now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_t1, /* usec= */ 0)); + break; + case 7: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* rebind the lease, triggering the corresponding timer event source now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_t2, /* usec= */ 0)); + break; + case 8: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* release and stop. */ + ASSERT_OK(sd_dhcp_client_stop(client)); + break; + case 9: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + break; + default: + assert_not_reached(); + } -static void test_bootp_one(void) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - ASSERT_OK(sd_event_new(&e)); - ASSERT_NOT_NULL(e); + return 0; +} +TEST(basic) { _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_new(&client)); - ASSERT_NOT_NULL(client); - - 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, NULL)); - - callback_recv = test_bootp_recv_request; - - ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 30 * USEC_PER_SEC, 0, - NULL, INT_TO_PTR(-ETIMEDOUT))); - + setup(basic_io_handler, basic_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_send_release(client, true)); 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; -} - -TEST(bootp) { - FOREACH_ELEMENT(i, bootp_addr_data) { - bootp_test_context = i; - test_bootp_one(); - } + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } static int intro(void) { From ab9d673c6b67d831daa2049d6690b9394f6cde02 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 14 May 2026 08:43:59 +0200 Subject: [PATCH 1802/2155] update TODO --- TODO.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO.md b/TODO.md index 8d623eb0de333..8f760294a88f0 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- format-table: introduce the concept of a "title" for a table, which remains + closely associated with the table. in most cases where want to output + multiple tables from the same tool we want to separate things with a title, + hence we might as well associate the title with the table itself, and + streamline a few things. + - report: add filtering by metric prefix, so that we reports don't have to include everything, and we have a way to have "small" and "big" reports, that have different number of fields. From 3eba8d6265fec1ea69d1c1d99825d50e996114f8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 14:37:29 +0900 Subject: [PATCH 1803/2155] test-dhcp-client: add test case for anonymized mode --- src/libsystemd-network/test-dhcp-client.c | 97 +++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 7a6c163e28fb5..0dd5623486071 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -326,6 +326,30 @@ static void verify_request(sd_dhcp_message *m, uint8_t type) { ASSERT_STREQ(str, extra_option_164); } +static void verify_anonymized_request(sd_dhcp_message *m, uint8_t type) { + verify_header(m); + verify_basic_options(m, type); + + _cleanup_set_free_ Set *prl = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &prl)); + + for (uint8_t i = 178; i <= 207; i++) + ASSERT_FALSE(set_contains(prl, UINT_TO_PTR(i))); + + uint8_t code; + FOREACH_ARGUMENT(code, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + SD_DHCP_OPTION_MUD_URL, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_FQDN, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + SD_DHCP_OPTION_USER_CLASS, + 163, + 164) + ASSERT_FALSE(dhcp_message_has_option(m, code)); +} + static void verify_request_server_address(sd_dhcp_message *m) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a)); @@ -594,6 +618,79 @@ TEST(basic) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int anonymize_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_anonymized_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 2: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_anonymized_request(request, DHCP_REQUEST); + verify_request_server_address(request); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int anonymize_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(anonymize) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(anonymize_io_handler, anonymize_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_anonymize(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From f7c144b9c74bc8b9e266f87c7a66dfb1e8160162 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 14:38:24 +0900 Subject: [PATCH 1804/2155] test-dhcp-client: add test case for rapid commit mode --- src/libsystemd-network/test-dhcp-client.c | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 0dd5623486071..51eca61bc0343 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -691,6 +691,63 @@ TEST(anonymize) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int rapid_commit_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + ASSERT_TRUE(dhcp_message_has_option(request, SD_DHCP_OPTION_RAPID_COMMIT)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + ASSERT_OK(dhcp_message_append_option_flag(reply, SD_DHCP_OPTION_RAPID_COMMIT)); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int rapid_commit_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(rapid_commit) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(rapid_commit_io_handler, rapid_commit_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_rapid_commit(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From 9bd6b949d3b10a20c58fd2e8f1d521d9a882994f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 14:39:06 +0900 Subject: [PATCH 1805/2155] test-dhcp-client: add test case for INIT-REBOOT sequence --- src/libsystemd-network/test-dhcp-client.c | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 51eca61bc0343..e9ff0d95566ea 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -748,6 +748,62 @@ TEST(rapid_commit) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int init_reboot_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int init_reboot_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(init_reboot) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(init_reboot_io_handler, init_reboot_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_address(client, &client_address.in)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From 7df92102833f24a4428a223d6cdc77c804bb98d1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 14:39:26 +0900 Subject: [PATCH 1806/2155] test-dhcp-client: add test case for BOOTP client mode --- src/libsystemd-network/test-dhcp-client.c | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index e9ff0d95566ea..3adb201459e0c 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -804,6 +804,62 @@ TEST(init_reboot) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int bootp_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_header(request); + ASSERT_TRUE(tlv_isempty(&request->options)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int bootp_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(bootp) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(bootp_io_handler, bootp_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_bootp(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From 115c354b57997445e2a6e1d45a54e6ebd6cf279d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 15 Apr 2026 03:57:10 +0900 Subject: [PATCH 1807/2155] test-dhcp-client: add test cases for IPv6-only mode --- src/libsystemd-network/test-dhcp-client.c | 361 ++++++++++++++++++++++ 1 file changed, 361 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 3adb201459e0c..13f9e7c464574 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -860,6 +860,367 @@ TEST(bootp) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int ipv6_only_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + /* This is used multiple times. */ + switch (count) { + case 1: /* test case: before discover */ + case 2: /* test case: after offer */ + case 3: /* test case: before request */ + case 4: /* test case: before ack */ + case 6: { /* test case: after ack */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 5: /* test case: before ack */ + case 7: { /* test case: after ack */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 8: { /* test case: init-reboot */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (init-reboot) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 9: { /* test case: rapid commit */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + ASSERT_OK(dhcp_message_append_option_flag(reply, SD_DHCP_OPTION_RAPID_COMMIT)); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_before_discover_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_after_offer_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_before_request_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); + ASSERT_EQ(client->request_attempt, 0u); + + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_EQ(client->state, DHCP_STATE_STOPPED); + + ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_OFF)); + return 0; +} + +static int ipv6_only_before_request_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, ipv6_only_before_request_defer_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_before_ack_post_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) + /* Boost the time for sending DHCPREQUEST */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + + else if (client->state == DHCP_STATE_REQUESTING) { + ASSERT_EQ(client->request_attempt, 1u); + + /* Set IPv6 connectivity after a DHCPREQUEST sent. */ + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + /* Still running */ + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); + + } else if (client->state == DHCP_STATE_BOUND) + ASSERT_OK(sd_dhcp_client_stop(client)); + + return 0; +} + +static int ipv6_only_before_ack_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_post(e, /* ret= */ NULL, ipv6_only_before_ack_post_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + break; + case 3: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_after_ack_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); + /* Boost the time for sending DHCPREQUEST */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + return 0; +} + +static int ipv6_only_after_ack_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, ipv6_only_after_ack_defer_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + /* still running */ + ASSERT_EQ(client->state, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_init_reboot_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_rapid_commit_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(ipv6_only) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + + /* case 1: IPv6 connectivity is acquired before starting the client. */ + setup(ipv6_only_io_handler, ipv6_only_before_discover_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 2: IPv6 connectivity is acquired after DHCPOFFER received. */ + setup(ipv6_only_io_handler, ipv6_only_after_offer_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 3: IPv6 connectivity is acquired before sending DHCPREQUEST. */ + setup(ipv6_only_io_handler, ipv6_only_before_request_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 4: IPv6 connectivity is acquired before DHCPACK received. */ + setup(ipv6_only_io_handler, ipv6_only_before_ack_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 5: IPv6 connectivity is acquired after DHCPACK received. */ + setup(ipv6_only_io_handler, ipv6_only_after_ack_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 6: IPv6 connectivity is acquired on reboot. */ + setup(ipv6_only_io_handler, ipv6_only_init_reboot_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_request_address(client, &client_address.in)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 7: IPv6 connectivity is acquired on rapid commit. */ + setup(ipv6_only_io_handler, ipv6_only_rapid_commit_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_rapid_commit(client, true)); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From acdaee339ff9cf42e49a37a1d94ea0a7ecb3747b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 16 Apr 2026 08:39:56 +0900 Subject: [PATCH 1808/2155] test-dhcp-client: add test cases for discover attempt counter --- src/libsystemd-network/test-dhcp-client.c | 159 ++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 13f9e7c464574..5407ccb0b3208 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -1221,6 +1221,165 @@ TEST(ipv6_only) { ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } +static int discover_attempt_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1 ... 5: /* test case: no reply */ + case 6 ... 9: /* test case: reset on bound (1st cycle) */ + case 12 ... 15: { /* test case: reset on bound (2nd cycle) */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + /* Boost the retransmit timer. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + break; + } + case 10: + case 16: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 11: + case 17: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } + + return 0; +} + +static int discover_attempt_no_reply_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1 ... 3: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count + 2u); + break; + case 4: + ASSERT_EQ(event, -ETIMEDOUT); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int discover_attempt_reset_on_bound_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1 ... 3: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count + 2u); + break; + case 4: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + break; + case 5: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + /* expire the lease now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_expire, 0)); + break; + case 6: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_EXPIRED); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + break; + case 7 ... 9: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count - 4u); + break; + case 10: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + break; + case 11: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + ASSERT_OK(sd_dhcp_client_stop(client)); + break; + case 12: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(discover_attempt) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + + /* case 1: no reply */ + setup(discover_attempt_io_handler, discover_attempt_no_reply_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_max_attempts(client, 5)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 2: attempt is reset on bound */ + setup(discover_attempt_io_handler, discover_attempt_reset_on_bound_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_max_attempts(client, 5)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + static int intro(void) { ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; From 335ac4a71d4fec132448c1a4c33df09cff8224b6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 16 Apr 2026 04:43:12 +0900 Subject: [PATCH 1809/2155] fuzz-dhcp-client: externally set socket fd, rather than override functions --- src/libsystemd-network/fuzz-dhcp-client.c | 54 ++++++++++------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 074b52b7c0b38..ad289754ed1cd 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -9,41 +9,20 @@ #include "dhcp-client-internal.h" #include "dhcp-lease-internal.h" #include "dhcp-message.h" -#include "dhcp-network.h" +#include "fd-util.h" #include "fuzz.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "tests.h" -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t id, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); -} - -int dhcp_network_send_raw_socket(int fd, const union sockaddr_union *link, const struct iovec_wrapper *iovw) { - return 0; -} - -int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); -} - -int dhcp_network_send_udp_socket(int fd, be32_t address, uint16_t port, const struct iovec_wrapper *iovw) { - return 0; -} - int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - static const uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; - static const uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + static const struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }, bcast_addr = { + .length = ETH_ALEN, + .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}, + }; ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); @@ -57,10 +36,25 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ASSERT_OK(sd_dhcp_client_new(&client)); ASSERT_NOT_NULL(client); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + client->socket_fd = TAKE_FD(socket_fd[0]); + + /* Set a fake socket address, as the client will never call dhcp_network_bind_raw_socket() when + * socket_fd is set. */ + client->link.ll = (struct sockaddr_ll) { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_IP), + .sll_ifindex = 42, + .sll_hatype = ARPHRD_ETHER, + .sll_halen = bcast_addr.length, + }; + memcpy(client->link.ll.sll_addr, bcast_addr.bytes, bcast_addr.length); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 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)); + ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); ASSERT_OK(sd_dhcp_client_start(client)); client->xid = 2; From a192c67630b7ed52fdc4c2cbb9661123af161dd3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 17 May 2026 03:20:45 +0900 Subject: [PATCH 1810/2155] ci/alpine: do not install util-linux-login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reasons, after util-linux is bumped from 2.41.4-r0 to 2.42-r0, the 'su' command from util-linux-login seems to not correctly run commands in https://github.com/jirutka/setup-alpine/blob/v1.4.1/alpine.sh and causes the following spurious failure: ``` 2026-05-15T21:19:15.6539432Z ##[group]Set up user runner 2026-05-15T21:19:15.6981963Z /bin/sh: line 0: ��: not found 2026-05-15T21:19:15.6982503Z /bin/sh: line 1: ␡ELF␂␁␁␃: not found 2026-05-15T21:19:15.6985788Z /bin/sh: line 10: ␒␐␆␒B␈␒�␄␒y␄␒�␁␒␞␇␒:␁␒�␃␒�␄␒@␁␒9␈␒?␆␒␚␈␒x: not found 2026-05-15T21:19:15.7010731Z /bin/sh: line 33: can't open ␂␒-␂␒�: no such file 2026-05-15T21:19:15.7016026Z /bin/sh: line 33: syntax error: unexpected word (expecting ")") 2026-05-15T21:19:15.7049583Z 2026-05-15T21:19:15.7050199Z ␛[1;31mError occurred at line 338:␛[0m 2026-05-15T21:19:15.7050830Z 335 | echo 'permit nopass keepenv $SUDO_USER' | tee /etc/doas.d/root.conf 2026-05-15T21:19:15.7051287Z 336 | fi 2026-05-15T21:19:15.7051549Z 337 | SHELL 2026-05-15T21:19:15.7052039Z ␛[1;31m> 338 | abin/"$INPUT_SHELL_NAME" --root /.setup.sh␛[0m 2026-05-15T21:19:15.7052506Z 339 | 2026-05-15T21:19:15.7052796Z 340 | rm .setup.sh 2026-05-15T21:19:15.7053172Z 341 | endgroup 2026-05-15T21:19:15.7096322Z ##[error]Error occurred at line 338: abin/"$INPUT_SHELL_NAME" --root /.setup.sh (see the job log for more information) 2026-05-15T21:19:15.7101400Z ##[error]Process completed with exit code 1. ``` Let's not install the package. It seems no command provided by the package is used. --- .github/workflows/unit-tests-musl.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml index a5b619796f2b6..e2a7908875f37 100644 --- a/.github/workflows/unit-tests-musl.yml +++ b/.github/workflows/unit-tests-musl.yml @@ -92,7 +92,6 @@ jobs: tpm2-tss-tcti-device tzdata util-linux-dev - util-linux-login util-linux-misc utmps-dev valgrind-dev From 149adb2fdce0d9a40f9332ecb1a48a486fce5194 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 18 Apr 2026 04:31:37 +0900 Subject: [PATCH 1811/2155] dhcp-message-dump: introduce dump_dhcp_message() It dumps DHCP messages in a human friendly format. It will be used in networkctl in a later commit. --- src/libsystemd-network/dhcp-message-dump.c | 1178 ++++++++++++++++++++ src/libsystemd-network/dhcp-message-dump.h | 11 + src/libsystemd-network/meson.build | 1 + src/libsystemd-network/test-dhcp-message.c | 64 ++ 4 files changed, 1254 insertions(+) create mode 100644 src/libsystemd-network/dhcp-message-dump.c create mode 100644 src/libsystemd-network/dhcp-message-dump.h diff --git a/src/libsystemd-network/dhcp-message-dump.c b/src/libsystemd-network/dhcp-message-dump.c new file mode 100644 index 0000000000000..3c9283351a31e --- /dev/null +++ b/src/libsystemd-network/dhcp-message-dump.c @@ -0,0 +1,1178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ansi-color.h" +#include "arphrd-util.h" +#include "dhcp-message-dump.h" +#include "dhcp-protocol.h" +#include "dhcp-route.h" +#include "dns-resolver-internal.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "format-table.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +static int iovec_to_hex(const struct iovec *iov, char **ret) { + assert(iov); + assert(ret); + + _cleanup_free_ char *str = new(char, iov->iov_len * 3); + if (!str) + return log_oom(); + + char *p = str; + FOREACH_ARRAY(v, ((uint8_t*) iov->iov_base), iov->iov_len) { + if (p != str) + *p++ = ':'; + *p++ = hexchar(*v >> 4); + *p++ = hexchar(*v & 0x0f); + } + *p = '\0'; + + *ret = TAKE_PTR(str); + return 0; +} + +static int iovw_to_strv(const struct iovec_wrapper *iovw, char ***ret) { + int r; + + assert(iovw); + assert(ret); + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + _cleanup_free_ char *escaped = cescape_length(iov->iov_base, iov->iov_len); + if (!escaped) + return log_oom(); + + r = strv_consume_with_size(&strv, &n_strv, TAKE_PTR(escaped)); + if (r < 0) + return log_oom(); + } + + *ret = TAKE_PTR(strv); + return 0; +} + +static void table_apply_flags(Table *table, DumpDHCPMessageFlag flags) { + assert(table); + + table_set_header(table, FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_FULL)) + table_set_width(table, 0); +} + +static int dump_dhcp_option_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("code", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_SUBOPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION); + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, tlv->entries) + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(iov, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, PTR_TO_UINT32(tagp), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +static int dump_dhcp_option_vendor_identifying_vendor_class(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, tlv->entries) { + uint32_t enterprise_number = PTR_TO_UINT32(key); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_get_alloc(tlv, enterprise_number, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read vendor class of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovec_split(&iov, /* length_size= */ 1, &iovw); + if (r < 0) + return log_error_errno(r, "Failed to parse vendor class of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_strv_free_ char **strv = NULL; + r = iovw_to_strv(&iovw, &strv); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, enterprise_number, + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Identifying Vendor Class:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +static int dump_dhcp_option_vendor_identifying_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "code", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0, (size_t) 1); + + for (unsigned i = 0; i <= 1; i++) { + TableCell *cell = table_get_cell(table, 0, i); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + } + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, tlv->entries) { + uint32_t enterprise_number = PTR_TO_UINT32(key); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_get_alloc(tlv, enterprise_number, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read vendor specific information of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_(tlv_done) TLV sub_tlv = TLV_INIT(TLV_DHCP4_SUBOPTION); + r = tlv_parse(&sub_tlv, &iov); + if (r < 0) + return log_error_errno(r, "Failed to parse vendor specific information of enterprise number %"PRIu32": %m", enterprise_number); + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, sub_tlv.entries) { + uint32_t code = PTR_TO_UINT32(tagp); + + FOREACH_ARRAY(i, iovw->iovec, iovw->count) { + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(i, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, enterprise_number, + TABLE_UINT32, code, + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + } + } + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Identifying Vendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +typedef enum DHCPOptionType { + DHCP_OPTION_TYPE_AUTO, + DHCP_OPTION_TYPE_HEX, + DHCP_OPTION_TYPE_FLAG, + DHCP_OPTION_TYPE_BOOL, + DHCP_OPTION_TYPE_UINT8, + DHCP_OPTION_TYPE_UINT16, + DHCP_OPTION_TYPE_TIME, + DHCP_OPTION_TYPE_STRING, + DHCP_OPTION_TYPE_ADDRESS, + _DHCP_OPTION_TYPE_AUTO_MAX, + DHCP_OPTION_TYPE_SIP = _DHCP_OPTION_TYPE_AUTO_MAX, + DHCP_OPTION_TYPE_FQDN, + DHCP_OPTION_TYPE_ROUTE, + DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA, + DHCP_OPTION_TYPE_SEARCH_DOMAINS, + DHCP_OPTION_TYPE_DNR, + DHCP_OPTION_TYPE_6RD, + DHCP_OPTION_TYPE_TBD, + _DHCP_OPTION_TYPE_MAX, + _DHCP_OPTION_TYPE_INVALID = -EINVAL, +} DHCPOptionType; + +static const char * const dhcp_option_type_table[_DHCP_OPTION_TYPE_AUTO_MAX] = { + [DHCP_OPTION_TYPE_AUTO] = "auto", + [DHCP_OPTION_TYPE_HEX] = "hex", + [DHCP_OPTION_TYPE_FLAG] = "flag", + [DHCP_OPTION_TYPE_BOOL] = "bool", + [DHCP_OPTION_TYPE_UINT8] = "uint8", + [DHCP_OPTION_TYPE_UINT16] = "uint16", + [DHCP_OPTION_TYPE_TIME] = "time", + [DHCP_OPTION_TYPE_STRING] = "string", + [DHCP_OPTION_TYPE_ADDRESS] = "address", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_option_type, DHCPOptionType); + +static DHCPOptionType dhcp_option_type_from_code(uint8_t code) { + switch (code) { + case SD_DHCP_OPTION_PAD: + return -EINVAL; + case SD_DHCP_OPTION_SUBNET_MASK: + case SD_DHCP_OPTION_ROUTER: + case SD_DHCP_OPTION_TIME_SERVER: + case SD_DHCP_OPTION_NAME_SERVER: + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: + case SD_DHCP_OPTION_LOG_SERVER: + case SD_DHCP_OPTION_QUOTES_SERVER: + case SD_DHCP_OPTION_LPR_SERVER: + case SD_DHCP_OPTION_IMPRESS_SERVER: + case SD_DHCP_OPTION_RLP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_HOST_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_BOOT_FILE_SIZE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_MERIT_DUMP_FILE: + case SD_DHCP_OPTION_DOMAIN_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_SWAP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_ROOT_PATH: + case SD_DHCP_OPTION_EXTENSION_FILE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_FORWARD: + case SD_DHCP_OPTION_SOURCE_ROUTE: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_DEFAULT_IP_TTL: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_MTU_TIMEOUT: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_MTU_INTERFACE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_MTU_SUBNET: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_BROADCAST: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_MASK_DISCOVERY: + case SD_DHCP_OPTION_MASK_SUPPLIER: + case SD_DHCP_OPTION_ROUTER_DISCOVERY: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_ROUTER_REQUEST: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_TRAILERS: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_ARP_TIMEOUT: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_ETHERNET: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_DEFAULT_TCP_TTL: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_KEEPALIVE_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_KEEPALIVE_DATA: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_NIS_DOMAIN: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_SERVER: + case SD_DHCP_OPTION_NTP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + return DHCP_OPTION_TYPE_TBD; + case SD_DHCP_OPTION_NETBIOS_NAME_SERVER: + case SD_DHCP_OPTION_NETBIOS_DIST_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_NETBIOS_NODE_TYPE: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_NETBIOS_SCOPE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_X_WINDOW_FONT: + case SD_DHCP_OPTION_X_WINDOW_MANAGER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_ERROR_MESSAGE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_RENEWAL_TIME: + case SD_DHCP_OPTION_REBINDING_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NETWARE_IP_DOMAIN: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_DOMAIN_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_SERVER_ADDR: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_BOOT_SERVER_NAME: + case SD_DHCP_OPTION_BOOT_FILENAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_HOME_AGENT_ADDRESS: + case SD_DHCP_OPTION_SMTP_SERVER: + case SD_DHCP_OPTION_POP3_SERVER: + case SD_DHCP_OPTION_NNTP_SERVER: + case SD_DHCP_OPTION_WWW_SERVER: + case SD_DHCP_OPTION_FINGER_SERVER: + case SD_DHCP_OPTION_IRC_SERVER: + case SD_DHCP_OPTION_STREETTALK_SERVER: + case SD_DHCP_OPTION_STDA_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_USER_CLASS: + return DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA; + case SD_DHCP_OPTION_RAPID_COMMIT: + return DHCP_OPTION_TYPE_FLAG; + case SD_DHCP_OPTION_FQDN: + return DHCP_OPTION_TYPE_FQDN; + case SD_DHCP_OPTION_POSIX_TIMEZONE: + case SD_DHCP_OPTION_TZDB_TIMEZONE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_DOMAIN_SEARCH: + return DHCP_OPTION_TYPE_SEARCH_DOMAINS; + case SD_DHCP_OPTION_SIP_SERVER: + return DHCP_OPTION_TYPE_SIP; + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + return DHCP_OPTION_TYPE_TBD; + case SD_DHCP_OPTION_MUD_URL: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_V4_DNR: + return DHCP_OPTION_TYPE_DNR; + case SD_DHCP_OPTION_6RD: + return DHCP_OPTION_TYPE_6RD; + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_END: + return -EINVAL; + default: + return DHCP_OPTION_TYPE_HEX; + } +} + +static int dump_dhcp_option_hex(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %u: %m", code); + + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(&iov, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_flag(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + r = dhcp_message_get_option_flag(message, code); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to dump DHCP option %u as flag: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_BOOLEAN, true); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_bool(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint8_t u; + r = dhcp_message_get_option_u8(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as boolean: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_BOOLEAN, !!u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_uint8(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint8_t u; + r = dhcp_message_get_option_u8(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as uint8: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_UINT8, u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_uint16(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint16_t u; + r = dhcp_message_get_option_u16(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as uint16: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_UINT16, u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_time(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + usec_t usec; + r = dhcp_message_get_option_sec(message, code, /* max_as_infinity= */ true, &usec); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as time: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_TIMESPAN, usec); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_string(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ char *str = NULL; + r = dhcp_message_get_option_string(message, code, &str); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as string: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_address(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ struct in_addr *addrs = NULL; + size_t n_addrs; + r = dhcp_message_get_option_addresses(message, code, &n_addrs, &addrs); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as address: %m", code); + } + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(a, addrs, n_addrs) { + r = strv_extend_with_size(&strv, &n_strv, IN4_ADDR_TO_STRING(a)); + if (r < 0) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_sip(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_SIP_SERVER); + + _cleanup_strv_free_ char **strv = NULL; + if (dhcp_message_get_option_domains(message, code, &strv) < 0) + return dump_dhcp_option_address(table, message, code, /* fallback= */ true); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_fqdn(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_FQDN); + + _cleanup_free_ char *fqdn = NULL; + uint8_t flags; + if (dhcp_message_get_option_fqdn(message, &flags, &fqdn) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code)); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "flags: 0x%x, fqdn: %s", flags, fqdn); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_route(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes; + if (dhcp_message_get_option_routes(message, code, &n_routes, &routes) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(route, routes, n_routes) { + r = strv_extendf_with_size(&strv, &n_strv, "%s via %s", + IN4_ADDR_PREFIX_TO_STRING(&route->dst_addr, route->dst_prefixlen), + IN4_ADDR_TO_STRING(&route->gw_addr)); + if (r < 0) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_length_prefixed_data(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + if (dhcp_message_get_option_length_prefixed_data(message, code, /* length_size= */ 1, &iovw) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + r = iovw_to_strv(&iovw, &strv); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_tbd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + if (!dhcp_message_has_option(message, code)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "The DHCP message does not have option %u.", code); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, "See below."); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_search_domains(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_strv_free_ char **strv = NULL; + if (dhcp_message_get_option_domains(message, code, &strv) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_dnr(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_V4_DNR); + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + + if (dhcp_message_get_option_dnr(message, &n_resolvers, &resolvers) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + r = dns_resolvers_to_dot_strv(resolvers, n_resolvers, &strv); + if (r < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_6rd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_6RD); + + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + size_t n_br_addresses; + _cleanup_free_ struct in_addr *br_addresses = NULL; + if (dhcp_message_get_option_6rd(message, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_free_ char *str = asprintf_safe("ipv4masklen: %u, prefix: %s, br_addresses: ", + ipv4masklen, IN6_ADDR_PREFIX_TO_STRING(&prefix, prefixlen)); + if (!str) + return log_oom(); + + assert(n_br_addresses > 0); + _cleanup_free_ char *br_addresses_str = NULL; + FOREACH_ARRAY(a, br_addresses, n_br_addresses) + if (!strextend_with_separator(&br_addresses_str, ", ", IN4_ADDR_TO_STRING(a))) + return log_oom(); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code)); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "%s%s", str, br_addresses_str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_one(Table *table, sd_dhcp_message *message, uint8_t code, DHCPOptionType type) { + assert(table); + assert(message); + assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)); + + typedef int (*dump_dhcp_option_t)(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback); + + static const dump_dhcp_option_t functions[_DHCP_OPTION_TYPE_MAX] = { + [DHCP_OPTION_TYPE_HEX] = dump_dhcp_option_hex, + [DHCP_OPTION_TYPE_FLAG] = dump_dhcp_option_flag, + [DHCP_OPTION_TYPE_BOOL] = dump_dhcp_option_bool, + [DHCP_OPTION_TYPE_UINT8] = dump_dhcp_option_uint8, + [DHCP_OPTION_TYPE_UINT16] = dump_dhcp_option_uint16, + [DHCP_OPTION_TYPE_TIME] = dump_dhcp_option_time, + [DHCP_OPTION_TYPE_STRING] = dump_dhcp_option_string, + [DHCP_OPTION_TYPE_ADDRESS] = dump_dhcp_option_address, + [DHCP_OPTION_TYPE_SIP] = dump_dhcp_option_sip, + [DHCP_OPTION_TYPE_FQDN] = dump_dhcp_option_fqdn, + [DHCP_OPTION_TYPE_ROUTE] = dump_dhcp_option_route, + [DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA] = dump_dhcp_option_length_prefixed_data, + [DHCP_OPTION_TYPE_SEARCH_DOMAINS] = dump_dhcp_option_search_domains, + [DHCP_OPTION_TYPE_DNR] = dump_dhcp_option_dnr, + [DHCP_OPTION_TYPE_6RD] = dump_dhcp_option_6rd, + [DHCP_OPTION_TYPE_TBD] = dump_dhcp_option_tbd, + }; + + bool fallback = false; + if (type == DHCP_OPTION_TYPE_AUTO) { + type = dhcp_option_type_from_code(code); + fallback = true; + } + + assert(functions[type]); + return functions[type](table, message, code, fallback); +} + +static int parse_arg(const char *arg, uint8_t *ret_code, DHCPOptionType *ret_type) { + _cleanup_free_ char *buf = NULL; + const char *code_str, *type_str; + int r; + + assert(arg); + + const char *colon = strchr(arg, ':'); + if (colon) { + buf = strndup(arg, colon - arg); + if (!buf) + return log_oom(); + + code_str = buf; + type_str = colon + 1; + } else { + code_str = arg; + type_str = NULL; + } + + uint8_t code; + r = safe_atou8(code_str, &code); + if (r < 0) + return log_error_errno(r, "Failed to parse option code number '%s': %m", code_str); + + if (IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid option code number: %u", code); + + DHCPOptionType type = DHCP_OPTION_TYPE_AUTO; + if (type_str) { + type = dhcp_option_type_from_string(type_str); + if (type < 0) + return log_error_errno(type, "Failed to parse option type '%s': %m", type_str); + } + + if (ret_code) + *ret_code = code; + if (ret_type) + *ret_type = type; + return 0; +} + +static int dump_dhcp_options(sd_dhcp_message *message, char * const *args, DumpDHCPMessageFlag flags) { + int r; + + assert(message); + + _cleanup_(table_unrefp) Table *table = table_new("code", "name", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + bool + has_vendor_specific_information = false, + has_vendor_identifying_vendor_class = false, + has_vendor_identifying_vendor_specific_information = false; + + if (strv_isempty(args)) { + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, message->options.entries) { + uint32_t tag = PTR_TO_UINT32(tagp); + assert(tag > 0); + assert(tag < UINT8_MAX); + + r = dump_dhcp_option_one(table, message, tag, DHCP_OPTION_TYPE_AUTO); + if (r < 0) + return r; + + switch (tag) { + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + has_vendor_specific_information = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + has_vendor_identifying_vendor_class = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + has_vendor_identifying_vendor_specific_information = true; + break; + } + } + } else + STRV_FOREACH(arg, args) { + uint8_t code = SD_DHCP_OPTION_PAD; /* avoid false maybe-uninitialized warning */ + DHCPOptionType type = DHCP_OPTION_TYPE_AUTO; /* avoid false maybe-uninitialized warning */ + r = parse_arg(*arg, &code, &type); + if (r < 0) + return r; + assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)); + + r = dump_dhcp_option_one(table, message, code, type); + if (r < 0) + return r; + + if (type == DHCP_OPTION_TYPE_AUTO) + switch (code) { + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + has_vendor_specific_information = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + has_vendor_identifying_vendor_class = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + has_vendor_identifying_vendor_specific_information = true; + break; + } + } + + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sOptions:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + r = table_print_or_warn(table); + if (r < 0) + return r; + + if (has_vendor_specific_information) { + r = dump_dhcp_option_vendor_specific_information(message, flags); + if (r < 0) + return r; + } + + if (has_vendor_identifying_vendor_class) { + r = dump_dhcp_option_vendor_identifying_vendor_class(message, flags); + if (r < 0) + return r; + } + + if (has_vendor_identifying_vendor_specific_information) { + r = dump_dhcp_option_vendor_identifying_vendor_specific_information(message, flags); + if (r < 0) + return r; + } + + return 0; +} + +static int dump_buffer(Table *table, const char *field, uint8_t *buf, size_t len) { + int r; + + assert(table); + assert(field); + assert(buf); + assert(len > 0); + + uint8_t *nul = memchr(buf, 0, len); + if (nul) + len = nul - buf; + + if (len == 0) + return 0; + + _cleanup_free_ char *str = NULL; + r = make_cstring(buf, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &str); + if (r < 0) + return log_error_errno(r, "Failed to parse buffer for field '%s': %m", field); + + if (isempty(str)) + return 0; + + r = table_add_many( + table, + TABLE_FIELD, field, + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_header(sd_dhcp_message *message, DumpDHCPMessageFlag flags) { + int r; + + assert(message); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + struct hw_addr_data hw_addr; + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return log_error_errno(r, "Failed to get hardware address from DHCP message: %m"); + + struct in_addr yiaddr = { .s_addr = message->header.yiaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Hardware Type", + TABLE_STRING, arphrd_to_name(message->header.htype), + TABLE_FIELD, "Hardware Address", + TABLE_STRING, HW_ADDR_TO_STR(&hw_addr), + TABLE_FIELD, "Client Address", + TABLE_IN_ADDR, &yiaddr); + if (r < 0) + return table_log_add_error(r); + + if (message->header.siaddr != INADDR_ANY) { + struct in_addr siaddr = { .s_addr = message->header.siaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Server Address", + TABLE_IN_ADDR, &siaddr); + if (r < 0) + return table_log_add_error(r); + } + + if (message->header.giaddr != INADDR_ANY) { + struct in_addr giaddr = { .s_addr = message->header.giaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Relay Agent Address", + TABLE_IN_ADDR, &giaddr); + if (r < 0) + return table_log_add_error(r); + } + + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) { + r = dump_buffer(table, "Server Host Name", message->header.sname, sizeof(message->header.sname)); + if (r < 0) + return r; + } + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) { + r = dump_buffer(table, "Boot File Name", message->header.file, sizeof(message->header.file)); + if (r < 0) + return r; + } + + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sHeader:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +int dump_dhcp_message(sd_dhcp_message *m, char * const *args, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + if (strv_isempty(args)) { + r = dump_dhcp_header(m, flags); + if (r < 0) + return r; + + putchar('\n'); + } + + return dump_dhcp_options(m, args, flags); +} diff --git a/src/libsystemd-network/dhcp-message-dump.h b/src/libsystemd-network/dhcp-message-dump.h new file mode 100644 index 0000000000000..6ebd5a2c4654c --- /dev/null +++ b/src/libsystemd-network/dhcp-message-dump.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-message.h" + +typedef enum { + DUMP_DHCP_MESSAGE_LEGEND = 1 << 0, + DUMP_DHCP_MESSAGE_FULL = 1 << 1, +} DumpDHCPMessageFlag; + +int dump_dhcp_message(sd_dhcp_message *message, char * const *args, DumpDHCPMessageFlag flags); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index fed2c98c710dd..ab1ac35409cb8 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -4,6 +4,7 @@ libsystemd_network_sources = files( 'arp-util.c', 'dhcp-client-send.c', 'dhcp-message.c', + 'dhcp-message-dump.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index b38220aef5259..f42a0bfb4f85a 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "dhcp-client-id-internal.h" #include "dhcp-message.h" +#include "dhcp-message-dump.h" #include "dhcp-protocol.h" #include "dhcp-route.h" #include "dns-packet.h" @@ -545,6 +546,9 @@ TEST(dhcp_message) { /* send */ verify_send_udp(m, xid, &hw_addr); verify_send_raw(m, xid, &hw_addr); + + /* dump */ + ASSERT_OK(dump_dhcp_message(m, /* args= */ NULL, DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); } static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { @@ -581,6 +585,8 @@ static void test_domains_one(size_t len, const uint8_t *data, char * const *expe ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1 - (len + 1) / 2, sip + (len + 1) / 2)); ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); ASSERT_TRUE(strv_equal(strv, expected)); + + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("119", "120"), DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); } static void test_domains_fail(size_t len, const uint8_t *data) { @@ -804,6 +810,8 @@ TEST(dnr) { ASSERT_EQ(resolvers[1].port, 33u); ASSERT_NULL(resolvers[1].dohpath); + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("162"), DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); + /* missing DoH path */ static uint8_t invalid[] = { /* length */ @@ -875,4 +883,60 @@ TEST(dnr) { ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EMSGSIZE); } +TEST(dump_vendor) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + /* Vendor Specific Information (43) -- TLV */ + static uint8_t option_43[] = { + 1, 6, 'm', 'o', 'd', 'e', 'm', '1', + 2, 6, 's', 't', 'a', 't', 'u', 's', + 3, 6, 1, 2, 3, 4, 5, 6, + }; + + /* Vendor Class Identifier (60) -- typically string */ + static uint8_t option_60[] = { + 'n', 'e', 't', 'w', 'o', 'r', 'k', 'd', + }; + + /* User Class (77) -- length-prefixed data, typically strings */ + static uint8_t option_77[] = { + 6, 'l', 'a', 'p', 't', 'o', 'p', + 4, 'c', 'o', 'r', 'p', + }; + + /* Vendor-Identifying Vendor Class -- length-prefixed data tagged with enterprise number */ + static uint8_t option_124[] = { + 0x00, 0x00, 0x00, 0x09, /* enterprise number: 9 */ + 13, + 5, 'c', 'i', 's', 'c', 'o', + 6, 'f', 'o', 'o', 'b', 'a', 'r', + 0x00, 0x00, 0x11, 0x8b, /* enterprise number: 4491 */ + 21, + 9, 'd', 'o', 'c', 's', 'i', 's', '3', '.', '0', + 10, 'e', 'R', 'o', 'u', 't', 'e', 'r', '1', '.', '0', + }; + + /* Vendor-Identifying Vendor-Specific Information (125) -- sub TLVs with enterprise number */ + static uint8_t option_125[] = { + 0x00, 0x00, 0x00, 0x09, /* enterprise number: 9 */ + 12, + 1, 4, 'C', 'i', 's', 'c', + 2, 4, 'I', 'O', 'S', 'X', + 0x00, 0x00, 0x11, 0x8b, /* enterprise number: 4491 */ + 16, + 1, 6, 'm', 'o', 'd', 'e', 'm', '1', + 2, 6, 's', 't', 'a', 't', 'u', 's', + }; + + ASSERT_OK(dhcp_message_append_option(m, 43, ELEMENTSOF(option_43), option_43)); + ASSERT_OK(dhcp_message_append_option(m, 60, ELEMENTSOF(option_60), option_60)); + ASSERT_OK(dhcp_message_append_option(m, 77, ELEMENTSOF(option_77), option_77)); + ASSERT_OK(dhcp_message_append_option(m, 124, ELEMENTSOF(option_124), option_124)); + ASSERT_OK(dhcp_message_append_option(m, 125, ELEMENTSOF(option_125), option_125)); + + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("43", "60", "77", "124", "125"), + DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 6cbf8222ca96061f9b6840b1e23d88fb263b1892 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 02:58:52 +0900 Subject: [PATCH 1812/2155] networkctl: add dhcp-lease verb to dump DHCP lease This shows the DHCP message of an acquired DHCP lease. Example: ``` $ networkctl dhcp-lease eth0 Header: KEY VALUE Hardware Type: ETHER Hardware Address: 41:42:43:31:32:33 Client Address: 192.0.2.123 Server Address: 192.0.2.1 Options: CODE NAME DATA 1 subnet mask 255.255.255.0 3 router 192.0.2.1 6 domain name server 192.0.2.1 12 hostname test-node 15 domain name lan 28 broadcast address 192.0.2.255 42 NTP server 192.0.2.11 192.0.2.12 51 lease time 1d 53 message type 5 54 server identifier 192.0.2.1 58 renewal time 11h 5min 38s 59 rebinding time 20h 5min 38s 119 domain search hoge.example.com foo.example.com ``` --- man/networkctl.xml | 116 ++++++++++++++++++++++++++++ shell-completion/bash/networkctl | 2 +- shell-completion/zsh/_networkctl | 3 +- src/network/meson.build | 1 + src/network/networkctl-dhcp-lease.c | 67 ++++++++++++++++ src/network/networkctl-dhcp-lease.h | 6 ++ src/network/networkctl.c | 3 + tools/command_ignorelist | 9 +++ 8 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/network/networkctl-dhcp-lease.c create mode 100644 src/network/networkctl-dhcp-lease.h diff --git a/man/networkctl.xml b/man/networkctl.xml index 561eb932386bf..b436797999e04 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -287,6 +287,122 @@ + + + dhcp-lease + INTERFACE + CODE:FORMAT + … + + + + Show the DHCP message of the acquired lease (i.e. the received DHCPACK message). Takes an + interface name and optional DHCP option codes. If no option code is specified, both the DHCP + message header and all options are shown. If one or more option codes are specified, only the + specified options are shown. + + When called with the option, the entire DHCP message is shown in + JSON format. In that case, any specified option codes are ignored. + + By default, each option is displayed in a human-readable format, depending on the option. + For example, DNS server addresses are shown as a list of IP addresses (e.g. + 192.0.2.1), and the lease time is shown as a human-readable duration (e.g. + 8h). + + Each option code may optionally be followed by a formatter, separated by a colon (e.g. + 42:address). This can be useful if the default formatting is not appropriate. + + + The following formatters are supported: + + + + + Use the default formatting. + + + + + + Show the raw bytes in hexadecimal, separated by colons. + + + + + + Treat the option as having zero-length data and display yes. + + + + + + Treat the option as a 1-byte boolean and display yes or no. + + + + + + Treat the option as a 1-byte unsigned integer. + + + + + + Treat the option as a 2-byte unsigned integer. + + + + + + Treat the option as a 4-byte time value in seconds and display it as a human-readable duration. + + + + + + Treat the option as a string. + + + + + + Treat the option as one or more IPv4 addresses and display them in human-readable form. + + + + + Produces output similar to: + $ networkctl dhcp-lease eth0 +Header: + KEY VALUE + Hardware Type: ETHER +Hardware Address: 41:42:43:31:32:33 + Client Address: 192.0.2.123 + Server Address: 192.0.2.1 + +Options: +CODE NAME DATA + 1 subnet mask 255.255.255.0 + 3 router 192.0.2.1 + 6 domain name server 192.0.2.1 + 12 hostname test-node + 15 domain name lan + 28 broadcast address 192.0.2.255 + 42 NTP server 192.0.2.11 + 192.0.2.12 + 51 lease time 1d + 53 message type 5 + 54 server identifier 192.0.2.1 + 58 renewal time 11h 5min 38s + 59 rebinding time 20h 5min 38s + 119 domain search hoge.example.com + foo.example.com + + + + + + lldp diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl index c16768079e30e..fe186a49a2e7f 100644 --- a/shell-completion/bash/networkctl +++ b/shell-completion/bash/networkctl @@ -51,7 +51,7 @@ _networkctl() { local -A VERBS=( [STANDALONE]='label reload' - [LINKS]='status list lldp delete renew up down forcerenew reconfigure' + [LINKS]='status dhcp-lease list lldp delete renew up down forcerenew reconfigure' [FILES_OR_LINKS]='edit cat' [FILES]='mask unmask' [BOOL]='persistent-storage' diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl index cf072c0fcbba6..c44b346949834 100644 --- a/shell-completion/zsh/_networkctl +++ b/shell-completion/zsh/_networkctl @@ -7,6 +7,7 @@ _networkctl_cmds=( 'list:List existing links' 'status:Show information about the specified links' + 'dhcp-lease:Show DHCP lease' 'lldp:Show Link Layer Discovery Protocol status' 'label:Show address labels' 'delete:Delete virtual netdevs' @@ -26,7 +27,7 @@ local -a _links cmd="${${_networkctl_cmds[(r)$words[1]:*]%%:*}}" case $cmd in - (list|status|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure) + (list|status|dhcp-lease|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure) for link in ${(f)"$(_call_program links networkctl list --no-legend)"}; do _links+=($link[(w)2]:$link); done if [[ -n "$_links" ]]; then _describe -t links 'links' _links $( [[ $cmd == (edit|cat) ]] && print -- -P@ ) diff --git a/src/network/meson.build b/src/network/meson.build index 7370d011d0a22..0319102492590 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -128,6 +128,7 @@ networkctl_sources = files( 'networkctl-address-label.c', 'networkctl-config-file.c', 'networkctl-description.c', + 'networkctl-dhcp-lease.c', 'networkctl-dump-util.c', 'networkctl-journal.c', 'networkctl-link-info.c', diff --git a/src/network/networkctl-dhcp-lease.c b/src/network/networkctl-dhcp-lease.c new file mode 100644 index 0000000000000..465bff0979926 --- /dev/null +++ b/src/network/networkctl-dhcp-lease.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-netlink.h" +#include "sd-varlink.h" + +#include "dhcp-message-dump.h" +#include "log.h" +#include "networkctl.h" +#include "networkctl-dhcp-lease.h" +#include "networkctl-link-info.h" +#include "networkctl-util.h" +#include "strv.h" + +int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + /* networkctl dhcp-lease INTERFACE [CODE[:FORMAT] ...] */ + assert(argc >= 2); + + pager_open(arg_pager_flags); + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + + const char *ifname = argv[1]; + + _cleanup_(link_info_array_freep) LinkInfo *link = NULL; + r = acquire_link_info(vl, rtnl, STRV_MAKE(ifname), &link); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; /* already logged in acquire_link_info(). */ + if (r > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Interface name '%s' matches multiple interfaces.", ifname); + + if (!link->dhcp_message) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "Interface '%s' does not have DHCPv4 lease.", link->name); + + if (sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = dhcp_message_build_json(link->dhcp_message, &v); + if (r < 0) + return log_error_errno(r, "Failed to build JSON variant from DHCP message: %m"); + + r = sd_json_variant_dump(v, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump JSON variant: %m"); + + return 0; + } + + DumpDHCPMessageFlag flags = 0; + SET_FLAG(flags, DUMP_DHCP_MESSAGE_LEGEND, arg_legend); + SET_FLAG(flags, DUMP_DHCP_MESSAGE_FULL, arg_full); + + return dump_dhcp_message(link->dhcp_message, strv_skip(argv, 2), flags); +} diff --git a/src/network/networkctl-dhcp-lease.h b/src/network/networkctl-dhcp-lease.h new file mode 100644 index 0000000000000..f068df3b5ce58 --- /dev/null +++ b/src/network/networkctl-dhcp-lease.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index bdf487bf2e803..bf74f10a4a48e 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -12,6 +12,7 @@ #include "networkctl.h" #include "networkctl-address-label.h" #include "networkctl-config-file.h" +#include "networkctl-dhcp-lease.h" #include "networkctl-list.h" #include "networkctl-lldp.h" #include "networkctl-misc.h" @@ -42,6 +43,8 @@ VERB_SCOPE(, verb_list_links, "list", "[PATTERN... "List links"); VERB_SCOPE(, verb_link_status, "status", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, "Show link status"); +VERB_SCOPE(, verb_dhcp_lease, "dhcp-lease", "INTERFACE [CODE[:FORMAT]...]", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Show DHCP lease"); VERB_SCOPE(, verb_link_lldp_status, "lldp", "[PATTERN...]", VERB_ANY, VERB_ANY, 0, "Show LLDP neighbors"); VERB_SCOPE(, verb_list_address_labels, "label", NULL, 1, 1, 0, diff --git a/tools/command_ignorelist b/tools/command_ignorelist index 548acff8bd034..cb256d664726a 100644 --- a/tools/command_ignorelist +++ b/tools/command_ignorelist @@ -51,6 +51,15 @@ logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="Kill logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="InhibitDelayMaxSec="] machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="PRETTY_HOSTNAME="] machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="ICON_NAME="] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="auto"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="hex"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="flag"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="bool"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint8"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint16"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="time"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="string"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="address"] os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="NAME="] os-release.xml ./refsect1[title="Options"]/refsect2[title="Information about the version of the operating system"]/variablelist/varlistentry[term="VERSION="] os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="ID="] From ddd535a4247c1201ae36e053980a16ce4f29804a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 17 May 2026 21:03:39 +0900 Subject: [PATCH 1813/2155] NEWS: mention new 'networkctl dhcp-lease' command --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index bc7ee1e2bf702..4e2166feffbd7 100644 --- a/NEWS +++ b/NEWS @@ -78,6 +78,12 @@ CHANGES WITH 261 in spe: configuration extensions merged from the main system itself cannot be used to modify the resources which are used in the early boot. + Changes in systemd-networkd and networkctl: + + * A new 'networkctl dhcp-lease INTERFACE' command has been added to + dump acquired DHCP leases. This may be useful for inspecting the + DHCP options provided by the server. + CHANGES WITH 260: Feature Removals and Incompatible Changes: From 89cf6d81f78fb140f1527e65a56003ec9619060a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 16:19:17 +0900 Subject: [PATCH 1814/2155] dhcp-client-send: use dhcp_message_send_{udp,raw}() --- src/libsystemd-network/dhcp-client-send.c | 49 +++++------------------ 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index 5fe804db60fc9..89bae11a691e4 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -10,9 +10,7 @@ #include "dhcp-message.h" #include "dhcp-network.h" #include "fd-util.h" -#include "iovec-wrapper.h" #include "ip-util.h" -#include "socket-util.h" static int client_get_socket(sd_dhcp_client *client, int domain) { int r, d, fd; @@ -116,39 +114,16 @@ static int client_send_raw( fd_close = fd; } - _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; - r = dhcp_message_build(message, &payload); - if (r < 0) - return r; - - struct iphdr ip; - struct udphdr udp; - r = udp_packet_build( + r = dhcp_message_send_raw( + message, + fd, + client->ifindex, INADDR_ANY, client->port, + &client->bcast_addr, INADDR_BROADCAST, client->server_port, - client->ip_service_type, - &payload, - &ip, - &udp); - if (r < 0) - return r; - - _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; - r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); - if (r < 0) - return r; - - r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); - if (r < 0) - return r; - - r = iovw_put_iovw(&iovw, &payload); - if (r < 0) - return r; - - r = dhcp_network_send_raw_socket(fd, &client->link, &iovw); + client->ip_service_type); if (r < 0) return r; @@ -196,16 +171,12 @@ static int client_send_udp( fd_close = fd; } - _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; - r = dhcp_message_build(message, &payload); - if (r < 0) - return r; - - r = dhcp_network_send_udp_socket( + r = dhcp_message_send_udp( + message, fd, + INADDR_ANY, client->lease->server_address, - client->server_port, - &payload); + client->server_port); if (r < 0) return r; From 0c69b247410ac265297454bf6f37c55d43d3e984 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 3 May 2026 03:24:41 +0900 Subject: [PATCH 1815/2155] dhcp-client-send: move/update BPF code and socket initialization from dhcp-network.c - BPF code now supports the case when IP header has IP packet options, - also set socket priority to UDP socket. Co-developed-by: Google Gemini Pro --- src/libsystemd-network/dhcp-client-send.c | 230 ++++++++++++++++++++-- src/libsystemd-network/dhcp-network.c | 204 +------------------ src/libsystemd-network/dhcp-network.h | 15 -- 3 files changed, 211 insertions(+), 238 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index 89bae11a691e4..f4f3b52a6598d 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include "sd-event.h" @@ -8,9 +9,215 @@ #include "dhcp-client-send.h" #include "dhcp-lease-internal.h" /* IWYU pragma: keep */ #include "dhcp-message.h" -#include "dhcp-network.h" +#include "errno-util.h" #include "fd-util.h" #include "ip-util.h" +#include "socket-util.h" +#include "unaligned.h" + +/* The minimal DHCP packet size without IP header: + * UDP header (8 bytes) + DHCP header (without options) */ +#define DHCP_MINIMUM_UDP_SIZE (sizeof(struct udphdr) + sizeof(struct DHCPMessageHeader)) + +static int client_set_bpf(sd_dhcp_client *client, int fd) { + assert(client); + assert(fd >= 0); + + size_t hlen = 0; + uint32_t mac_hi = 0; + uint16_t mac_lo = 0; + if (client->arp_type != ARPHRD_INFINIBAND) + hlen = client->hw_addr.length; + if (hlen == ETH_ALEN) { + mac_hi = unaligned_read_be32(client->hw_addr.bytes); + mac_lo = unaligned_read_be16(client->hw_addr.bytes + 4); + } + + struct sock_filter filter[] = { + /* 1. Basic packet length check. + * Check against the minimum possible length. + * Note, BPF_MSH extracts the lower 4 bits (IHL) and multiplies by 4 to get the byte length, + * it needs to be combined with BPF_LDX. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length in bytes */ + BPF_STMT(BPF_MISC + BPF_TXA, 0), /* A <- X */ + BPF_STMT(BPF_ALU + BPF_ADD + BPF_K, DHCP_MINIMUM_UDP_SIZE), /* A += UDP header + DHCP header */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A (minimal DHCP packet size) */ + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_X, 0, 1, 0), /* packet length >= minimal DHCP packet size ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 2. Protocol check (Fixed offset in IPv4 header) */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 3. IP Fragmentation checks. + * When an IP packet is larger than the MTU, it is fragmented into smaller pieces. The UDP + * header is ONLY present in the very first fragment. Since BPF filters are stateless and + * cannot reassemble fragments, we must explicitly drop any packet that is part of a + * fragmented sequence to avoid parsing raw payload data as if it were a UDP/DHCP header. */ + + /* 3a. Check the 'More Fragments' (MF) bit. + * If the bit is set, it means there are more fragments following this one. Hence, the packet + * must be dropped. */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, frag_off)), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? (No more fragments) */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore packet if MF == 1 */ + + /* 3b. Check the 'Fragment Offset' field. + * This indicates the position of this specific fragment relative to the beginning of the + * original, unfragmented packet. If the offset is greater than 0, it means this is a + * subsequent fragment (e.g., the 2nd or later piece), hence it must be dropped. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, frag_off)), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? (This is the first fragment) */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore packet if Offset != 0 */ + + /* 4. Variable Offset Processing (Support for IP Options) + * Load the IP header length again. It will be used BPF_IND below. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length in bytes */ + + /* 4a. Check UDP destination port using indirect load (X + offset) */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, dest)), /* A <- (UDP destination port) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->port, 1, 0), /* UDP destination port == 68 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4b. Check DHCP operation code (op) using indirect load (X + UDP header len + op offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, op)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4c. Check hardware type using indirect load (X + UDP header len + htype offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, htype)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->arp_type, 1, 0), /* htype == client->arp_type ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4d. Check message xid using indirect load (X + UDP header len + xid offset) */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, xid)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->xid, 1, 0), /* xid == client->xid ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4e. Check hardware address length using indirect load (X + UDP header len + hlen offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, hlen)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hlen, 1, 0), /* hlen == expected hlen ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4f. Check hardware address when the hardware address length is 6 (ETH_ALEN) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 6), /* hlen == ETH_ALEN ? */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, chaddr)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac_hi, 1, 0), /* first 4 bytes of chaddr == mac_hi ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, chaddr) + 4), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac_lo, 1, 0), /* next 2 bytes of chaddr == mac_lo ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4g. Check DHCP magic cookie using indirect load (X + UDP header len + magic cookie offset) */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, magic)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* All checks passed, accept the entire packet. */ + BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */ + }; + + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + + return RET_NERRNO(setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog))); +} + +static int client_open_raw_socket(sd_dhcp_client *client) { + int r; + + assert(client); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + /* While bind() with sockaddr_ll is strictly sufficient for AF_PACKET, we also set SO_BINDTOIFINDEX + * to initialize the kernel's sk_bound_dev_if state. This ensures compatibility with cgroup/eBPF + * filters and maintains consistency. */ + r = socket_bind_to_ifindex(fd, client->ifindex); + if (r < 0) + return r; + + r = client_set_bpf(client, fd); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, client->socket_priority); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_PACKET, PACKET_AUXDATA, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = client->ifindex, + }; + + if (bind(fd, &sa.sa, sockaddr_ll_len(&sa.ll)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +static int client_open_udp_socket(sd_dhcp_client *client) { + int r; + + assert(client); + assert(client->lease); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = socket_bind_to_ifindex(fd, client->ifindex); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, client->socket_priority); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, client->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(client->port), + .in.sin_addr.s_addr = client->lease->address, + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} static int client_get_socket(sd_dhcp_client *client, int domain) { int r, d, fd; @@ -98,20 +305,9 @@ static int client_send_raw( 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, - /* so_priority_set= */ true, - client->socket_priority); + fd = fd_close = client_open_raw_socket(client); if (fd < 0) return fd; - - fd_close = fd; } r = dhcp_message_send_raw( @@ -160,15 +356,9 @@ static int client_send_udp( 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); + fd = fd_close = client_open_udp_socket(client); if (fd < 0) return fd; - - fd_close = fd; } r = dhcp_message_send_udp( diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c index c78f49159f7e4..39b001a647774 100644 --- a/src/libsystemd-network/dhcp-network.c +++ b/src/libsystemd-network/dhcp-network.c @@ -3,187 +3,13 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include -#include -#include -#include -#include +#include #include "dhcp-network.h" #include "dhcp-protocol.h" -#include "ether-addr-util.h" #include "fd-util.h" #include "iovec-wrapper.h" #include "socket-util.h" -#include "unaligned.h" - -static int _bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - assert(ifindex > 0); - assert(link); - assert(hw_addr); - assert(bcast_addr); - assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); - - switch (arp_type) { - case ARPHRD_ETHER: - assert(hw_addr->length == ETH_ALEN); - assert(bcast_addr->length == ETH_ALEN); - break; - case ARPHRD_INFINIBAND: - assert(hw_addr->length == 0); - assert(bcast_addr->length == INFINIBAND_ALEN); - break; - default: - assert_not_reached(); - } - - struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ - BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ - BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (uint8_t) hw_addr->length, 1, 0), /* address length == hw_addr->length ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - - /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally - * compare chaddr for ETH_ALEN bytes. */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 8), /* A (the MAC address length) == ETH_ALEN ? */ - BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(hw_addr->bytes)), /* X <- 4 bytes of client's MAC */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(hw_addr->bytes + 4)), /* X <- remainder of client's MAC */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */ - }; - struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = filter - }; - _cleanup_close_ int s = -EBADF; - int r; - - s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); - if (r < 0) - return r; - - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); - if (r < 0) - return r; - - if (so_priority_set) { - r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority); - if (r < 0) - return r; - } - - link->ll = (struct sockaddr_ll) { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_IP), - .sll_ifindex = ifindex, - .sll_hatype = htobe16(arp_type), - .sll_halen = bcast_addr->length, - }; - /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ - memcpy(link->ll.sll_addr, bcast_addr->bytes, bcast_addr->length); - - r = bind(s, &link->sa, sockaddr_ll_len(&link->ll)); - if (r < 0) - return -errno; - - return TAKE_FD(s); -} - -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - static struct hw_addr_data default_eth_bcast = { - .length = ETH_ALEN, - .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, - }, default_ib_bcast = { - .length = INFINIBAND_ALEN, - .infiniband = { - 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff - }, - }; - - assert(ifindex > 0); - assert(link); - assert(hw_addr); - - switch (arp_type) { - case ARPHRD_ETHER: - return _bind_raw_socket(ifindex, link, xid, - hw_addr, - (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast, - arp_type, port, so_priority_set, so_priority); - - case ARPHRD_INFINIBAND: - return _bind_raw_socket(ifindex, link, xid, - &HW_ADDR_NULL, - (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast, - arp_type, port, so_priority_set, so_priority); - default: - return -EINVAL; - } -} int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { union sockaddr_union src = { @@ -266,31 +92,3 @@ int dhcp_network_send_raw_socket( return 0; } - -int dhcp_network_send_udp_socket( - int fd, - be32_t address, - uint16_t port, - const struct iovec_wrapper *iovw) { - - assert(fd >= 0); - assert(!iovw_isempty(iovw)); - - union sockaddr_union dest = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(port), - .in.sin_addr.s_addr = address, - }; - - struct msghdr mh = { - .msg_name = &dest.sa, - .msg_namelen = sizeof(dest.in), - .msg_iov = iovw->iovec, - .msg_iovlen = iovw->count, - }; - - if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} diff --git a/src/libsystemd-network/dhcp-network.h b/src/libsystemd-network/dhcp-network.h index 6bef8ddaf9cbf..117e3f4dc8c39 100644 --- a/src/libsystemd-network/dhcp-network.h +++ b/src/libsystemd-network/dhcp-network.h @@ -4,16 +4,6 @@ #include "sd-forward.h" #include "sparse-endian.h" -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority); int dhcp_network_bind_udp_socket( int ifindex, be32_t address, @@ -23,8 +13,3 @@ int dhcp_network_send_raw_socket( int fd, const union sockaddr_union *link, const struct iovec_wrapper *iovw); -int dhcp_network_send_udp_socket( - int fd, - be32_t address, - uint16_t port, - const struct iovec_wrapper *iovw); From a72f23d2b8fc356eaa640e5edb48fb7a79a74df7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 3 May 2026 14:50:13 +0900 Subject: [PATCH 1816/2155] sd-dhcp-client: drop unused data in sd_dhcp_client With the previous commit, now sd_dhcp_client.link is not used anymore. Let's drop it. --- src/libsystemd-network/dhcp-client-internal.h | 1 - src/libsystemd-network/fuzz-dhcp-client.c | 11 ----------- src/libsystemd-network/test-dhcp-client.c | 11 ----------- 3 files changed, 23 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index ccae831284326..bd9308e2d1d78 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -44,7 +44,6 @@ struct sd_dhcp_client { uint16_t port; uint16_t server_port; - union sockaddr_union link; sd_event_source *receive_message; bool request_broadcast; Set *req_opts; diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index ad289754ed1cd..0737e0c2cd9b8 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -40,17 +40,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); client->socket_fd = TAKE_FD(socket_fd[0]); - /* Set a fake socket address, as the client will never call dhcp_network_bind_raw_socket() when - * socket_fd is set. */ - client->link.ll = (struct sockaddr_ll) { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_IP), - .sll_ifindex = 42, - .sll_hatype = ARPHRD_ETHER, - .sll_halen = bcast_addr.length, - }; - memcpy(client->link.ll.sll_addr, bcast_addr.bytes, bcast_addr.length); - ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 5407ccb0b3208..2098470becbcd 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -82,17 +82,6 @@ static void setup(sd_event_io_handler_t io_handler, sd_dhcp_client_callback_t cl client->socket_fd = TAKE_FD(socket_fd[0]); - /* Set a fake socket address, as the client will never call dhcp_network_bind_raw_socket() when - * socket_fd is set. */ - client->link.ll = (struct sockaddr_ll) { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_IP), - .sll_ifindex = 42, - .sll_hatype = ARPHRD_ETHER, - .sll_halen = bcast_addr.length, - }; - memcpy(client->link.ll.sll_addr, bcast_addr.bytes, bcast_addr.length); - ASSERT_OK(sd_dhcp_client_attach_event(client, e, SD_EVENT_PRIORITY_NORMAL)); 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)); From 42c689f63de53089e573cbb945d84477a91b8248 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 12 Mar 2026 05:19:25 +0900 Subject: [PATCH 1817/2155] sd-device: logs about refused property Follow-up for a62cd5a153ffe18c27aff02685ed75c5bc4509a2. --- src/libsystemd/sd-device/sd-device.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index ae44d0c86fd96..9d9ac7d4300f9 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -15,6 +15,7 @@ #include "dirent-util.h" #include "env-util.h" #include "errno-util.h" +#include "escape.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -108,8 +109,15 @@ int device_add_property_aux(sd_device *device, const char *key, const char *valu assert(device); assert(key); - if (!property_is_valid(key, value)) + if (!property_is_valid(key, value)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *escaped_key = cescape(key), + *escaped_value = cescape(strempty(value)); + log_device_debug(device, "sd-device: Refusing invalid property: %s=%s", + strnull(escaped_key), strnull(escaped_value)); + } return -EINVAL; + } if (db) properties = &device->properties_db; From 8e8d3a22b2a6f766f9251a7c8963ca3d1c033788 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 18 May 2026 01:17:42 +0900 Subject: [PATCH 1818/2155] sd-device: use string_is_safe() at one more place --- src/libsystemd/sd-device/sd-device.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 9d9ac7d4300f9..307137b431a8b 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -31,7 +31,6 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "utf8.h" int device_new_aux(sd_device **ret) { sd_device *device; @@ -97,10 +96,10 @@ static bool property_is_valid(const char *key, const char *value) { return true; /* refuse invalid UTF8 and control characters */ - if (!utf8_is_valid(value) || string_has_cc(value, /* ok= */ NULL)) - return false; - - return true; + return string_is_safe(value, + STRING_ALLOW_BACKSLASHES | + STRING_ALLOW_QUOTES | + STRING_ALLOW_GLOBS); } int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db) { From c54decc406ba5ea8cff59f4d435112cf61b2fbea Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 21 Feb 2026 06:14:53 +0900 Subject: [PATCH 1819/2155] ruff: use single quotes for multiline strings --- ruff.toml | 10 ++++- src/boot/generate-hwids-section.py | 8 ++-- src/core/generate-bpf-delegate-configs.py | 4 +- src/test/generate-sym-test.py | 24 +++++------ src/ukify/ukify.py | 14 +++---- .../integration-test-wrapper.py | 40 +++++++++---------- 6 files changed, 53 insertions(+), 47 deletions(-) diff --git a/ruff.toml b/ruff.toml index 6c0ec6ceb84bb..d4b77fb1e724b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,12 @@ target-version = "py39" line-length = 109 -lint.select = ["E", "F", "I", "UP"] +lint.select = ["E", "F", "I", "Q", "UP"] [format] -quote-style = "single" +# The formatter prefers double quotes for multiline quotes, +# Hence, let's make the formatter not change quotations. +quote-style = "preserve" + +[lint.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "single" diff --git a/src/boot/generate-hwids-section.py b/src/boot/generate-hwids-section.py index cfe6aea739aa3..df929af3b764d 100755 --- a/src/boot/generate-hwids-section.py +++ b/src/boot/generate-hwids-section.py @@ -16,13 +16,13 @@ hwids = ukify.parse_hwid_dir(Path(sys.argv[1])) print( - """/* SPDX-License-Identifier: LGPL-2.1-or-later */ + '''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include // NOLINTNEXTLINE(misc-use-internal-linkage) const uint8_t hwids_section_data[] = { - """, + ''', end='', ) @@ -34,9 +34,9 @@ print('') print( - """}; + '''}; // NOLINTNEXTLINE(misc-use-internal-linkage) -const size_t hwids_section_len =""", +const size_t hwids_section_len =''', f'{len(hwids)};', ) diff --git a/src/core/generate-bpf-delegate-configs.py b/src/core/generate-bpf-delegate-configs.py index 200c913b8a826..4e4a54322a480 100755 --- a/src/core/generate-bpf-delegate-configs.py +++ b/src/core/generate-bpf-delegate-configs.py @@ -30,12 +30,12 @@ def print_usage_and_exit() -> None: enumName = '' if output == 'doc': - print("""\ + print('''\ -""") +''') for line in file: line = line.strip() diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index b8d64d623f04f..aa6c916fc7c92 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -120,43 +120,43 @@ def process_source_file(file: IO[str]) -> None: continue -print("""/* SPDX-License-Identifier: LGPL-2.1-or-later */ +print('''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include -""") +''') for header in sys.argv[3:]: with open(header, 'r') as f: if process_header_file(f): print('#include "{}"'.format(header.split('/')[-1])) -print(""" +print(''' /* We want to check deprecated symbols too, without complaining */ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -""") +''') -print(""" +print(''' struct symbol { const char *name; const void *symbol; }; -static struct symbol symbols_from_sym[] = {""") +static struct symbol symbols_from_sym[] = {''') with open(sys.argv[1], 'r') as f: process_sym_file(f) -print(""" {} -}, symbols_from_header[] = {""") +print(''' {} +}, symbols_from_header[] = {''') for header in sys.argv[3:]: with open(header, 'r') as f: print(process_header_file(f), end='') -print(""" {} -}, symbols_from_source[] = {""") +print(''' {} +}, symbols_from_source[] = {''') for dirpath, _, filenames in sorted(os.walk(sys.argv[2])): for filename in sorted(filenames): @@ -168,7 +168,7 @@ def process_source_file(file: IO[str]) -> None: with p.open('rt') as f: process_source_file(f) -print(""" {} +print(''' {} }; static int sort_callback(const void *a, const void *b) { @@ -239,4 +239,4 @@ def process_source_file(file: IO[str]) -> None: } return n_error == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -}""") +}''') diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 03d475e5e763e..11374f607dd51 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -599,7 +599,7 @@ def sign(input_f: str, output_f: str, opts: UkifyConfig) -> None: ) cmd = [ tool, - "sign", + 'sign', '--private-key', opts.sb_key, '--certificate', opts.sb_cert, *( @@ -1305,15 +1305,15 @@ def parse_efifw_dir(path: Path) -> bytes: return efifw_blob -STUB_SBAT = """\ +STUB_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/ -""" +''' -ADDON_SBAT = """\ +ADDON_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html -""" +''' def make_uki(opts: UkifyConfig) -> None: @@ -2326,11 +2326,11 @@ def create_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( description='Build and sign Unified Kernel Images', usage='\n ' - + textwrap.dedent("""\ + + textwrap.dedent('''\ ukify {b}build{e} [--linux=LINUX] [--initrd=INITRD] [options…] ukify {b}genkey{e} [options…] ukify {b}inspect{e} FILE… [options…] - """).format(b=Style.bold, e=Style.reset), + ''').format(b=Style.bold, e=Style.reset), allow_abbrev=False, add_help=False, epilog='\n '.join(('config file:', *config_example())), diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 69b4333fee3bd..38d0415a1bbf2 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -21,13 +21,13 @@ from types import FrameType from typing import Optional -EMERGENCY_EXIT_DROPIN = """\ +EMERGENCY_EXIT_DROPIN = '''\ [Unit] Wants=emergency-exit.service -""" +''' -EMERGENCY_EXIT_SERVICE = """\ +EMERGENCY_EXIT_SERVICE = '''\ [Unit] DefaultDependencies=no Conflicts=shutdown.target @@ -38,7 +38,7 @@ [Service] ExecStart=false -""" +''' @dataclasses.dataclass(frozen=True) @@ -472,43 +472,43 @@ def main() -> None: name = args.name + (f'-{i}' if (i := os.getenv('MESON_TEST_ITERATION')) else '') dropin = textwrap.dedent( - """\ + '''\ [Service] StandardOutput=journal+console - """ + ''' ) if not shell: dropin += textwrap.dedent( - """ + ''' [Unit] SuccessAction=exit SuccessActionExitStatus=123 - """ + ''' ) if os.getenv('TEST_MATCH_SUBTEST'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_SUBTEST={os.environ['TEST_MATCH_SUBTEST']} - """ + ''' ) if os.getenv('TEST_MATCH_TESTCASE'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_TESTCASE={os.environ['TEST_MATCH_TESTCASE']} - """ + ''' ) if os.getenv('TEST_RUN_DFUZZER'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_RUN_DFUZZER={os.environ['TEST_RUN_DFUZZER']} - """ + ''' ) if os.getenv('TEST_JOURNAL_USE_TMP', '0') == '1': @@ -525,14 +525,14 @@ def main() -> None: if not sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Unit] FailureAction=exit - """ + ''' ) elif not shell: dropin += textwrap.dedent( - """ + ''' [Unit] Wants=multi-user.target getty-pre.target Before=getty-pre.target @@ -546,17 +546,17 @@ def main() -> None: IgnoreSIGPIPE=no # bash ignores SIGTERM KillSignal=SIGHUP - """ + ''' ) if sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Service] ExecStartPre=/usr/lib/systemd/tests/testdata/integration-test-setup.sh setup ExecStopPost=/usr/lib/systemd/tests/testdata/integration-test-setup.sh finalize StateDirectory=%N - """ + ''' ) if args.rtc: From 75f7304512307d5b4c7d59394c50cbb8191cf91b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Feb 2026 04:36:23 +0900 Subject: [PATCH 1820/2155] ruff: do not warn about too long line and lambda assignment --- ruff.toml | 7 ++++++- src/test/generate-sym-test.py | 2 +- src/ukify/ukify.py | 20 +++++++++---------- .../integration-test-wrapper.py | 4 ++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/ruff.toml b/ruff.toml index d4b77fb1e724b..35a4f5dc4312e 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,11 @@ target-version = "py39" line-length = 109 -lint.select = ["E", "F", "I", "Q", "UP"] + +[lint] +select = ["E", "F", "I", "Q", "UP"] +# E501: line-too-long +# E731: lambda-assignment +ignore = ["E501", "E731"] [format] # The formatter prefers double quotes for multiline quotes, diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index aa6c916fc7c92..065ad14447a5e 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # -# ruff: noqa: E501 UP015 +# ruff: noqa: UP015 import os import re diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 11374f607dd51..4a13019e249f1 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -1109,7 +1109,7 @@ def pe_add_sections(opts: UkifyConfig, uki: UKI, output: str) -> None: encoded = json.dumps(j).encode() if len(encoded) > section.SizeOfRawData: raise PEError( - f'Not enough space in existing section .pcrsig of size {section.SizeOfRawData} to append new data of size {len(encoded)}' # noqa: E501 + f'Not enough space in existing section .pcrsig of size {section.SizeOfRawData} to append new data of size {len(encoded)}' ) section.Misc_VirtualSize = len(encoded) # bytes(n) results in an array of n zeroes @@ -1498,7 +1498,7 @@ def make_uki(opts: UkifyConfig) -> None: if names[0] != '.profile': raise ValueError( - f'Expected .profile section as first valid section in PE profile binary {profile} but got {names[0]}' # noqa: E501 + f'Expected .profile section as first valid section in PE profile binary {profile} but got {names[0]}' ) if names.count('.profile') > 1: @@ -1688,7 +1688,7 @@ def generate_keys(opts: UkifyConfig) -> None: if not work: raise ValueError( - 'genkey: --secureboot-private-key=/--secureboot-certificate= or --pcr-private-key/--pcr-public-key must be specified' # noqa: E501 + 'genkey: --secureboot-private-key=/--secureboot-certificate= or --pcr-private-key/--pcr-public-key must be specified' ) @@ -1879,7 +1879,7 @@ def apply_config( elif self.type: conv = self.type else: - conv = lambda s: s # noqa: E731 + conv = lambda s: s # This is a bit ugly, but --initrd and --devicetree-auto are the only options # with multiple args on the command line and a space-separated list in the @@ -2116,14 +2116,14 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: ConfigItem( '--secureboot-private-key', dest='sb_key', - help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', # noqa: E501 + help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', config_key='UKI/SecureBootPrivateKey', ), ConfigItem( '--secureboot-certificate', dest='sb_cert', help=( - 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing' # noqa: E501 + 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing' ), config_key='UKI/SecureBootCertificate', ), @@ -2132,7 +2132,7 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: dest='sb_certdir', default='/etc/pki/pesign', help=( - 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign' # noqa: E501 + 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign' ), config_key='UKI/SecureBootCertificateDir', config_push=ConfigItem.config_set, @@ -2141,7 +2141,7 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: '--secureboot-certificate-name', dest='sb_cert_name', help=( - 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing' # noqa: E501 + 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing' ), config_key='UKI/SecureBootCertificateName', ), @@ -2458,7 +2458,7 @@ def finalize_options(opts: argparse.Namespace) -> None: # both param given, infer sbsign and in case it was given, ensure signtool=sbsign if opts.signtool and opts.signtool not in ('sbsign', 'systemd-sbsign'): raise ValueError( - f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' # noqa: E501 + f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' ) if not opts.signtool: opts.signtool = 'sbsign' @@ -2478,7 +2478,7 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: raise ValueError( - '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' # noqa: E501 + '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' ) opts.profile = resolve_at_path(opts.profile) diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 38d0415a1bbf2..43d11e0e2e1c0 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -589,7 +589,7 @@ def main() -> None: *(['--forward-journal', journal_file] if journal_file else []), *( [ - '--credential', f'systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)}', # noqa: E501 + '--credential', f'systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)}', '--credential', f'systemd.unit-dropin.emergency.target={shlex.quote(EMERGENCY_EXIT_DROPIN)}', ] if not sys.stdin.isatty() @@ -700,7 +700,7 @@ def main() -> None: iter = os.environ['GITHUB_RUN_ATTEMPT'] runner = os.environ['TEST_RUNNER'] artifact = ( - f'ci-{wf}-{id}-{iter}-{summary.distribution}-{summary.release}-{runner}-failed-test-journals' # noqa: E501 + f'ci-{wf}-{id}-{iter}-{summary.distribution}-{summary.release}-{runner}-failed-test-journals' ) ops += [f'gh run download {id} --name {artifact} -D ci/{artifact}'] journal_file = Path(f'ci/{artifact}/test/journal/{name}.journal') From f74118be4f1588fda57f74fa3259dd0b5d980daf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:43:03 +0900 Subject: [PATCH 1821/2155] ycm: apply "ruff format" and "ruff check --fix" --- .ycm_extra_conf.py | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 46b5ecd9d9abd..c64cc48a7be24 100755 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -43,11 +43,11 @@ import glob import os -import ycm_core +import ycm_core -SOURCE_EXTENSIONS = (".C", ".cpp", ".cxx", ".cc", ".c", ".m", ".mm") -HEADER_EXTENSIONS = (".H", ".h", ".hxx", ".hpp", ".hh") +SOURCE_EXTENSIONS = ('.C', '.cpp', '.cxx', '.cc', '.c', '.m', '.mm') +HEADER_EXTENSIONS = ('.H', '.h', '.hxx', '.hpp', '.hh') def DirectoryOfThisScript(): @@ -69,19 +69,18 @@ def GuessBuildDirectory(): containing '.ninja_log' file two levels above the current directory; returns this single directory only if there is one candidate. """ - result = os.path.join(DirectoryOfThisScript(), "build") + result = os.path.join(DirectoryOfThisScript(), 'build') if os.path.exists(result): return result - result = glob.glob(os.path.join(DirectoryOfThisScript(), - "..", "..", "*", ".ninja_log")) + result = glob.glob(os.path.join(DirectoryOfThisScript(), '..', '..', '*', '.ninja_log')) if not result: - return "" + return '' if 1 != len(result): - return "" + return '' return os.path.split(result[0])[0] @@ -106,9 +105,7 @@ def TraverseByDepth(root, include_extensions): # print(subdirs) if include_extensions: get_ext = os.path.splitext - subdir_extensions = { - get_ext(f)[-1] for f in file_list if get_ext(f)[-1] - } + subdir_extensions = {get_ext(f)[-1] for f in file_list if get_ext(f)[-1]} if subdir_extensions & include_extensions: result.add(root_dir) else: @@ -119,11 +116,11 @@ def TraverseByDepth(root, include_extensions): return result -_project_src_dir = os.path.join(DirectoryOfThisScript(), "src") -_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({".h"})) +_project_src_dir = os.path.join(DirectoryOfThisScript(), 'src') +_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({'.h'})) flags = [ - "-x", - "c" + '-x', + 'c', # The following flags are partially redundant due to the existence of # 'compile_commands.json'. # '-Wall', @@ -135,7 +132,7 @@ def TraverseByDepth(root, include_extensions): ] for include_dir in _include_dirs_set: - flags.append("-I" + include_dir) + flags.append('-I' + include_dir) # Set this to the absolute path to the folder (NOT the file!) containing the # compile_commands.json file to use that instead of 'flags'. See here for @@ -165,13 +162,13 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): return list(flags) new_flags = [] make_next_absolute = False - path_flags = ["-isystem", "-I", "-iquote", "--sysroot="] + path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] for flag in flags: new_flag = flag if make_next_absolute: make_next_absolute = False - if not flag.startswith("/"): + if not flag.startswith('/'): new_flag = os.path.join(working_directory, flag) for path_flag in path_flags: @@ -180,7 +177,7 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): break if flag.startswith(path_flag): - path = flag[len(path_flag):] + path = flag[len(path_flag) :] new_flag = path_flag + os.path.join(working_directory, path) break @@ -213,8 +210,7 @@ def GetCompilationInfoForFile(filename): for extension in SOURCE_EXTENSIONS: replacement_file = basename + extension if os.path.exists(replacement_file): - compilation_info = \ - database.GetCompilationInfoForFile(replacement_file) + compilation_info = database.GetCompilationInfoForFile(replacement_file) if compilation_info.compiler_flags_: return compilation_info return None @@ -238,13 +234,14 @@ def FlagsForFile(filename, **kwargs): final_flags = MakeRelativePathsInFlagsAbsolute( compilation_info.compiler_flags_, - compilation_info.compiler_working_dir_) + compilation_info.compiler_working_dir_, + ) else: relative_to = DirectoryOfThisScript() final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) return { - "flags": final_flags, - "do_cache": True + 'flags': final_flags, + 'do_cache': True, } From a3dc3dc61b3aa019d0f2cf3a5274a73096d3edd6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:47:18 +0900 Subject: [PATCH 1822/2155] hwdb/acpi-update: apply "ruff format" --- hwdb.d/acpi-update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hwdb.d/acpi-update.py b/hwdb.d/acpi-update.py index 41670b32bbc7c..66a11d8502dbd 100755 --- a/hwdb.d/acpi-update.py +++ b/hwdb.d/acpi-update.py @@ -5,6 +5,7 @@ # pylint: disable=consider-using-with + def read_table(filename): table = list(reader(open(filename, newline=''))) table = table[1:] # Skip header @@ -15,6 +16,7 @@ def read_table(filename): # a mistake, strip it. print(f'\nacpi:{row[1].strip()}*:\n ID_VENDOR_FROM_DATABASE={row[0].strip()}') + print('''\ # This file is part of systemd. # From 1c98dff834fe72dce12c812206ab0a9247bf28b3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 21 Feb 2026 03:29:00 +0900 Subject: [PATCH 1823/2155] hwdb/ids_parser: apply "ruff format" and "ruff check --fix" --- hwdb.d/ids_parser.py | 115 +++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 38 deletions(-) diff --git a/hwdb.d/ids_parser.py b/hwdb.d/ids_parser.py index ed2c615508def..7efd934f466e8 100755 --- a/hwdb.d/ids_parser.py +++ b/hwdb.d/ids_parser.py @@ -3,11 +3,24 @@ import re import sys -from pyparsing import (Word, White, Literal, Regex, - LineEnd, SkipTo, - ZeroOrMore, OneOrMore, Combine, Optional, Suppress, - Group, ParserElement, - stringEnd, pythonStyleComment) + +from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + ParserElement, + Regex, + SkipTo, + Suppress, + White, + Word, + ZeroOrMore, + pythonStyleComment, + stringEnd, +) EOL = LineEnd().suppress() NUM1 = Word('0123456789abcdefABCDEF', exact=1) @@ -22,7 +35,9 @@ ParserElement.setDefaultWhitespaceChars(' \n') + def klass_grammar(): + # fmt: off klass_line = Literal('C ').suppress() + NUM2('klass') + text_eol('text') subclass_line = TAB + NUM2('subclass') + text_eol('text') protocol_line = TAB + TAB + NUM2('protocol') + text_eol('name') @@ -32,9 +47,12 @@ def klass_grammar(): klass = (klass_line('KLASS') - ZeroOrMore(Group(subclass)('SUBCLASSES*') ^ COMMENTLINE.suppress())) + # fmt: on return klass + def usb_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') interface_line = TAB + TAB + NUM4('interface') + NUM4('interface2') + text_eol('text') @@ -55,11 +73,14 @@ def usb_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ other_group.suppress() ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def pci_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') subvendor_line = TAB + TAB + NUM4('a') + White(' ') + NUM4('b') + text_eol('name') @@ -75,11 +96,14 @@ def pci_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def sdio_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') vendor = (vendor_line('VENDOR') + @@ -91,11 +115,14 @@ def sdio_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def oui_grammar(type): + # fmt: off prefix_line = (Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix') - Literal('(hex)') - text_eol('text')) if type == 'small': @@ -115,18 +142,23 @@ def oui_grammar(type): grammar = (Literal('OUI') + text_eol('header') + text_eol('header') + text_eol('header') + EMPTYLINE + OneOrMore(Group(vendor)('VENDORS*')) + stringEnd()) + # fmt: on grammar.parseWithTabs() return grammar def header(file, *sources): - print('''\ + sep = ' ' if len(sources) == 1 else '\n# ' + joined = sep + sep.join(sources) + print( + f'''\ # This file is part of systemd. # -# Data imported from:{}{}'''.format(' ' if len(sources) == 1 else '\n# ', - '\n# '.join(sources)), - file=file) +# Data imported from:{joined}''', + file=file, + ) + def add_item(items, key, value): if key in items: @@ -134,6 +166,7 @@ def add_item(items, key, value): else: items[key] = value + def usb_vendor_model(p): items = {} @@ -147,19 +180,19 @@ def usb_vendor_model(p): text = vendor_dev.text.strip() add_item(items, (vendor, device), text) - with open('20-usb-vendor-model.hwdb', 'wt') as out: + with open('20-usb-vendor-model.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): if len(key) == 1: p, n = 'usb:v{}*', 'VENDOR' else: - p, n = 'usb:v{}p{}*', 'MODEL', - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + p, n = 'usb:v{}p{}*', 'MODEL' + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def usb_classes(p): items = {} @@ -182,7 +215,7 @@ def usb_classes(p): if klass != '00' and not re.match(r'(\?|None|Unused)\s*$', text): add_item(items, (klass, subclass, protocol), text) - with open('20-usb-classes.hwdb', 'wt') as out: + with open('20-usb-classes.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): @@ -192,11 +225,11 @@ def usb_classes(p): p, n = 'usb:v*p*d*dc{}dsc{}*', 'SUBCLASS' else: p, n = 'usb:v*p*d*dc{}dsc{}dp{}*', 'PROTOCOL' - print('', p.format(*key), - f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_vendor_model(p): items = {} @@ -215,12 +248,12 @@ def pci_vendor_model(p): sub_model = subvendor_group.b.upper() sub_text = subvendor_group.name.strip() if sub_text.startswith(text): - sub_text = sub_text[len(text):].lstrip() + sub_text = sub_text[len(text) :].lstrip() if sub_text: sub_text = f' ({sub_text})' add_item(items, (vendor, device, sub_vendor, sub_model), text + sub_text) - with open('20-pci-vendor-model.hwdb', 'wt') as out: + with open('20-pci-vendor-model.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -230,11 +263,11 @@ def pci_vendor_model(p): p, n = 'pci:v0000{}d0000{}*', 'MODEL' else: p, n = 'pci:v0000{}d0000{}sv0000{}sd0000{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_classes(p): items = {} @@ -253,7 +286,7 @@ def pci_classes(p): text = protocol_group.name.strip() add_item(items, (klass, subclass, protocol), text) - with open('20-pci-classes.hwdb', 'wt') as out: + with open('20-pci-classes.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -263,11 +296,11 @@ def pci_classes(p): p, n = 'pci:v*d*sv*sd*bc{}sc{}*', 'SUBCLASS' else: p, n = 'pci:v*d*sv*sd*bc{}sc{}i{}*', 'INTERFACE' - print('', p.format(*key), - f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_vendor_model(p): items = {} @@ -281,7 +314,7 @@ def sdio_vendor_model(p): text = device_group.text.strip() add_item(items, (vendor, device), text) - with open('20-sdio-vendor-model.hwdb', 'wt') as out: + with open('20-sdio-vendor-model.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for key in sorted(items): @@ -289,11 +322,11 @@ def sdio_vendor_model(p): p, n = 'sdio:c*v{}*', 'VENDOR' else: p, n = 'sdio:c*v{}d{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_classes(p): items = {} @@ -302,16 +335,21 @@ def sdio_classes(p): text = klass_group.text.strip() add_item(items, klass, text) - with open('20-sdio-classes.hwdb', 'wt') as out: + with open('20-sdio-classes.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for klass in sorted(items): - print(f'', - f'sdio:c{klass}v*d*', - f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', sep='\n', file=out) + print( + '', + f'sdio:c{klass}v*d*', + f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', + sep='\n', + file=out, + ) print(f'Wrote {out.name}') + # MAC Address Block Large/Medium/Small # Large MA-L 24/24 bit (OUI) # Medium MA-M 28/20 bit (OUI prefix owned by IEEE) @@ -338,19 +376,20 @@ def oui(p1, p2, p3): key = prefix + start if end else prefix add_item(items, key, text) - with open('20-OUI.hwdb', 'wt') as out: - header(out, - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt') + with open('20-OUI.hwdb', 'w') as out: + header( + out, + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt', + ) for pattern in sorted(items): - print(f'', - f'OUI:{pattern}*', - f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) + print('', f'OUI:{pattern}*', f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) print(f'Wrote {out.name}') + if __name__ == '__main__': args = sys.argv[1:] From 4fa9302fa48380727b025d5774f1717d6b054f47 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:48:13 +0900 Subject: [PATCH 1824/2155] hwdb/parse_hwdb: apply "ruff format" and "ruff check --fix" --- hwdb.d/parse_hwdb.py | 379 ++++++++++++++++++++++++------------------- 1 file changed, 210 insertions(+), 169 deletions(-) diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index e70b0ff04e94e..5b9d528119f87 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -26,16 +26,31 @@ # SOFTWARE. import glob +import os import string import sys -import os try: - from pyparsing import (Word, White, Literal, ParserElement, Regex, LineEnd, - OneOrMore, Combine, Or, Optional, Suppress, Group, - nums, alphanums, printables, - stringEnd, pythonStyleComment, - ParseBaseException) + from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + Or, + ParseBaseException, + ParserElement, + Regex, + Suppress, + White, + Word, + alphanums, + nums, + printables, + pythonStyleComment, + stringEnd, + ) except ImportError: print('pyparsing is not available') sys.exit(77) @@ -57,11 +72,7 @@ ecodes = None print('WARNING: evdev is not available') -try: - from functools import lru_cache -except ImportError: - # don't do caching on old python - lru_cache = lambda: (lambda f: f) +from functools import lru_cache EOL = LineEnd().suppress() EMPTYLINE = LineEnd() @@ -72,78 +83,90 @@ UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_') # Those patterns are used in type-specific matches -TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'), - 'evdev': ('name', 'atkbd', 'input'), - 'fb': ('pci', 'vmbus'), - 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), - 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'keyboard': ('name', ), - 'sensor': ('modalias', - 'accel-base', - 'accel-display', - 'accel-camera', - 'proximity-palmrest', - 'proximity-palmrest-left', - 'proximity-palmrest-right', - 'proximity-lap', - 'proximity-wifi', - 'proximity-lte', - 'proximity-wifi-lte', - 'proximity-wifi-left', - 'proximity-wifi-right', - ), - 'ieee1394-unit-function' : ('node', ), - 'camera': ('usb'), - } +TYPES = { + 'mouse': ('usb', 'bluetooth', 'ps2', '*'), + 'evdev': ('name', 'atkbd', 'input'), + 'fb': ('pci', 'vmbus'), + 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), + 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'keyboard': ('name',), + 'sensor': ( + 'modalias', + 'accel-base', + 'accel-display', + 'accel-camera', + 'proximity-palmrest', + 'proximity-palmrest-left', + 'proximity-palmrest-right', + 'proximity-lap', + 'proximity-wifi', + 'proximity-lte', + 'proximity-wifi-lte', + 'proximity-wifi-left', + 'proximity-wifi-right', + ), + 'ieee1394-unit-function': ('node',), + 'camera': ('usb'), +} # Patterns that are used to set general properties on a device -GENERAL_MATCHES = {'acpi', - 'bluetooth', - 'dmi', - 'ieee1394', - 'OUI', - 'pci', - 'sdio', - 'tpm2', - 'usb', - 'vmbus', - } +GENERAL_MATCHES = { + 'acpi', + 'bluetooth', + 'dmi', + 'ieee1394', + 'OUI', + 'pci', + 'sdio', + 'tpm2', + 'usb', + 'vmbus', +} + def upperhex_word(length): return Word(nums + 'ABCDEF', exact=length) -@lru_cache() + +@lru_cache def hwdb_grammar(): ParserElement.setDefaultWhitespaceChars('') - prefix = Or(category + ':' + Or(conn) + ':' - for category, conn in TYPES.items()) + prefix = Or(category + ':' + Or(conn) + ':' for category, conn in TYPES.items()) matchline_typed = Combine(prefix + Word(printables + ' ' + '®')) matchline_general = Combine(Or(GENERAL_MATCHES) + ':' + Word(printables + ' ' + '®')) matchline = (matchline_typed | matchline_general) + EOL - propertyline = (White(' ', exact=1).suppress() + - Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&')) - - Optional(pythonStyleComment)) + - EOL) + propertyline = ( + White(' ', exact=1).suppress() + + Combine( + UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&')) - Optional(pythonStyleComment) + ) + + EOL + ) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL - group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - - (EMPTYLINE ^ stringEnd()).suppress()) + group = ( + OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) + - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) + - (EMPTYLINE ^ stringEnd()).suppress() + ) commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd() return grammar -@lru_cache() + +@lru_cache def property_grammar(): ParserElement.setDefaultWhitespaceChars(' ') - dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))('SETTINGS*') + dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))( + 'SETTINGS*' + ) mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL mount_matrix = Group(mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX') xkb_setting = Optional(Word(alphanums + '+-/@._')) @@ -153,132 +176,139 @@ def property_grammar(): # Although this set doesn't cover all of characters in database entries, it's enough for test targets. name_literal = Word(printables + ' ') - props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))), - ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), - ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), - ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), - ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), - ('ID_INPUT_3D_MOUSE', zero_one), - ('ID_AUTOSUSPEND', zero_one), - ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), - ('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), - ('ID_INPUT_KEY', id_input_setting), - ('ID_INPUT_KEYBOARD', id_input_setting), - ('ID_INPUT_MOUSE', id_input_setting), - ('ID_INPUT_POINTINGSTICK', id_input_setting), - ('ID_INPUT_SWITCH', id_input_setting), - ('ID_INPUT_TABLET', id_input_setting), - ('ID_INPUT_TABLET_PAD', id_input_setting), - ('ID_INPUT_TOUCHPAD', id_input_setting), - ('ID_INPUT_TOUCHSCREEN', id_input_setting), - ('ID_INPUT_TRACKBALL', id_input_setting), - ('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'))), - ('XKB_FIXED_LAYOUT', xkb_setting), - ('XKB_FIXED_VARIANT', xkb_setting), - ('XKB_FIXED_MODEL', xkb_setting), - ('KEYBOARD_LED_NUMLOCK', Literal('0')), - ('KEYBOARD_LED_CAPSLOCK', Literal('0')), - ('ACCEL_MOUNT_MATRIX', mount_matrix), - ('ACCEL_LOCATION', Or(('display', 'base'))), - ('PROXIMITY_NEAR_LEVEL', INTEGER), - ('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', zero_one), - ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), - ('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))), - ('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', 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] - kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') - - Suppress('=') - - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') - ] - abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - - Suppress('=') - - Word('-' + nums + ':')('VALUE') - ] + props = ( + ('MOUSE_DPI', Group(OneOrMore(dpi_setting))), + ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), + ('ID_INPUT_3D_MOUSE', zero_one), + ('ID_AUTOSUSPEND', zero_one), + ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), + ('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), + ('ID_INPUT_KEY', id_input_setting), + ('ID_INPUT_KEYBOARD', id_input_setting), + ('ID_INPUT_MOUSE', id_input_setting), + ('ID_INPUT_POINTINGSTICK', id_input_setting), + ('ID_INPUT_SWITCH', id_input_setting), + ('ID_INPUT_TABLET', id_input_setting), + ('ID_INPUT_TABLET_PAD', id_input_setting), + ('ID_INPUT_TOUCHPAD', id_input_setting), + ('ID_INPUT_TOUCHSCREEN', id_input_setting), + ('ID_INPUT_TRACKBALL', id_input_setting), + ('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'))), + ('XKB_FIXED_LAYOUT', xkb_setting), + ('XKB_FIXED_VARIANT', xkb_setting), + ('XKB_FIXED_MODEL', xkb_setting), + ('KEYBOARD_LED_NUMLOCK', Literal('0')), + ('KEYBOARD_LED_CAPSLOCK', Literal('0')), + ('ACCEL_MOUNT_MATRIX', mount_matrix), + ('ACCEL_LOCATION', Or(('display', 'base'))), + ('PROXIMITY_NEAR_LEVEL', INTEGER), + ('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', zero_one), + ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), + ( + 'SOUND_FORM_FACTOR', + Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone')), + ), + ('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', 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] + kbd_props = [ + Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') + - Suppress('=') + - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') + ] + abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - Suppress('=') - Word('-' + nums + ':')('VALUE')] grammar = Or(fixed_props + kbd_props + abs_props) + EOL return grammar + ERROR = False + + def error(fmt, *args, **kwargs): global ERROR ERROR = True print(fmt.format(*args, **kwargs)) + def convert_properties(group): matches = [m[0] for m in group.MATCHES] props = [p[0] for p in group.PROPERTIES] return matches, props + def parse(fname): grammar = hwdb_grammar() try: - with open(fname, 'r', encoding='UTF-8') as f: + with open(fname, encoding='UTF-8') as f: parsed = grammar.parseFile(f) except ParseBaseException as e: error('Cannot parse {}: {}', fname, e) return [] return [convert_properties(g) for g in parsed.GROUPS] + def check_matches(groups): matches = sum((group[0] for group in groups), []) # This is a partial check. The other cases could be also done, but those # three are the most commonly wrong. grammars = { - 'bluetooth' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'usb' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'pci' : 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', + 'bluetooth': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'usb': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'pci': 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', } for match in matches: @@ -301,11 +331,13 @@ def check_matches(groups): error('Match {!r} is duplicated', match) prev = match + def check_one_default(prop, settings): defaults = [s for s in settings if s.DEFAULT] if len(defaults) > 1: error('More than one star entry: {!r}', prop) + def check_one_mount_matrix(prop, value): numbers = [s for s in value if s not in {';', ','}] if len(numbers) != 9: @@ -316,28 +348,33 @@ def check_one_mount_matrix(prop, value): error('Wrong accel matrix: {!r}', prop) bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0 if bad_x or bad_y or bad_z: - error('Mount matrix is all zero in {} row: {!r}', - 'x' if bad_x else ('y' if bad_y else 'z'), - prop) + error('Mount matrix is all zero in {} row: {!r}', 'x' if bad_x else ('y' if bad_y else 'z'), prop) + def check_one_keycode(value): if value != '!' and ecodes is not None: key = 'KEY_' + value.upper() - if not (key in ecodes or - value.upper() in ecodes or - # new keys added in kernel 5.5 - 'KBD_LCD_MENU' in key): + if not ( + key in ecodes + or value.upper() in ecodes + # new keys added in kernel 5.5 + or 'KBD_LCD_MENU' in key + ): # fmt: skip error('Keycode {} unknown', key) + def check_wheel_clicks(properties): - pairs = (('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), - ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), - ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), - ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE')) + pairs = ( + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), + ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE'), + ) for pair in pairs: if pair[0] in properties and pair[1] not in properties: error('{} requires {} to be specified', *pair) + def check_properties(groups): grammar = property_grammar() for _, props in groups: @@ -364,6 +401,7 @@ def check_properties(groups): check_wheel_clicks(seen_props) + def print_summary(fname, groups): n_matches = sum(len(matches) for matches, props in groups) n_props = sum(len(props) for matches, props in groups) @@ -372,12 +410,15 @@ def print_summary(fname, groups): if n_matches == 0 or n_props == 0: print(f'{fname}: no matches or props') + if __name__ == '__main__': - args = sys.argv[1:] or sorted([ - os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', - os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', - *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), - ]) + args = sys.argv[1:] or sorted( + [ + os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', + os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', + *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), + ] + ) for fname in args: groups = parse(fname) From 522fdd7d9661b0aeede9a106d434e9200c60a4fb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:48:59 +0900 Subject: [PATCH 1825/2155] man/90-rearrange-path: apply "ruff format" and "ruff check --fix" --- man/90-rearrange-path.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/man/90-rearrange-path.py b/man/90-rearrange-path.py index b5b6294754cae..278311f0ab523 100755 --- a/man/90-rearrange-path.py +++ b/man/90-rearrange-path.py @@ -17,6 +17,7 @@ import os import pathlib + def rearrange_bin_sbin(path): """Make sure any pair of …/bin, …/sbin directories is in this order @@ -27,15 +28,16 @@ def rearrange_bin_sbin(path): for i in range(len(items)): if 'sbin' in items[i].parts: ind = items[i].parts.index('sbin') - bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind+1:]) - if bin in items[i+1:]: - j = i + 1 + items[i+1:].index(bin) + bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind + 1 :]) + if bin in items[i + 1 :]: + j = i + 1 + items[i + 1 :].index(bin) items[i], items[j] = items[j], items[i] return ':'.join(p.as_posix() for p in items) + if __name__ == '__main__': - path = os.environ['PATH'] # This should be always set. - # If it is not, we will just crash, which is OK too. + # This should be always set. If it is not, we will just crash, which is OK too. + path = os.environ['PATH'] new = rearrange_bin_sbin(path) if new != path: - print('PATH={}'.format(new)) + print(f'PATH={new}') From 14848f1f71bca0cc159bc31b912d0727afad86e4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:49:47 +0900 Subject: [PATCH 1826/2155] man/check-os-release-simple: apply "ruff format" --- man/check-os-release-simple.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/check-os-release-simple.py b/man/check-os-release-simple.py index ce73c77b14a36..8c64982790237 100755 --- a/man/check-os-release-simple.py +++ b/man/check-os-release-simple.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: MIT-0 import platform + os_release = platform.freedesktop_os_release() pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'fedora' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'fedora' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Fedora!') From 548196170047967c40b57897282cbb98427c1fac Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:50:19 +0900 Subject: [PATCH 1827/2155] man/check-os-release: apply "ruff format" --- man/check-os-release.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/check-os-release.py b/man/check-os-release.py index f0a64f349662a..ec71e57a31a54 100755 --- a/man/check-os-release.py +++ b/man/check-os-release.py @@ -5,6 +5,7 @@ import re import sys + def read_os_release(): try: filename = '/etc/os-release' @@ -23,14 +24,13 @@ def read_os_release(): val = ast.literal_eval(val) yield name, val else: - print(f'{filename}:{line_number}: bad line {line!r}', - file=sys.stderr) + print(f'{filename}:{line_number}: bad line {line!r}', file=sys.stderr) + os_release = dict(read_os_release()) pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'debian' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'debian' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Debian!') From 4758d1f71a0c5e3d0122d8cd0ce8c8500545d9cb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:50:59 +0900 Subject: [PATCH 1828/2155] man/notify-example: apply "ruff format" --- man/notify-selfcontained-example.py | 48 +++++++++++++++++------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/man/notify-selfcontained-example.py b/man/notify-selfcontained-example.py index 6a1e25b99b2ff..1302116221146 100755 --- a/man/notify-selfcontained-example.py +++ b/man/notify-selfcontained-example.py @@ -18,49 +18,56 @@ reloading = False terminating = False + def notify(message): if not message: - raise ValueError("notify() requires a message") + raise ValueError('notify() requires a message') - socket_path = os.environ.get("NOTIFY_SOCKET") + socket_path = os.environ.get('NOTIFY_SOCKET') if not socket_path: return - if socket_path[0] not in ("/", "@"): - raise OSError(errno.EAFNOSUPPORT, "Unsupported socket type") + if socket_path[0] not in ('/', '@'): + raise OSError(errno.EAFNOSUPPORT, 'Unsupported socket type') # Handle abstract socket. - if socket_path[0] == "@": - socket_path = "\0" + socket_path[1:] + if socket_path[0] == '@': + socket_path = '\0' + socket_path[1:] with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) as sock: sock.connect(socket_path) sock.sendall(message) + def notify_ready(): - notify(b"READY=1") + notify(b'READY=1') + def notify_reloading(): microsecs = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000 - notify(f"RELOADING=1\nMONOTONIC_USEC={microsecs}".encode()) + notify(f'RELOADING=1\nMONOTONIC_USEC={microsecs}'.encode()) + def notify_stopping(): - notify(b"STOPPING=1") + notify(b'STOPPING=1') + def reload(signum, frame): global reloading reloading = True + def terminate(signum, frame): global terminating terminating = True + def main(): - print("Doing initial setup") + print('Doing initial setup') global reloading, terminating # Set up signal handlers. - print("Setting up signal handlers") + print('Setting up signal handlers') signal.signal(signal.SIGHUP, reload) signal.signal(signal.SIGINT, terminate) signal.signal(signal.SIGTERM, terminate) @@ -68,13 +75,13 @@ def main(): # Do any other setup work here. # Once all setup is done, signal readiness. - print("Done setting up") + print('Done setting up') notify_ready() - print("Starting loop") + print('Starting loop') while not terminating: if reloading: - print("Reloading") + print('Reloading') reloading = False # Support notifying the manager when reloading configuration. @@ -86,19 +93,20 @@ def main(): # Do some reconfiguration work here. - print("Done reloading") + print('Done reloading') notify_ready() # Do the real work here ... - print("Sleeping for five seconds") + print('Sleeping for five seconds') time.sleep(5) - print("Terminating") + print('Terminating') notify_stopping() -if __name__ == "__main__": + +if __name__ == '__main__': sys.stdout.reconfigure(line_buffering=True) - print("Starting app") + print('Starting app') main() - print("Stopped app") + print('Stopped app') From a89c11b67219c4aee65254f733a2f0311b113acf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:52:37 +0900 Subject: [PATCH 1829/2155] filesystem-sets: apply "ruff format" and "ruff check --fix" --- src/basic/filesystem-sets.py | 207 ++++++++++++++++++----------------- 1 file changed, 109 insertions(+), 98 deletions(-) diff --git a/src/basic/filesystem-sets.py b/src/basic/filesystem-sets.py index 18daa48681eda..be08664aef65a 100755 --- a/src/basic/filesystem-sets.py +++ b/src/basic/filesystem-sets.py @@ -124,12 +124,12 @@ 'z3fold': ['Z3FOLD_MAGIC'], 'zonefs': ['ZONEFS_MAGIC'], 'zsmalloc': ['ZSMALLOC_MAGIC'], -} +} # fmt: skip # System magics are sometimes not unique, because file systems got new # revisions or got renamed. Let's prefer newer over older here, and thus ignore # the old names. -OBSOLETE_NAMES = { +OBSOLETE_NAMES = { 'cpuset', # magic taken over by cgroupfs 'devtmpfs', # not a file system of its own, but just a "named superblock" of tmpfs 'ext2', # ext4 is the newest revision of ext2 + ext3 @@ -142,115 +142,116 @@ 'nfs', # nfs4 is the newest revision of nfs 'pvfs2', # orangefs is the new name of pvfs2 'smb3', # smb3 is an alias for cifs -} +} # fmt: skip FILESYSTEM_SETS = [ ( - "@basic-api", - "Basic filesystem API", - "cgroup", - "cgroup2", - "devpts", - "devtmpfs", - "mqueue", - "proc", - "sysfs", + '@basic-api', + 'Basic filesystem API', + 'cgroup', + 'cgroup2', + 'devpts', + 'devtmpfs', + 'mqueue', + 'proc', + 'sysfs', ), ( - "@anonymous", - "Anonymous inodes", - "anon_inodefs", - "pipefs", - "sockfs", + '@anonymous', + 'Anonymous inodes', + 'anon_inodefs', + 'pipefs', + 'sockfs', ), ( - "@application", - "Application virtual filesystems", - "autofs", - "fuse", - "overlay", + '@application', + 'Application virtual filesystems', + 'autofs', + 'fuse', + 'overlay', ), ( - "@auxiliary-api", - "Auxiliary filesystem API", - "binfmt_misc", - "configfs", - "efivarfs", - "fusectl", - "hugetlbfs", - "rpc_pipefs", - "securityfs", + '@auxiliary-api', + 'Auxiliary filesystem API', + 'binfmt_misc', + 'configfs', + 'efivarfs', + 'fusectl', + 'hugetlbfs', + 'rpc_pipefs', + 'securityfs', ), ( - "@common-block", - "Common block device filesystems", - "btrfs", - "erofs", - "exfat", - "ext4", - "f2fs", - "iso9660", - "ntfs3", - "squashfs", - "udf", - "vfat", - "xfs", + '@common-block', + 'Common block device filesystems', + 'btrfs', + 'erofs', + 'exfat', + 'ext4', + 'f2fs', + 'iso9660', + 'ntfs3', + 'squashfs', + 'udf', + 'vfat', + 'xfs', ), ( - "@historical-block", - "Historical block device filesystems", - "ext2", - "ext3", - "minix", + '@historical-block', + 'Historical block device filesystems', + 'ext2', + 'ext3', + 'minix', ), ( - "@network", - "Well-known network filesystems", - "afs", - "ceph", - "cifs", - "gfs", - "gfs2", - "ncp", - "ncpfs", - "nfs", - "nfs4", - "ocfs2", - "orangefs", - "pvfs2", - "smb3", - "smbfs", + '@network', + 'Well-known network filesystems', + 'afs', + 'ceph', + 'cifs', + 'gfs', + 'gfs2', + 'ncp', + 'ncpfs', + 'nfs', + 'nfs4', + 'ocfs2', + 'orangefs', + 'pvfs2', + 'smb3', + 'smbfs', ), ( - "@privileged-api", - "Privileged filesystem API", - "bpf", - "debugfs", - "pstore", - "tracefs", + '@privileged-api', + 'Privileged filesystem API', + 'bpf', + 'debugfs', + 'pstore', + 'tracefs', ), ( - "@security", - "Security/MAC API VFS", - "apparmorfs", - "selinuxfs", - "smackfs", + '@security', + 'Security/MAC API VFS', + 'apparmorfs', + 'selinuxfs', + 'smackfs', ), ( - "@temporary", - "Temporary filesystems", - "ramfs", - "tmpfs", + '@temporary', + 'Temporary filesystems', + 'ramfs', + 'tmpfs', ), ( - "@known", - "All known filesystems declared in the kernel", + '@known', + 'All known filesystems declared in the kernel', *NAME_TO_MAGIC.keys(), ), ] + def generate_gperf(): - print("""\ + print('''\ /* SPDX-License-Identifier: LGPL-2.1-or-later */ %{ #if __GNUC__ >= 15 @@ -275,12 +276,13 @@ def generate_gperf(): %omit-struct-type %struct-type %includes -%%""") +%%''') for name, magics in NAME_TO_MAGIC.items(): - print(f"{name + ',':16} {{{', '.join(magics)}}}") + print(f'{name + ",":16} {{{", ".join(magics)}}}') + def generate_fs_type_to_string(): - print("""\ + print('''\ #include #include "filesystems.h" @@ -290,7 +292,7 @@ def generate_fs_type_to_string(): #define PROJECT_FILE __FILE__ const char* fs_type_to_string(statfs_f_type_t magic) { - switch (magic) {""") + switch (magic) {''') for name, magics in NAME_TO_MAGIC.items(): if name in OBSOLETE_NAMES: @@ -299,10 +301,11 @@ def generate_fs_type_to_string(): print(f' case (statfs_f_type_t) {magic}:') print(f' return "{name}";') - print("""\ + print('''\ } return NULL; -}""") +}''') + def generate_fs_in_group(): print('bool fs_in_group(const struct statfs *st, FilesystemGroups fs_group) {') @@ -312,14 +315,14 @@ def generate_fs_in_group(): magics = sorted(set(sum((NAME_TO_MAGIC[fs] for fs in filesystems), start=[]))) enum = 'FILESYSTEM_SET_' + name[1:].upper().replace('-', '_') print(f' case {enum}:') - opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' - for magic in magics) + opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' for magic in magics) print(f' return {opts};') print(' default: assert_not_reached();') print(' }') print('}') + def generate_filesystem_sets(): print('const FilesystemSet filesystem_sets[_FILESYSTEM_SET_MAX] = {') @@ -329,37 +332,45 @@ def generate_filesystem_sets(): print(f' [{enum}] = {{') print(f' .name = "{name}",') print(f' .help = "{desc}",') - print(f' .value =') + print(' .value =') for filesystem in filesystems: print(f' "{filesystem}\\0"') print(' },') print('};') + def magic_defines(): cpp = os.environ['CPP'].split() out = subprocess.check_output( [*cpp, '-dM', '-include', 'linux/magic.h', '-'], stdin=subprocess.DEVNULL, - text=True) + text=True, + ) for line in out.splitlines(): _, name, *rest = line.split() - if ('_MAGIC' in name - and rest and rest[0].startswith('0x') - and name not in { + if ( + '_MAGIC' in name + and rest + and rest[0].startswith('0x') + and name + not in { 'STACK_END_MAGIC', 'MTD_INODE_FS_MAGIC', 'FUTEXFS_SUPER_MAGIC', 'CRAMFS_MAGIC_WEND', - }): + } + ): yield name + def check(): kernel_magics = set(magic_defines()) our_magics = set(sum(NAME_TO_MAGIC.values(), start=[])) extra = kernel_magics - our_magics if extra: - sys.exit(f"kernel knows additional filesystem magics: {', '.join(sorted(extra))}") + sys.exit(f'kernel knows additional filesystem magics: {", ".join(sorted(extra))}') + if __name__ == '__main__': for arg in sys.argv[1:]: From c5abf64d3775adfdcd64dcdf23bdf79fe4d52bb3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:53:29 +0900 Subject: [PATCH 1830/2155] fuzz-bootspec: apply "ruff format" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also replaces open().read() with Path().read_text(). Co-authored-by: Jörg Behrmann --- src/fuzz/fuzz-bootspec-gen.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/fuzz/fuzz-bootspec-gen.py b/src/fuzz/fuzz-bootspec-gen.py index a73e59203bbeb..927c5ed529842 100755 --- a/src/fuzz/fuzz-bootspec-gen.py +++ b/src/fuzz/fuzz-bootspec-gen.py @@ -4,14 +4,12 @@ """Generate sample input for fuzz-bootspec""" import json -import os import sys +from pathlib import Path -config = open(sys.argv[1]).read() -loader = [entry for entry in open(sys.argv[2], encoding='utf-16-le').read().split('\0') - if len(entry) > 2] # filter out fluff from bad decoding -entries = [(os.path.basename(name), open(name).read()) - for name in sys.argv[3:]] +config = Path(sys.argv[1]).read_text() +loader = [entry for entry in Path(sys.argv[2]).read_text(encoding='utf-16-le').split('\0') if len(entry) > 2] +entries = [(Path(name).name, Path(name).read_text()) for name in sys.argv[3:]] data = { 'config': config, From b0a06e2a81f9ff5f03926857f1986af74b213185 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:54:24 +0900 Subject: [PATCH 1831/2155] generate-syscall: apply "ruff format" and "ruff check --fix" Also, this makes opened files closed when not necessary. --- src/include/override/sys/generate-syscall.py | 61 ++++++++++++-------- src/include/override/sys/syscall.h | 2 +- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 4391df6da349b..ba44c93eb26da 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import functools +import sys # We only generate numbers for a dozen or so syscalls SYSCALLS = [ @@ -12,24 +12,29 @@ 'quotactl_fd', # defined in glibc header since glibc-2.35 'removexattrat', 'setxattrat', -] +] # fmt: skip + def dictify(f): def wrap(*args, **kwargs): return dict(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @dictify def parse_syscall_table(filename): print(f'Reading {filename}…') - for line in open(filename): - items = line.split() - if len(items) >= 2: - yield items[0], int(items[1]) + with open(filename) as f: + for line in f: + items = line.split() + if len(items) >= 2: + yield items[0], int(items[1]) + def parse_syscall_tables(filenames): - return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) - for filename in filenames} + return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) for filename in filenames} + HEADER = '''\ /* SPDX-License-Identifier: LGPL-2.1-or-later @@ -108,12 +113,15 @@ def parse_syscall_tables(filenames): # else # define systemd_NR_{syscall} {nr_x86_64} # endif +''' + +DEF_TEMPLATE_C = '''\ # elif !defined(missing_arch_template) -%s +# warning "{syscall}() syscall number is unknown for your architecture" # endif ''' -DEF_TEMPLATE_C = '''\ +DEF_TEMPLATE_D = '''\ /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_{syscall} && __NR_{syscall} >= 0 @@ -130,23 +138,29 @@ def parse_syscall_tables(filenames): # endif #endif''' -DEF_TEMPLATE = (DEF_TEMPLATE_A + - DEF_TEMPLATE_B % '# warning "{syscall}() syscall number is unknown for your architecture"' + - DEF_TEMPLATE_C) +DEF_TEMPLATE = DEF_TEMPLATE_A + DEF_TEMPLATE_B + DEF_TEMPLATE_C + DEF_TEMPLATE_D -ARCH_CHECK = '''\ +ARCH_CHECK_A = '''\ /* Note: if this code looks strange, this is because it is derived from the same * template as the per-syscall blocks below. */ -''' + '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() - if ' define ' not in line) % '''\ +''' + +ARCH_CHECK_B = '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() if ' define ' not in line) + +ARCH_CHECK_C = '''\ + +# else # warning "Current architecture is missing from the template" -# define missing_arch_template 1''' +# define missing_arch_template 1 +# endif''' + +ARCH_CHECK = ARCH_CHECK_A + ARCH_CHECK_B + ARCH_CHECK_C + def print_syscall_def(syscall, tables, out): - mappings = {f'nr_{arch}':t.get(syscall, -1) - for arch, t in tables.items()} - print(DEF_TEMPLATE.format(syscall=syscall, **mappings), - file=out) + mappings = {f'nr_{arch}': t.get(syscall, -1) for arch, t in tables.items()} + print(DEF_TEMPLATE.format(syscall=syscall, **mappings), file=out) + def print_syscall_defs(syscalls, tables, out): print(HEADER, file=out) @@ -154,12 +168,13 @@ def print_syscall_defs(syscalls, tables, out): for syscall in syscalls: print_syscall_def(syscall, tables, out) + if __name__ == '__main__': output_file = sys.argv[1] arch_files = sys.argv[2:] - out = open(output_file, 'wt') tables = parse_syscall_tables(arch_files) - print_syscall_defs(SYSCALLS, tables, out) + with open(output_file, 'w') as out: + print_syscall_defs(SYSCALLS, tables, out) print(f'Wrote {output_file}') diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index 108885b725a31..da555e79080c4 100644 --- a/src/include/override/sys/syscall.h +++ b/src/include/override/sys/syscall.h @@ -49,7 +49,7 @@ # if defined(__ILP32__) # else # endif -# elif !defined(missing_arch_template) +# else # warning "Current architecture is missing from the template" # define missing_arch_template 1 # endif From cd7bb9604966600b2d9b46a8d3c3876a48772f8d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:54:49 +0900 Subject: [PATCH 1832/2155] log-generator: apply "ruff format" and "ruff check --fix" --- src/journal-remote/log-generator.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py index 2843afb4c93c7..1e2c0e5c3e96b 100755 --- a/src/journal-remote/log-generator.py +++ b/src/journal-remote/log-generator.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import argparse +import sys PARSER = argparse.ArgumentParser() PARSER.add_argument('n', type=int) @@ -12,7 +12,7 @@ PARSER.add_argument('--data-type', choices={'random', 'simple'}) OPTIONS = PARSER.parse_args() -template = """\ +template = '''\ __CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c __REALTIME_TIMESTAMP={realtime_ts} __MONOTONIC_TIMESTAMP={monotonic_ts} @@ -30,7 +30,7 @@ _PID=25721 _SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} DATA={data} -""" +''' priority = 3 facility = 6 @@ -51,14 +51,16 @@ data = '{:0{}}'.format(counter, OPTIONS.data_size) counter += 1 - entry = template.format(m=0x198603b12d7 + i, - realtime_ts=1404101101501873 + i, - monotonic_ts=1753961140951 + i, - source_realtime_ts=1404101101483516 + i, - priority=priority, - facility=facility, - message=message, - data=data) + entry = template.format( + m=0x198603B12D7 + i, + realtime_ts=1404101101501873 + i, + monotonic_ts=1753961140951 + i, + source_realtime_ts=1404101101483516 + i, + priority=priority, + facility=facility, + message=message, + data=data, + ) bytes += len(entry) @@ -69,4 +71,4 @@ if OPTIONS.dots: print(file=sys.stderr) -print('Wrote {} bytes'.format(bytes), file=sys.stderr) +print(f'Wrote {bytes} bytes', file=sys.stderr) From 408445901469db12996c155e62c8b764de885b2b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:55:31 +0900 Subject: [PATCH 1833/2155] ethtool-link-mode: apply "ruff format" --- src/shared/ethtool-link-mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ethtool-link-mode.py b/src/shared/ethtool-link-mode.py index 6d23f3c43aee9..c64fa6c456381 100755 --- a/src/shared/ethtool-link-mode.py +++ b/src/shared/ethtool-link-mode.py @@ -7,7 +7,7 @@ import sys OVERRIDES = { - 'autoneg' : 'autonegotiation', + 'autoneg': 'autonegotiation', } mode = sys.argv[1] From 246e4594ed3b7d7ff1bdd312ebcb846869d96b76 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:56:07 +0900 Subject: [PATCH 1834/2155] generate-dns_type: apply "ruff format" and "ruff check --fix" --- src/shared/generate-dns_type-gperf.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/shared/generate-dns_type-gperf.py b/src/shared/generate-dns_type-gperf.py index 0fd003a8023ae..79d92cb617999 100755 --- a/src/shared/generate-dns_type-gperf.py +++ b/src/shared/generate-dns_type-gperf.py @@ -1,26 +1,24 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -"""Generate %-from-name.gperf from %-list.txt -""" +"""Generate %-from-name.gperf from %-list.txt""" import sys name, prefix, input = sys.argv[1:] -print("""\ -%{ +print(f'''\ +%{{ _Pragma("GCC diagnostic ignored \\"-Wimplicit-fallthrough\\"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored \\"-Wzero-as-null-pointer-constant\\"") #endif -%}""") -print("""\ -struct {}_name {{ const char* name; int id; }}; +%}} +struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""".format(name)) +%%''') for line in open(input): line = line.rstrip() s = line.replace('_', '-') - print("{}, {}{}".format(s, prefix, line)) + print(f'{s}, {prefix}{line}') From d86df93f96db391b381a7eca31574372da6abfa2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:35:03 +0900 Subject: [PATCH 1835/2155] generate-syscall-list: apply "ruff check --fix" --- src/shared/generate-syscall-list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/generate-syscall-list.py b/src/shared/generate-syscall-list.py index c0975a06da812..111e8b7367e04 100755 --- a/src/shared/generate-syscall-list.py +++ b/src/shared/generate-syscall-list.py @@ -4,4 +4,4 @@ import sys for line in open(sys.argv[1]): - print('"{}\\0"'.format(line.strip())) + print(f'"{line.strip()}\\0"') From 58aa8824f3d611473b9e4e4b732e2d6b826c98ba Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:56:52 +0900 Subject: [PATCH 1836/2155] test_ukify: apply "ruff format" Also drops unnecessary noqa attribute for importing pefile. --- src/ukify/test/test_ukify.py | 545 ++++++++++++++++++++--------------- 1 file changed, 319 insertions(+), 226 deletions(-) diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 224a38569f280..0fe27601f15b4 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -33,8 +33,7 @@ sys.exit(77) try: - # pyflakes: noqa - import pefile # noqa + import pefile except ImportError as e: print(str(e), file=sys.stderr) sys.exit(77) @@ -59,42 +58,51 @@ slow_tests = True arg_tools = ['--tools', build_root] if build_root else [] -if build_root and ( - p := pathlib.Path(f"{build_root}/linux{ukify.guess_efi_arch()}.efi.stub") -).exists(): +if ( + build_root + and (p := pathlib.Path(f'{build_root}/linux{ukify.guess_efi_arch()}.efi.stub')).exists() +): # fmt: skip arg_tools += ['--stub', p] + def systemd_measure(): opts = ukify.create_parser().parse_args(arg_tools) return ukify.find_tool('systemd-measure', opts=opts) + def test_guess_efi_arch(): arch = ukify.guess_efi_arch() assert arch in ukify.EFI_ARCHES + def test_shell_join(): assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '" + def test_round_up(): assert ukify.round_up(0) == 0 assert ukify.round_up(4095) == 4096 assert ukify.round_up(4096) == 4096 assert ukify.round_up(4097) == 8192 + def test_namespace_creation(): ns = ukify.create_parser().parse_args(()) assert ns.linux is None assert ns.initrd is None + def test_config_example(): ex = ukify.config_example() assert '[UKI]' in ex assert 'Splash = BMP' in ex + def test_apply_config(tmp_path): config = tmp_path / 'config1.conf' - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -117,7 +125,9 @@ def test_apply_config(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) ns = ukify.create_parser().parse_args(['build']) ns.linux = None @@ -125,9 +135,11 @@ def test_apply_config(tmp_path): ukify.apply_config(ns, config) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5\n6 7 8' assert ns.os_release == '@some/path1' assert ns.devicetree == pathlib.Path('some/path2') @@ -148,9 +160,11 @@ def test_apply_config(tmp_path): ukify.finalize_options(ns) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5 6 7 8' assert ns.os_release == pathlib.Path('some/path1') assert ns.devicetree == pathlib.Path('some/path2') @@ -168,6 +182,7 @@ def test_apply_config(tmp_path): assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] + def test_parse_args_minimal(): with pytest.raises(ValueError): ukify.parse_args([]) @@ -175,32 +190,39 @@ def test_parse_args_minimal(): opts = ukify.parse_args('arg1 arg2'.split()) assert opts.linux == pathlib.Path('arg1') assert opts.initrd == [pathlib.Path('arg2')] - assert opts.os_release in (pathlib.Path('/etc/os-release'), - pathlib.Path('/usr/lib/os-release')) + assert opts.os_release in ( + pathlib.Path('/etc/os-release'), + pathlib.Path('/usr/lib/os-release'), + ) + def test_parse_args_many_deprecated(): opts = ukify.parse_args( - ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - ]) + [ + '/ARG1', + '///ARG2', + '/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -221,34 +243,37 @@ def test_parse_args_many_deprecated(): assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False + def test_parse_args_many(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - '--policy-digest', - '--no-policy-digest', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + '--policy-digest', + '--no-policy-digest', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -270,14 +295,17 @@ def test_parse_args_many(): assert opts.measure is False assert opts.policy_digest is False + def test_parse_sections(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=/ARG2', - '--section=test:TESTTESTTEST', - '--section=test2:@FILE', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=/ARG2', + '--section=test:TESTTESTTEST', + '--section=test2:@FILE', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2')] @@ -293,11 +321,13 @@ def test_parse_sections(): assert opts.sections[1].tmpfile is None assert opts.sections[1].measure is False + def test_config_priority(tmp_path): config = tmp_path / 'config1.conf' # config: use pesign and give certdir + certname - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -321,44 +351,50 @@ def test_config_priority(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) # args: use sbsign and give key + cert, should override pesign opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline= a b c ', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--signtool=sbsign', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline= a b c ', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--signtool=sbsign', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + ] + ) ukify.apply_config(opts, config) ukify.finalize_options(opts) assert opts.linux == pathlib.Path('/ARG1') - assert opts.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3'), - pathlib.Path('/ARG2'), - pathlib.Path('/ARG3 WITH SPACE')] + assert opts.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + pathlib.Path('/ARG2'), + pathlib.Path('/ARG3 WITH SPACE'), + ] assert opts.cmdline == 'a b c' assert opts.os_release == 'K1=V1\nK2=V2' assert opts.devicetree == pathlib.Path('DDDDTTTT') @@ -370,16 +406,17 @@ def test_config_priority(tmp_path): assert opts.pcr_public_keys == ['PKEY2', 'some/path8'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' - assert opts.signtool == 'sbsign' # from args - assert opts.sb_key == 'SBKEY' # from args - assert opts.sb_cert == pathlib.Path('SBCERT') # from args - assert opts.sb_certdir == 'some/path5' # from config - assert opts.sb_cert_name == 'some/name1' # from config + assert opts.signtool == 'sbsign' # from args + assert opts.sb_key == 'SBKEY' # from args + assert opts.sb_cert == pathlib.Path('SBCERT') # from args + assert opts.sb_certdir == 'some/path5' # from config + assert opts.sb_cert_name == 'some/name1' # from config assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is True + def test_help(capsys): with pytest.raises(SystemExit): ukify.parse_args(['--help']) @@ -387,6 +424,7 @@ def test_help(capsys): assert '--section' in out.out assert not out.err + def test_help_display(capsys): with pytest.raises(SystemExit): ukify.parse_args(['inspect', '--help']) @@ -394,6 +432,7 @@ def test_help_display(capsys): assert '--section' in out.out assert not out.err + def test_help_error_deprecated(capsys): with pytest.raises(SystemExit): ukify.parse_args(['a', 'b', '--no-such-option']) @@ -402,6 +441,7 @@ def test_help_error_deprecated(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + def test_help_error(capsys): with pytest.raises(SystemExit): ukify.parse_args(['build', '--no-such-option']) @@ -410,6 +450,7 @@ def test_help_error(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + @pytest.fixture(scope='session') def kernel_initrd(): items = sorted(glob.glob('/lib/modules/*/vmlinuz')) @@ -426,6 +467,7 @@ def kernel_initrd(): # We don't look _into_ the initrd. Any file is OK. return ['--linux', linux, '--initrd', ukify.__file__] + def test_check_splash(): try: # pyflakes: noqa @@ -436,16 +478,19 @@ def test_check_splash(): with pytest.raises(OSError): ukify.check_splash(os.devnull) + def test_basic_operation(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + ] + ) try: ukify.check_inputs(opts) except OSError as e: @@ -458,20 +503,23 @@ def test_basic_operation(kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + def test_sections(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=K1=V1\nK2=V2\n', - '--section=.test:CONTENTZ', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=K1=V1\nK2=V2\n', + '--section=.test:CONTENTZ', + ] + ) try: ukify.check_inputs(opts) @@ -484,24 +532,25 @@ def test_sections(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname test'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) shutil.rmtree(tmp_path) + def test_addon(tmp_path): output = f'{tmp_path}/addon.efi' args = [ 'build', f'--output={output}', '--cmdline=ARG1 ARG2 ARG3', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo foo,1 bar,2 -""", +''', '--section=.test:CONTENTZ', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo baz,3 -""" +''', ] if stub := os.getenv('EFI_ADDON'): args += [f'--stub={stub}'] @@ -521,26 +570,33 @@ def test_addon(tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text cmdline test sbat'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) pe = pefile.PE(output, fast_load=True) found = False for section in pe.sections: - if section.Name.rstrip(b"\x00").decode() == ".sbat": + if section.Name.rstrip(b'\x00').decode() == '.sbat': assert found is False - split = section.get_data().rstrip(b"\x00").decode().splitlines() - assert split == ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"] + split = section.get_data().rstrip(b'\x00').decode().splitlines() + assert split == [ + 'sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md', + 'foo,1', + 'bar,2', + 'baz,3', + ] found = True assert found is True + def unbase64(filename): tmp = tempfile.NamedTemporaryFile() base64.decode(filename.open('rb'), tmp) tmp.flush() return tmp + def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -549,8 +605,9 @@ def test_uname_scraping(kernel_initrd): uname = ukify.Uname.scrape(kernel_initrd[1]) assert re.match(r'\d+\.\d+\.\d+', uname) + @pytest.mark.skipif(not slow_tests, reason='slow') -@pytest.mark.parametrize("days", [365*10, None]) +@pytest.mark.parametrize('days', [365 * 10, None]) def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -585,16 +642,20 @@ def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if shutil.which('sbverify'): # let's check that sbverify likes the resulting file - dump = subprocess.check_output([ - 'sbverify', - '--cert', cert.name, - output, - ], text=True) + dump = subprocess.check_output( + [ + 'sbverify', + '--cert', cert.name, + output, + ], + text=True, + ) # fmt: skip assert 'Signature verification OK' in dump shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_efi_signing_pesign(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -613,16 +674,18 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): subprocess.check_call(cmd) output = f'{tmp_path}/signed.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--signtool=pesign', - '--cmdline=ARG1 ARG2 ARG3', - f'--secureboot-certificate-name={name}', - f'--secureboot-certificate-dir={nss_db}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--signtool=pesign', + '--cmdline=ARG1 ARG2 ARG3', + f'--secureboot-certificate-name={name}', + f'--secureboot-certificate-dir={nss_db}', + ] + ) try: ukify.check_inputs(opts) @@ -632,15 +695,20 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): ukify.make_uki(opts) # let's check that pesign likes the resulting file - dump = subprocess.check_output([ - 'pesign', '-S', - '-i', output, - ], text=True) + dump = subprocess.check_output( + [ + 'pesign', + '-S', + '-i', output, + ], + text=True, + ) # fmt: skip assert f"The signer's common name is {author}" in dump shutil.rmtree(tmp_path) + def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -652,9 +720,9 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): key = unbase64(ourdir / 'example.signing.key.base64') output = f'{tmp_path}/signed2.efi' - uname_arg='1.2.3' - osrel_arg='Linux' if osrel else '' - cmdline_arg='ARG1 ARG2 ARG3' + uname_arg = '1.2.3' + osrel_arg = 'Linux' if osrel else '' + cmdline_arg = 'ARG1 ARG2 ARG3' args = [ 'build', @@ -698,9 +766,11 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): shutil.rmtree(tmp_path) + def test_inspect_no_osrel(kernel_initrd, tmp_path, capsys): test_inspect(kernel_initrd, tmp_path, capsys, osrel=False) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -722,7 +792,7 @@ def test_pcr_signing(kernel_initrd, tmp_path): '--uname=1.2.3', '--cmdline=ARG1 ARG2 ARG3', '--os-release=ID=foobar\n', - '--pcr-banks=sha384', # sha1 might not be allowed, use something else + '--pcr-banks=sha384', # sha1 might not be allowed, use something else f'--pcr-private-key={priv.name}', ] + arg_tools @@ -743,21 +813,25 @@ def test_pcr_signing(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) # objcopy fails when called without an output argument (EPERM). # It also fails when called with /dev/null (file truncated). # It also fails when called with /dev/zero (because it reads the # output file, infinitely in this case.) # So let's just call it with a dummy output argument. - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -766,10 +840,11 @@ def test_pcr_signing(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 4 # four items for four phases + assert len(sig['sha384']) == 4 # four items for four phases shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing2(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -791,24 +866,27 @@ def test_pcr_signing2(kernel_initrd, tmp_path): output = f'{tmp_path}/signed.efi' assert kernel_initrd[0] == '--linux' - opts = ukify.parse_args([ - 'build', - *kernel_initrd[:2], - f'--initrd={microcode.name}', - *kernel_initrd[2:], - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=ID=foobar\n', - '--pcr-banks=sha384', - f'--pcrpkey={pub2.name}', - f'--pcr-public-key={pub.name}', - f'--pcr-private-key={priv.name}', - '--phases=enter-initrd enter-initrd:leave-initrd', - f'--pcr-public-key={pub2.name}', - f'--pcr-private-key={priv2.name}', - '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable - ] + arg_tools) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd[:2], + f'--initrd={microcode.name}', + *kernel_initrd[2:], + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=ID=foobar\n', + '--pcr-banks=sha384', + f'--pcrpkey={pub2.name}', + f'--pcr-public-key={pub.name}', + f'--pcr-private-key={priv.name}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={pub2.name}', + f'--pcr-private-key={priv2.name}', + '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable + ] + + arg_tools + ) try: ukify.check_inputs(opts) @@ -821,16 +899,20 @@ def test_pcr_signing2(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub2.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -841,22 +923,25 @@ def test_pcr_signing2(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 6 # six items for six phases paths + assert len(sig['sha384']) == 6 # six items for six phases paths shutil.rmtree(tmp_path) + def test_key_cert_generation(tmp_path): - opts = ukify.parse_args([ - 'genkey', - f"--pcr-public-key={tmp_path / 'pcr1.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr1.priv.pem'}", - '--phases=enter-initrd enter-initrd:leave-initrd', - f"--pcr-public-key={tmp_path / 'pcr2.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr2.priv.pem'}", - '--phases=sysinit ready', - f"--secureboot-private-key={tmp_path / 'sb.priv.pem'}", - f"--secureboot-certificate={tmp_path / 'sb.cert.pem'}", - ]) + opts = ukify.parse_args( + [ + 'genkey', + f'--pcr-public-key={tmp_path / "pcr1.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr1.priv.pem"}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={tmp_path / "pcr2.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr2.priv.pem"}', + '--phases=sysinit ready', + f'--secureboot-private-key={tmp_path / "sb.priv.pem"}', + f'--secureboot-certificate={tmp_path / "sb.cert.pem"}', + ] + ) assert opts.verb == 'genkey' ukify.check_cert_and_keys_nonexistent(opts) @@ -867,39 +952,46 @@ def test_key_cert_generation(tmp_path): if not shutil.which('openssl'): return - for key in (tmp_path / 'pcr1.priv.pem', - tmp_path / 'pcr2.priv.pem', - tmp_path / 'sb.priv.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-in', key, - '-text', - '-noout', - ], text = True) + for key in (tmp_path / 'pcr1.priv.pem', tmp_path / 'pcr2.priv.pem', tmp_path / 'sb.priv.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-in', key, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Private-Key' in out assert '2048 bit' in out - for pub in (tmp_path / 'pcr1.pub.pem', - tmp_path / 'pcr2.pub.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-pubin', - '-in', pub, - '-text', - '-noout', - ], text = True) + for pub in (tmp_path / 'pcr1.pub.pem', tmp_path / 'pcr2.pub.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-pubin', + '-in', pub, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Public-Key' in out assert '2048 bit' in out - out = subprocess.check_output([ - 'openssl', 'x509', - '-in', tmp_path / 'sb.cert.pem', - '-text', - '-noout', - ], text = True) + out = subprocess.check_output( + [ + 'openssl', 'x509', + '-in', tmp_path / 'sb.cert.pem', + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Certificate' in out assert re.search(r'Issuer: CN\s?=\s?SecureBoot signing key on host', out) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_join_pcrsig(capsys, kernel_initrd, tmp_path): if kernel_initrd is None: @@ -965,5 +1057,6 @@ def test_join_pcrsig(capsys, kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + if __name__ == '__main__': sys.exit(pytest.main(sys.argv)) From 8fd2385709238660b900ae699a791b275b27b985 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:57:35 +0900 Subject: [PATCH 1837/2155] create-sys-script: apply "ruff format" and "ruff check --fix" --- test/create-sys-script.py | 59 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/test/create-sys-script.py b/test/create-sys-script.py index 11ed185de071b..f2995bcfd6a27 100755 --- a/test/create-sys-script.py +++ b/test/create-sys-script.py @@ -1,28 +1,29 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -OUTFILE_HEADER = """#!/usr/bin/env python3 +import filecmp +import os +import stat +import subprocess +import sys +import tempfile + +OUTFILE_HEADER = '''#!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # # create-sys-script.py # # © 2017 Canonical Ltd. # Author: Dan Streetman -""" +''' # Use this only to (re-)create the test/sys-script.py script, # after adding or modifying anything in the test/sys/ directory -import os, sys -import stat -import tempfile -import filecmp -import subprocess - OUTFILE_MODE = 0o775 -OUTFILE_FUNCS = r""" +OUTFILE_FUNCS = r''' import os, sys import shutil @@ -36,9 +37,9 @@ def f(path, mode, contents): with open(path, "wb") as f: f.write(contents) os.chmod(path, mode) -""" +''' -OUTFILE_MAIN = """ +OUTFILE_MAIN = ''' if len(sys.argv) < 2: exit("Usage: {} ".format(sys.argv[0])) @@ -49,7 +50,7 @@ def f(path, mode, contents): if os.path.exists('sys'): shutil.rmtree('sys') -""" +''' def handle_dir(outfile, path): @@ -67,17 +68,17 @@ def escape_single_quotes(b): r = repr(b)[2:-1] # python escapes all ' only if there are ' and " in the string if '"' not in r: - r = r.replace("'", r"\'") + r = r.replace("'", r'\'') # return line with all ' escaped return r def handle_file(outfile, path): m = os.lstat(path).st_mode & 0o777 - with open(path, "rb") as f: + with open(path, 'rb') as f: b = f.read() - if b.count(b"\n") > 1: - r = "\n".join( escape_single_quotes(l) for l in b.split(b"\n") ) + if b.count(b'\n') > 1: + r = '\n'.join(escape_single_quotes(line) for line in b.split(b'\n')) r = f"b'''{r}'''" else: r = repr(b) @@ -85,7 +86,7 @@ def handle_file(outfile, path): def process_sysdir(outfile): - for (dirpath, dirnames, filenames) in os.walk('sys'): + for dirpath, dirnames, filenames in os.walk('sys'): handle_dir(outfile, dirpath) for d in dirnames: path = os.path.join(dirpath, d) @@ -105,17 +106,17 @@ def verify_dir(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISDIR(mode_b): - raise Exception("Not directory") + raise Exception('Not directory') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') def verify_link(tmpd, path_a): path_b = os.path.join(tmpd, path_a) if not stat.S_ISLNK(os.lstat(path_b).st_mode): - raise Exception("Not symlink") + raise Exception('Not symlink') if os.readlink(path_a) != os.readlink(path_b): - raise Exception("Symlink dest mismatch") + raise Exception('Symlink dest mismatch') def verify_file(tmpd, path_a): @@ -123,16 +124,16 @@ def verify_file(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISREG(mode_b): - raise Exception("Not file") + raise Exception('Not file') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') if not filecmp.cmp(path_a, path_b, shallow=False): - raise Exception("File contents mismatch") + raise Exception('File contents mismatch') def verify_script(tmpd): any = False - for (dirpath, dirnames, filenames) in os.walk("sys"): + for dirpath, dirnames, filenames in os.walk('sys'): any = True try: path = dirpath @@ -154,7 +155,8 @@ def verify_script(tmpd): if not any: exit('Nothing found!') -if __name__ == "__main__": + +if __name__ == '__main__': if len(sys.argv) < 2: exit('Usage: create-sys-script.py /path/to/test/') @@ -163,10 +165,9 @@ def verify_script(tmpd): os.chdir(sys.argv[1]) - with open(outfile, "w") as f: + with open(outfile, 'w') as f: os.chmod(outfile, OUTFILE_MODE) - f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), - os.path.basename(outfile))) + f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), os.path.basename(outfile))) f.write(OUTFILE_FUNCS) f.write(OUTFILE_MAIN) process_sysdir(f) From a562d59fd9f9f0f07c82910c6336a5c7bd135518 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:42:27 +0900 Subject: [PATCH 1838/2155] generate-directives: apply "ruff check --fix" --- test/fuzz/generate-directives.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/fuzz/generate-directives.py b/test/fuzz/generate-directives.py index d05108962f7b8..0f6a744e34421 100755 --- a/test/fuzz/generate-directives.py +++ b/test/fuzz/generate-directives.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later +import collections +import re import sys -import collections, re d = collections.defaultdict(list) for line in open(sys.argv[1]): From f6b2a27e5956b510be992c6dc0fe52a5d4789833 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:58:16 +0900 Subject: [PATCH 1839/2155] TEST-04-JOURNAL: apply "ruff format" --- .../TEST-04-JOURNAL.units/logs-filtering-syslog.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py b/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py index e1d3ad7c116e6..e56348a7a1ddd 100755 --- a/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py +++ b/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py @@ -5,10 +5,7 @@ import syslog if __name__ == '__main__': - syslog.openlog(ident="logs-filtering", logoption=syslog.LOG_PID) - syslog.syslog(syslog.LOG_NOTICE, "Logging from the service, and ~more~ foo bar") + syslog.openlog(ident='logs-filtering', logoption=syslog.LOG_PID) + syslog.syslog(syslog.LOG_NOTICE, 'Logging from the service, and ~more~ foo bar') - subprocess.check_output( - ['journalctl', '--sync'], - stdin=subprocess.DEVNULL, - text=True) + subprocess.check_output(['journalctl', '--sync'], stdin=subprocess.DEVNULL, text=True) From ba90ffaef20081a177005e8aa42c70a08aa438d5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:59:13 +0900 Subject: [PATCH 1840/2155] networkd-test: apply "ruff format" and "ruff check --fix" --- test/networkd-test.py | 561 ++++++++++++++++++++++++++---------------- 1 file changed, 343 insertions(+), 218 deletions(-) diff --git a/test/networkd-test.py b/test/networkd-test.py index d9f7af3678260..27619da6ff1ab 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -33,8 +33,7 @@ NETWORK_UNITDIR = '/run/systemd/network' -NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', - path='/usr/lib/systemd:/lib/systemd') +NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', path='/usr/lib/systemd:/lib/systemd') RESOLV_CONF = '/run/systemd/resolve/resolv.conf' @@ -44,10 +43,10 @@ def setUpModule(): - global tmpmounts - """Initialize the environment, and perform sanity checks on it.""" + global tmpmounts + if shutil.which('networkctl') is None: raise unittest.SkipTest('networkd not installed') if shutil.which('resolvectl') is None: @@ -57,8 +56,10 @@ def setUpModule(): raise OSError(errno.ENOENT, 'systemd-networkd-wait-online not found') # Do not run any tests if the system is using networkd already and it's not virtualized - if (subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 and - subprocess.call(['systemd-detect-virt', '--quiet']) != 0): + if ( + subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 + and subprocess.call(['systemd-detect-virt', '--quiet']) != 0 + ): raise unittest.SkipTest('not virtualized and networkd is already active') # Ensure we don't mess with an existing networkd config @@ -80,7 +81,9 @@ def setUpModule(): # Generate debugging logs. os.makedirs('/run/systemd/system/systemd-networkd.service.d', exist_ok=True) - with open(f'/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8') as f: + with open( + '/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8' + ) as f: f.write('[Service]\nEnvironment=SYSTEMD_LOG_LEVEL=debug\n') subprocess.call(['systemctl', 'daemon-reload']) @@ -91,10 +94,15 @@ def setUpModule(): if subprocess.call(['getent', 'passwd', 'systemd-network']) != 0: subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network']) - for d in ['/etc/systemd/network', '/run/systemd/network', - '/run/systemd/netif', '/run/systemd/report', - '/run/systemd/resolve', '/run/systemd/resolve.hook']: - subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d]) + for d in [ + '/etc/systemd/network', + '/run/systemd/network', + '/run/systemd/netif', + '/run/systemd/report', + '/run/systemd/resolve', + '/run/systemd/resolve.hook', + ]: + subprocess.check_call(['mount', '-m', '-t', 'tmpfs', 'none', d]) tmpmounts.append(d) if os.path.isdir('/run/systemd/resolve'): os.chmod('/run/systemd/resolve', 0o755) @@ -119,11 +127,11 @@ def setUpModule(): def tearDownModule(): global tmpmounts for d in tmpmounts: - subprocess.check_call(["umount", "--lazy", d]) + subprocess.check_call(['umount', '--lazy', d]) for u in stopped_units: - subprocess.call(["systemctl", "stop", u]) + subprocess.call(['systemctl', 'stop', u]) for u in running_units: - subprocess.call(["systemctl", "restart", u]) + subprocess.call(['systemctl', 'restart', u]) class NetworkdTestingUtilities: @@ -135,14 +143,12 @@ class NetworkdTestingUtilities: def add_veth_pair(self, veth, peer, veth_options=(), peer_options=()): """Add a veth interface pair, and queue them to be removed.""" - subprocess.check_call(['ip', 'link', 'add', 'name', veth] + - list(veth_options) + - ['type', 'veth', 'peer', 'name', peer] + - list(peer_options)) + subprocess.check_call(['ip', 'link', 'add', 'name', veth, *veth_options, + 'type', 'veth', 'peer', 'name', peer, *peer_options]) # fmt: skip self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', peer]) def write_config(self, path, contents): - """"Write a configuration file, and queue it to be removed.""" + """ "Write a configuration file, and queue it to be removed.""" with open(path, 'w') as f: f.write(contents) @@ -155,8 +161,8 @@ def write_network(self, unit_name, contents): def write_network_dropin(self, unit_name, dropin_name, contents): """Write a network unit drop-in, and queue it to be removed.""" - dropin_dir = os.path.join(NETWORK_UNITDIR, "{}.d".format(unit_name)) - dropin_path = os.path.join(dropin_dir, "{}.conf".format(dropin_name)) + dropin_dir = os.path.join(NETWORK_UNITDIR, f'{unit_name}.d') + dropin_path = os.path.join(dropin_dir, f'{dropin_name}.conf') os.makedirs(dropin_dir, exist_ok=True) self.addCleanup(os.rmdir, dropin_dir) @@ -169,7 +175,7 @@ def read_attr(self, link, attribute): # Note we don't want to check if interface `link' is managed, we # want to evaluate link variable and pass the value of the link to # assert_link_states e.g. eth0=managed. - self.assert_link_states(**{link:'managed'}) + self.assert_link_states(**{link: 'managed'}) with open(os.path.join('/sys/class/net', link, attribute)) as f: return f.readline().strip() @@ -191,8 +197,7 @@ def assert_link_states(self, **kwargs): interfaces = set(kwargs) # Wait for the requested interfaces, but don't fail for them. - subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + - ['--interface={}'.format(iface) for iface in kwargs]) + subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + [f'--interface={iface}' for iface in kwargs]) # Validate each link state found in the networkctl output. out = subprocess.check_output(['networkctl', '--no-legend']).rstrip() @@ -202,14 +207,13 @@ def assert_link_states(self, **kwargs): iface = fields[1] expected = kwargs[iface] actual = fields[-1] - if (actual != expected and - not (expected == 'managed' and actual != 'unmanaged')): - self.fail("Link {} expects state {}, found {}".format(iface, expected, actual)) + if actual != expected and not (expected == 'managed' and actual != 'unmanaged'): + self.fail(f'Link {iface} expects state {expected}, found {actual}') interfaces.remove(iface) # Ensure that all requested interfaces have been covered. if interfaces: - self.fail("Missing links in status output: {}".format(interfaces)) + self.fail(f'Missing links in status output: {interfaces}') class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): @@ -217,7 +221,9 @@ class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): def wait_online(self): try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10'] + ) except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure print('---- interface status ----') @@ -233,40 +239,59 @@ def wait_online(self): print('---- journal ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service']) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service'] + ) raise def setUp(self): - self.write_network('50-port1.netdev', '''\ + self.write_network( + '50-port1.netdev', + '''\ [NetDev] Name=port1 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-port2.netdev', '''\ +''', + ) + self.write_network( + '50-port2.netdev', + '''\ [NetDev] Name=port2 Kind=dummy MACAddress=12:34:56:78:9a:bd -''') - self.write_network('50-mybridge.netdev', '''\ +''', + ) + self.write_network( + '50-mybridge.netdev', + '''\ [NetDev] Name=mybridge Kind=bridge -''') - self.write_network('50-port1.network', '''\ +''', + ) + self.write_network( + '50-port1.network', + '''\ [Match] Name=port1 [Network] Bridge=mybridge -''') - self.write_network('50-port2.network', '''\ +''', + ) + self.write_network( + '50-port2.network', + '''\ [Match] Name=port2 [Network] Bridge=mybridge -''') - self.write_network('50-mybridge.network', '''\ +''', + ) + self.write_network( + '50-mybridge.network', + '''\ [Match] Name=mybridge [Network] @@ -274,7 +299,8 @@ def setUp(self): DNS=192.168.250.1 Address=192.168.250.33/24 Gateway=192.168.250.1 -''') +''', + ) subprocess.call(['systemctl', 'reset-failed', 'systemd-networkd', 'systemd-resolved']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -290,17 +316,18 @@ def tearDown(self): subprocess.check_call(['ip', 'link', 'del', 'port2']) def test_bridge_init(self): - self.assert_link_states( - port1='managed', - port2='managed', - mybridge='managed') + self.assert_link_states(port1='managed', port2='managed', mybridge='managed') def test_bridge_port_priority(self): self.assertEqual(self.read_attr('port1', 'brport/priority'), '32') - self.write_network_dropin('50-port1.network', 'priority', '''\ + self.write_network_dropin( + '50-port1.network', + 'priority', + '''\ [Bridge] Priority=28 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port1', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -309,10 +336,14 @@ def test_bridge_port_priority(self): def test_bridge_port_priority_set_zero(self): """It should be possible to set the bridge port priority to 0""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'priority', '''\ + self.write_network_dropin( + '50-port2.network', + 'priority', + '''\ [Bridge] Priority=0 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -321,7 +352,10 @@ def test_bridge_port_priority_set_zero(self): def test_bridge_port_property(self): """Test the "[Bridge]" section keys""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'property', '''\ + self.write_network_dropin( + '50-port2.network', + 'property', + '''\ [Bridge] UnicastFlood=true HairPin=true @@ -331,7 +365,8 @@ def test_bridge_port_property(self): AllowPortToBeRoot=true Cost=555 Priority=23 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -345,14 +380,15 @@ def test_bridge_port_property(self): self.assertEqual(self.read_attr('port2', 'brport/bpdu_guard'), '0') self.assertEqual(self.read_attr('port2', 'brport/root_block'), '0') + class ClientTestBase(NetworkdTestingUtilities): """Provide common methods for testing networkd against servers.""" @classmethod def setUpClass(klass): klass.orig_log_level = subprocess.check_output( - ['systemctl', 'show', '--value', '--property', 'LogLevel'], - universal_newlines=True).strip() + ['systemctl', 'show', '--value', '--property', 'LogLevel'], universal_newlines=True + ).strip() subprocess.check_call(['systemd-analyze', 'log-level', 'debug']) @classmethod @@ -368,9 +404,9 @@ def setUp(self): # get current journal cursor subprocess.check_output(['journalctl', '--sync']) - out = subprocess.check_output(['journalctl', '-b', '--quiet', - '--no-pager', '-n0', '--show-cursor'], - universal_newlines=True) + out = subprocess.check_output( + ['journalctl', '-b', '--quiet', '--no-pager', '-n0', '--show-cursor'], universal_newlines=True + ) self.assertTrue(out.startswith('-- cursor:')) self.journal_cursor = out.split()[-1] @@ -383,44 +419,44 @@ def tearDown(self): subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink-metrics.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd.service']) - subprocess.call(['ip', 'link', 'del', 'dummy0'], - stderr=subprocess.DEVNULL) + subprocess.call(['ip', 'link', 'del', 'dummy0'], stderr=subprocess.DEVNULL) def show_journal(self, unit): - '''Show journal of given unit since start of the test''' + """Show journal of given unit since start of the test""" - print('---- {} ----'.format(unit)) + print(f'---- {unit} ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', - '--cursor', self.journal_cursor, '-u', unit]) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '--cursor', self.journal_cursor, '-u', unit] + ) def show_ifaces(self): - '''Show network interfaces''' + """Show network interfaces""" print('--- networkctl ---') sys.stdout.flush() subprocess.call(['networkctl', 'status', '-n', '0', '-a']) def show_resolvectl(self): - '''Show resolved settings''' + """Show resolved settings""" print('--- resolvectl ---') sys.stdout.flush() subprocess.call(['resolvectl']) def create_iface(self, ipv6=False): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" raise NotImplementedError('must be implemented by a subclass') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" raise NotImplementedError('must be implemented by a subclass') def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" raise NotImplementedError('must be implemented by a subclass') @@ -432,16 +468,18 @@ def start_unit(self, unit): self.show_journal(unit) raise - def do_test(self, coldplug=True, ipv6=False, extra_opts='', - online_timeout=10, dhcp_mode='yes'): + def do_test(self, coldplug=True, ipv6=False, extra_opts='', online_timeout=10, dhcp_mode='yes'): self.start_unit('systemd-resolved') - self.write_network(self.config, '''\ + self.write_network( + self.config, + f'''\ [Match] -Name={iface} +Name={self.iface} [Network] DHCP={dhcp_mode} {extra_opts} -'''.format(iface=self.iface, dhcp_mode=dhcp_mode, extra_opts=extra_opts)) +''', + ) if coldplug: # create interface first, then start networkd @@ -456,8 +494,9 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', self.start_unit('systemd-networkd') try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', - self.iface, '--timeout=%i' % online_timeout]) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, f'--timeout={online_timeout:d}'] + ) if ipv6: # check iface state and IP 6 address; FIXME: we need to wait a bit @@ -465,7 +504,12 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', # IPv6, but we want to wait for both for _ in range(10): out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface]) - if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out and b'tentative' not in out: + if ( + b'state UP' in out + and b'inet6 2600' in out + and b'inet 192.168' in out + and b'tentative' not in out + ): break time.sleep(1) else: @@ -476,43 +520,43 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', else: # should have link-local address on IPv6 only out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface]) - self.assertRegex(out, br'inet6 fe80::.* scope link') + self.assertRegex(out, rb'inet6 fe80::.* scope link') self.assertNotIn(b'scope global', out) # should have IPv4 address out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertIn(b'state UP', out) - self.assertRegex(out, br'inet 192.168.5.\d+/.* scope global dynamic') + self.assertRegex(out, rb'inet 192.168.5.\d+/.* scope global dynamic') # check networkctl state out = subprocess.check_output(['networkctl']) - self.assertRegex(out, (r'{}\s+ether\s+[a-z-]+\s+unmanaged'.format(self.if_router)).encode()) - self.assertRegex(out, (r'{}\s+ether\s+routable\s+configured'.format(self.iface)).encode()) + self.assertRegex(out, (rf'{self.if_router}\s+ether\s+[a-z-]+\s+unmanaged').encode()) + self.assertRegex(out, (rf'{self.iface}\s+ether\s+routable\s+configured').encode()) out = subprocess.check_output(['networkctl', '-n', '0', 'status', self.iface]) - self.assertRegex(out, br'Type:\s+ether') - self.assertRegex(out, br'State:\s+routable.*configured') - self.assertRegex(out, br'Online state:\s+online') - self.assertRegex(out, br'Address:\s+192.168.5.\d+') + self.assertRegex(out, rb'Type:\s+ether') + self.assertRegex(out, rb'State:\s+routable.*configured') + self.assertRegex(out, rb'Online state:\s+online') + self.assertRegex(out, rb'Address:\s+192.168.5.\d+') if ipv6: - self.assertRegex(out, br'2600::') + self.assertRegex(out, rb'2600::') else: - self.assertNotIn(br'2600::', out) - self.assertRegex(out, br'fe80::') - self.assertRegex(out, br'Gateway:\s+192.168.5.1') - self.assertRegex(out, br'DNS:\s+192.168.5.1') + self.assertNotIn(rb'2600::', out) + self.assertRegex(out, rb'fe80::') + self.assertRegex(out, rb'Gateway:\s+192.168.5.1') + self.assertRegex(out, rb'DNS:\s+192.168.5.1') except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure with open(os.path.join(NETWORK_UNITDIR, self.config)) as f: - print('\n---- {} ----\n{}'.format(self.config, f.read())) + print(f'\n---- {self.config} ----\n{f.read()}') print('---- interface status ----') sys.stdout.flush() subprocess.call(['ip', 'a', 'show', 'dev', self.iface]) - print('---- networkctl status {} ----'.format(self.iface)) + print(f'---- networkctl status {self.iface} ----') sys.stdout.flush() rc = subprocess.call(['networkctl', '-n', '0', 'status', self.iface]) if rc != 0: - print("'networkctl status' exited with an unexpected code {}".format(rc)) + print(f"'networkctl status' exited with an unexpected code {rc}") self.show_journal('systemd-networkd.service') self.print_server_log() raise @@ -536,18 +580,15 @@ def test_coldplug_dhcp_yes_ip4(self): def test_coldplug_dhcp_yes_ip4_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip4_only(self): # we have a 12s timeout on RA, so we need to wait longer - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - online_timeout=15) + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', online_timeout=15) def test_coldplug_dhcp_ip4_only_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip6(self): self.do_test(coldplug=True, ipv6=True) @@ -560,13 +601,18 @@ def test_hotplug_dhcp_ip6(self): self.do_test(coldplug=False, ipv6=True) def test_route_only_dns(self): - self.write_network('50-myvpn.netdev', '''\ + self.write_network( + '50-myvpn.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''\ +''', + ) + self.write_network( + '50-myvpn.network', + '''\ [Match] Name=dummy0 [Network] @@ -574,11 +620,11 @@ def test_route_only_dns(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -596,23 +642,28 @@ def test_route_only_dns(self): self.assertNotIn('nameserver 192.168.42.1\n', contents) def test_route_only_dns_all_domains(self): - self.write_network('50-myvpn.netdev', '''[NetDev] + self.write_network( + '50-myvpn.netdev', + '''[NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''[Match] +''', + ) + self.write_network( + '50-myvpn.network', + '''[Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company ~. -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -634,7 +685,7 @@ def test_route_only_dns_all_domains(self): @unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed') class DnsmasqClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against dnsmasq''' + """Test networkd client against dnsmasq""" def setUp(self): super().setUp() @@ -642,12 +693,25 @@ def setUp(self): self.iface_mac = 'de:ad:be:ef:47:11' def create_iface(self, ipv6=False, dnsmasq_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # add veth pair - subprocess.check_call(['ip', 'link', 'add', 'name', self.iface, - 'address', self.iface_mac, - 'type', 'veth', 'peer', 'name', self.if_router]) + subprocess.check_call( + [ + 'ip', + 'link', + 'add', + 'name', + self.iface, + 'address', + self.iface_mac, + 'type', + 'veth', + 'peer', + 'name', + self.if_router, + ] + ) # give our router an IP subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router]) @@ -666,14 +730,24 @@ def create_iface(self, ipv6=False, dnsmasq_opts=None): if dnsmasq_opts: extra_opts += dnsmasq_opts self.dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', '--log-dhcp', - '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=' + lease_file, '--bind-interfaces', - '--interface=' + self.if_router, '--except-interface=lo', - '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-dhcp', + '--log-facility=' + self.dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=' + lease_file, + '--bind-interfaces', + '--interface=' + self.if_router, + '--except-interface=lo', + '--dhcp-range=192.168.5.10,192.168.5.200', + ] + + extra_opts + ) def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router]) @@ -684,16 +758,17 @@ def shutdown_iface(self): self.dnsmasq = None def print_server_log(self, log_file=None): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" path = log_file if log_file else self.dnsmasq_log with open(path) as f: - sys.stdout.write('\n\n---- {} ----\n{}\n------\n\n'.format(os.path.basename(path), f.read())) + sys.stdout.write(f'\n\n---- {os.path.basename(path)} ----\n{f.read()}\n------\n\n') def test_resolved_domain_restricted_dns(self): - '''resolved: domain-restricted DNS servers''' + """resolved: domain-restricted DNS servers""" - # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at logs easier + # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at + # logs easier conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' os.makedirs(os.path.dirname(conf), exist_ok=True) with open(conf, 'w') as f: @@ -703,14 +778,17 @@ def test_resolved_domain_restricted_dns(self): # create interface for generic connections; this will map all DNS names # to 192.168.42.1 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1']) - self.write_network('50-general.network', '''\ + self.write_network( + '50-general.network', + f'''\ [Match] -Name={} +Name={self.iface} [Network] DHCP=ipv4 IPv6AcceptRA=no DNSSECNegativeTrustAnchors=search.example.com -'''.format(self.iface)) +''', + ) # create second device/dnsmasq for a .company/.lab VPN interface # static IPs for simplicity @@ -721,15 +799,26 @@ def test_resolved_domain_restricted_dns(self): vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log') vpn_dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', - '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=/dev/null', '--bind-interfaces', - '--interface=testvpnrouter', '--except-interface=lo', - '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4']) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-facility=' + vpn_dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=/dev/null', + '--bind-interfaces', + '--interface=testvpnrouter', + '--except-interface=lo', + '--address=/math.lab/10.241.3.3', + '--address=/cantina.company/10.241.4.4', + ] + ) self.addCleanup(vpn_dnsmasq.wait) self.addCleanup(vpn_dnsmasq.kill) - self.write_network('50-vpn.network', '''\ + self.write_network( + '50-vpn.network', + '''\ [Match] Name=testvpnclient [Network] @@ -738,11 +827,13 @@ def test_resolved_domain_restricted_dns(self): DNS=10.241.3.1 Domains=~company ~lab DNSSECNegativeTrustAnchors=company lab -''') +''', + ) self.start_unit('systemd-networkd') - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface, - '--interface=testvpnclient', '--timeout=20']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, '--interface=testvpnclient', '--timeout=20'] + ) # ensure we start fresh with every test subprocess.check_call(['systemctl', 'restart', 'systemd-resolved']) @@ -784,7 +875,7 @@ def test_resolved_domain_restricted_dns(self): raise def test_resolved_etc_hosts(self): - '''resolved queries to /etc/hosts''' + """resolved queries to /etc/hosts""" # enabled DNSSEC in allow-downgrade mode conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' @@ -811,10 +902,14 @@ def test_resolved_etc_hosts(self): # note: different IPv4 address here, so that it's easy to tell apart # what resolved the query - self.create_iface(dnsmasq_opts=['--host-record=my.example.com,172.16.99.1,2600::99:99', - '--host-record=other.example.com,172.16.0.42,2600::42', - '--mx-host=example.com,mail.example.com'], - ipv6=True) + self.create_iface( + dnsmasq_opts=[ + '--host-record=my.example.com,172.16.99.1,2600::99:99', + '--host-record=other.example.com,172.16.0.42,2600::42', + '--mx-host=example.com,mail.example.com', + ], + ipv6=True, + ) self.do_test(coldplug=None, ipv6=True) try: @@ -848,7 +943,7 @@ def test_resolved_etc_hosts(self): raise def test_transient_hostname(self): - '''networkd sets transient hostname from DHCP''' + """networkd sets transient hostname from DHCP""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) @@ -859,7 +954,7 @@ def test_transient_hostname(self): subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -873,10 +968,10 @@ def test_transient_hostname(self): if b'testgreen' in out: break time.sleep(5) - sys.stdout.write('[retry %i] ' % retry) + sys.stdout.write(f'[retry {retry}] ') sys.stdout.flush() else: - self.fail('Transient hostname not found in hostnamectl:\n{}'.format(out.decode())) + self.fail(f'Transient hostname not found in hostnamectl:\n{out.decode()}') # and also applied to the system self.assertEqual(socket.gethostname(), 'testgreen') except AssertionError: @@ -886,24 +981,23 @@ def test_transient_hostname(self): raise def test_transient_hostname_with_static(self): - '''transient hostname is not applied if static hostname exists''' + """transient hostname is not applied if static hostname exists""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) if not os.path.exists('/etc/hostname'): - self.write_config('/etc/hostname', "foobarqux") + self.write_config('/etc/hostname', 'foobarqux') else: - self.write_config('/run/hostname.tmp', "foobarqux") + self.write_config('/run/hostname.tmp', 'foobarqux') subprocess.check_call(['mount', '--bind', '/run/hostname.tmp', '/etc/hostname']) self.addCleanup(subprocess.call, ['umount', '/etc/hostname']) - socket.sethostname("foobarqux"); - + socket.sethostname('foobarqux') subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -911,7 +1005,7 @@ def test_transient_hostname_with_static(self): out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic') # static hostname wins over transient one, thus *not* applied - self.assertEqual(socket.gethostname(), "foobarqux") + self.assertEqual(socket.gethostname(), 'foobarqux') except AssertionError: self.show_journal('systemd-networkd.service') self.show_journal('systemd-hostnamed.service') @@ -920,21 +1014,22 @@ def test_transient_hostname_with_static(self): class NetworkdClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against networkd server''' + """Test networkd client against networkd server""" def setUp(self): super().setUp() self.dnsmasq = None def create_iface(self, ipv6=False, dhcpserver_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # run "router-side" networkd in own mount namespace to shield it from # "client-side" configuration and networkd (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh') self.addCleanup(os.remove, script) with os.fdopen(fd, 'w+') as f: - f.write('''\ + f.write( + '''\ #!/bin/sh set -eu mkdir -p /run/systemd/network @@ -984,34 +1079,46 @@ def create_iface(self, ipv6=False, dhcpserver_opts=None): # run networkd as in systemd-networkd.service exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=//; s/^[@+-]//; s/^!*//; p}}') -'''.format(ifr=self.if_router, - ifc=self.iface, - addr6=('Address=2600::1/64' if ipv6 else ''), - dhopts=(dhcpserver_opts or ''))) +'''.format( + ifr=self.if_router, + ifc=self.iface, + addr6=('Address=2600::1/64' if ipv6 else ''), + dhopts=(dhcpserver_opts or ''), + ) + ) os.fchmod(fd, 0o755) - subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service', - '-p', 'InaccessibleDirectories=-/etc/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/netif', - '-p', 'InaccessibleDirectories=-/run/systemd/report', - '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', - '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', - '--service-type=notify', script]) + subprocess.check_call( + [ + 'systemd-run', + '--unit=networkd-test-router.service', + '-p', 'InaccessibleDirectories=-/etc/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '-p', 'InaccessibleDirectories=-/run/systemd/report', + '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', + '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', + '--service-type=notify', + script, + ] + ) # fmt: skip # wait until devices got created for _ in range(50): - if subprocess.run(['ip', 'link', 'show', 'dev', self.if_router], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: + if subprocess.run( + ['ip', 'link', 'show', 'dev', self.if_router], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ).returncode == 0: # fmt: skip break time.sleep(0.1) else: subprocess.call(['ip', 'link', 'show', 'dev', self.if_router]) - self.fail('Timed out waiting for {ifr} created.'.format(ifr=self.if_router)) + self.fail(f'Timed out waiting for {self.if_router} created.') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service']) @@ -1021,7 +1128,7 @@ def shutdown_iface(self): self.if_router = None def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" self.show_journal('networkd-test-router.service') @@ -1038,13 +1145,18 @@ def test_search_domains(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] @@ -1052,7 +1164,8 @@ def test_search_domains(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= one two three four five six seven eight nine ten -''') +''', + ) self.start_unit('systemd-networkd') @@ -1068,24 +1181,34 @@ def test_dropin(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 -''') - self.write_network_dropin('50-test.network', 'dns', '''\ +''', + ) + self.write_network_dropin( + '50-test.network', + 'dns', + '''\ [Network] DNS=127.0.0.1 -''') +''', + ) self.start_unit('systemd-resolved') self.start_unit('systemd-networkd') @@ -1106,11 +1229,19 @@ def test_dropin(self): self.fail(f'Expected DNS servers not found in resolv.conf: {contents}') def test_dhcp_timezone(self): - '''networkd sets time zone from DHCP''' + """networkd sets time zone from DHCP""" def get_tz(): - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1', - '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.timedate1', + '/org/freedesktop/timedate1', + 'org.freedesktop.timedate1', + 'Timezone', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') @@ -1120,7 +1251,9 @@ def get_tz(): self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone]) self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu') - self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4') + self.do_test( + coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4' + ) # Should have applied the received timezone. This is asynchronous, so we need to wait for a while: for _ in range(20): @@ -1151,12 +1284,15 @@ def tearDown(self): def test_basic_matching(self): """Verify the Name= line works throughout this class.""" self.add_veth_pair('test_if1', 'fake_if2') - self.write_network('50-test.network', '''\ + self.write_network( + '50-test.network', + '''\ [Match] Name=test_* [Network] IPv6AcceptRA=no -''') +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_if1='managed', fake_if2='unmanaged') @@ -1165,15 +1301,17 @@ def test_inverted_matching(self): # Use a MAC address as the interfaces' common matching attribute # to avoid depending on udev, to support testing in containers. mac = '00:01:02:03:98:99' - self.add_veth_pair('test_veth', 'test_peer', - ['addr', mac], ['addr', mac]) - self.write_network('50-no-veth.network', '''\ + self.add_veth_pair('test_veth', 'test_peer', ['addr', mac], ['addr', mac]) + self.write_network( + '50-no-veth.network', + f'''\ [Match] -MACAddress={} +MACAddress={mac} Name=!nonexistent *peer* [Network] IPv6AcceptRA=no -'''.format(mac)) +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_veth='managed', test_peer='unmanaged') @@ -1192,14 +1330,14 @@ def setUp(self): # Define the contents of .network files to be read in order. self.configs = ( - "[Match]\nName=m1def\n", - "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n", - "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n", + '[Match]\nName=m1def\n', + '[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n', + '[Match]\nName=m1*\n[Link]\nUnmanaged=no\n', ) # Write out the .network files to be cleaned up automatically. for i, config in enumerate(self.configs): - self.write_network("%02d-test.network" % i, config) + self.write_network(f'{i:02d}-test.network', config) def tearDown(self): """Stop networkd.""" @@ -1215,43 +1353,30 @@ def test_unmanaged_setting(self): """Verify link states with Unmanaged= settings, hot-plug.""" subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_unmanaged_setting_coldplug(self): """Verify link states with Unmanaged= settings, cold-plug.""" self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_catchall_config(self): """Verify link states with a catch-all config, hot-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') def test_catchall_config_coldplug(self): """Verify link states with a catch-all config, cold-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, - verbosity=2)) + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) From 2c73e5b973ac4e1469a50a9474c5a9ac5cedefb2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 01:59:51 +0900 Subject: [PATCH 1841/2155] rule-syntax-check: apply "ruff format" and "ruff check --fix" --- test/rule-syntax-check.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py index ec1c75a854cda..777012a2ac98d 100755 --- a/test/rule-syntax-check.py +++ b/test/rule-syntax-check.py @@ -14,21 +14,21 @@ sys.exit('Specify files to test as arguments') quoted_string_re = r'"(?:[^\\"]|\\.)*"' -no_args_tests = re.compile(r'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*' + quoted_string_re + '$') +no_args_tests = re.compile(rf'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*{quoted_string_re}$') # fmt: skip # PROGRAM can also be specified as an assignment. program_assign = re.compile(r'PROGRAM\s*=\s*' + quoted_string_re + '$') -args_tests = re.compile(r'(ATTRS?|ENV|CONST|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*' + quoted_string_re + '$') -no_args_assign = re.compile(r'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*' + quoted_string_re + '$') -args_assign = re.compile(r'(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*' + quoted_string_re + '$') +args_tests = re.compile(rf'(ATTRS?|ENV|CONST|TEST){{([a-zA-Z0-9/_.*%-]+)}}\s*(?:=|!)=\s*{quoted_string_re}$') +no_args_assign = re.compile(rf'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*{quoted_string_re}$') # fmt: skip +args_assign = re.compile(rf'(ATTR|ENV|IMPORT|RUN){{([a-zA-Z0-9/_.*%-]+)}}\s*(=|\+=)\s*{quoted_string_re}$') # Find comma-separated groups, but allow commas that are inside quoted strings. # Using quoted_string_re + '?' so that strings missing the last double quote # will still match for this part that splits on commas. -comma_separated_group_re = re.compile(r'(?:[^,"]|' + quoted_string_re + '?)+') +comma_separated_group_re = re.compile(rf'(?:[^,"]|{quoted_string_re}?)+') result = 0 buffer = '' for path in rules_files: - print('# looking at {}'.format(path)) + print(f'# looking at {path}') lineno = 0 for line in open(path): lineno += 1 @@ -50,11 +50,14 @@ # it generally improves the readability of the rules. for clause_match in comma_separated_group_re.finditer(line): clause = clause_match.group().strip() - if not (no_args_tests.match(clause) or args_tests.match(clause) or - no_args_assign.match(clause) or args_assign.match(clause) or - program_assign.match(clause)): - - print('Invalid line {}:{}: {}'.format(path, lineno, line)) + if not ( + no_args_tests.match(clause) + or args_tests.match(clause) + or no_args_assign.match(clause) + or args_assign.match(clause) + or program_assign.match(clause) + ): + print(f'Invalid line {path}:{lineno}: {line}') print(' clause:', clause) print() result = 1 From d89d23bc8ff89972a12ecbe80b7119542205270d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:00:21 +0900 Subject: [PATCH 1842/2155] run-unit-tests: apply "ruff format" --- test/run-unit-tests.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/test/run-unit-tests.py b/test/run-unit-tests.py index de8ac5c26cb43..1b790d7a5cf47 100755 --- a/test/run-unit-tests.py +++ b/test/run-unit-tests.py @@ -6,8 +6,10 @@ import pathlib import subprocess import sys + try: import colorama as c + GREEN = c.Fore.GREEN YELLOW = c.Fore.YELLOW RED = c.Fore.RED @@ -16,23 +18,38 @@ except ImportError: GREEN = YELLOW = RED = RESET_ALL = BRIGHT = '' + class total: total = None good = 0 skip = 0 fail = 0 + def argument_parser(): p = argparse.ArgumentParser() - p.add_argument('-u', '--unsafe', action='store_true', - help='run "unsafe" tests too') - p.add_argument('-A', '--artifact_directory', - help='store output from failed tests in this dir') - p.add_argument('-s', '--skip', action='append', default=[], - help='skip the named test') + p.add_argument( + '-u', + '--unsafe', + action='store_true', + help='run "unsafe" tests too', + ) + p.add_argument( + '-A', + '--artifact_directory', + help='store output from failed tests in this dir', + ) + p.add_argument( + '-s', + '--skip', + action='append', + default=[], + help='skip the named test', + ) return p + opts = argument_parser().parse_args() unittestdir = pathlib.Path(__file__).parent.absolute() / 'unit-tests' From 87c0cf15fe24442e5c6ebd00a1517f6018c60054 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:00:40 +0900 Subject: [PATCH 1843/2155] sd-script: apply "ruff format" and "ruff check --fix" --- test/sd-script.py | 104 ++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/test/sd-script.py b/test/sd-script.py index 51ebf70c39eca..432136e692e6b 100755 --- a/test/sd-script.py +++ b/test/sd-script.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # +# ruff: noqa: F821 +# # sd-script.py: create LOTS of sd device entries in fake sysfs # # (C) 2018 Martin Wilck, SUSE Linux GmbH @@ -15,63 +17,67 @@ # Note that sys-script.py already creates 10 sd device nodes # (sda+sdb and partitions). This script starts with sdc. -import re -import os import errno +import os +import re import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) -class SD(object): +class SD: sd_major = [8] + list(range(65, 72)) + list(range(128, 136)) _name_re = re.compile(r'sd(?P[a-z]*)$') def _init_from_name(self, name): mt = self._name_re.match(name) if mt is None: - raise RuntimeError("invalid name %s" % name) - nm = mt.group("f") + raise RuntimeError(f'invalid name {name}') + nm = mt.group('f') base = 1 ls = nm[-1] nm = nm[:-1] - n = base * (ord(ls)-ord('a')) + n = base * (ord(ls) - ord('a')) while len(nm) > 0: ls = nm[-1] nm = nm[:-1] base *= 26 - n += base * (1 + ord(ls)-ord('a')) + n += base * (1 + ord(ls) - ord('a')) self._num = n def _init_from_dev(self, dev): - maj, min = dev.split(":") + maj, min = dev.split(':') maj = self.sd_major.index(int(maj, 10)) min = int(min, 10) num = int(min / 16) - self._num = 16*maj + num%16 + 256*int(num/16) + self._num = 16 * maj + num % 16 + 256 * int(num / 16) @staticmethod def _disk_num(a, b): - n = ord(a)-ord('a') + n = ord(a) - ord('a') if b != '': - n = 26 * (n+1) + ord(b)-ord('a') + n = 26 * (n + 1) + ord(b) - ord('a') return n @staticmethod def _get_major(n): - return SD.sd_major[(n%256)//16] + return SD.sd_major[(n % 256) // 16] + @staticmethod def _get_minor(n): - return 16 * (n % 16 + 16 * n//256) + return 16 * (n % 16 + 16 * n // 256) @staticmethod def _get_name(n): @@ -81,7 +87,7 @@ def _get_name(n): while n >= 0: s = chr(n % 26 + ord('a')) + s n = n // 26 - 1 - return "sd" + s + return 'sd' + s @staticmethod def _get_dev_t(n): @@ -90,9 +96,9 @@ def _get_dev_t(n): return (maj << 20) + min def __init__(self, arg): - if type(arg) is type(0): + if type(arg) is int: self._num = arg - elif arg.startswith("sd"): + elif arg.startswith('sd'): self._init_from_name(arg) else: self._init_from_dev(arg) @@ -110,9 +116,7 @@ def __hash__(self): return hash(self._num) def __str__(self): - return "%s/%s" % ( - self.devstr(), - self._get_name(self._num)) + return f'{self.devstr()}/{self._get_name(self._num)}' def major(self): return self._get_major(self._num) @@ -121,29 +125,27 @@ def minor(self): return self._get_minor(self._num) def devstr(self): - return "%d:%d" % (self._get_major(self._num), - self._get_minor(self._num)) + return f'{self._get_major(self._num)}:{self._get_minor(self._num)}' def namestr(self): return self._get_name(self._num) def longstr(self): - return "%d\t%s\t%s\t%08x" % (self._num, - self.devstr(), - self.namestr(), - self._get_dev_t(self._num)) + return f'{self._num}\t{self.devstr()}\t{self.namestr()}\t{self._get_dev_t(self._num):08x}' + class MySD(SD): def subst(self, first_sg): disk = { - "lun": self._num, - "major": self.major(), - "devnode": self.namestr(), - "disk_minor": self.minor(), - "sg_minor": first_sg + self._num, + 'lun': self._num, + 'major': self.major(), + 'devnode': self.namestr(), + 'disk_minor': self.minor(), + 'sg_minor': first_sg + self._num, } return disk + disk_template = r"""\ l('sys/bus/scsi/drivers/sd/7:0:0:{lun}', '../../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') l('sys/bus/scsi/devices/7:0:0:{lun}', '../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') @@ -282,29 +284,33 @@ def subst(self, first_sg): """ if len(sys.argv) != 3: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') + def create_part_sysfs(disk, sd, prt): part = disk - part.update ({ - "part_num": prt, - "part_minor": disk["disk_minor"] + prt, - }) + part.update( + { + 'part_num': prt, + 'part_minor': disk['disk_minor'] + prt, + } + ) try: exec(part_template.format(**part)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s%d exist" % (sd.namestr(), prt)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()}{prt} exist') else: - print("error for %s%d: %s" % (sd.namestr(), prt, si[1])) + print(f'error for {sd.namestr()}{prt}: {si[1]}') raise else: - print("sysfs structures for %s%d created" % (sd.namestr(), prt)) + print(f'sysfs structures for {sd.namestr()}{prt} created') + def create_disk_sysfs(dsk, first_sg, n): sd = MySD(dsk) @@ -314,17 +320,16 @@ def create_disk_sysfs(dsk, first_sg, n): exec(disk_template.format(**disk)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s exist" % sd.namestr()) - elif (si.errno == errno.ENOENT): - print("error for %s: %s - have you run sys-script py first?" % - (sd.namestr(), si.strerror)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()} exist') + elif si.errno == errno.ENOENT: + print(f'error for {sd.namestr()}: {si.strerror} - have you run sys-script py first?') return -1 else: - print("error for %s: %s" % (sd.namestr(), si.strerror)) + print(f'error for {sd.namestr()}: {si.strerror}') raise else: - print("sysfs structures for %s created" % sd.namestr()) + print(f'sysfs structures for {sd.namestr()} created') n += 1 if n >= last: @@ -338,6 +343,7 @@ def create_disk_sysfs(dsk, first_sg, n): return n + os.chdir(sys.argv[1]) n = 0 last = int(sys.argv[2]) From 124bbf37de2b2191110c13b8a0857d031cc79fb5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 26 Feb 2026 00:40:08 +0900 Subject: [PATCH 1844/2155] sys-script: apply "ruff format" and "ruff check --fix" --- test/sys-script.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/sys-script.py b/test/sys-script.py index e9ce665313525..be45a211e214c 100755 --- a/test/sys-script.py +++ b/test/sys-script.py @@ -6,30 +6,36 @@ # © 2017 Canonical Ltd. # Author: Dan Streetman -import os, sys +import os import shutil +import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) + if len(sys.argv) < 2: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') os.chdir(sys.argv[1]) if os.path.exists('sys'): shutil.rmtree('sys') +# fmt: off d('sys', 0o755) d('sys/kernel', 0o775) f('sys/kernel/kexec_crash_loaded', 0o664, b'0\n') @@ -16855,3 +16861,4 @@ def f(path, mode, contents): f('sys/devices/platform/i8042/serio1/input/input1/capabilities/sw', 0o644, b'0\n') f('sys/devices/platform/i8042/serio1/input/input1/capabilities/ev', 0o644, b'7\n') f('sys/devices/platform/i8042/serio1/input/input1/capabilities/led', 0o644, b'0\n') +# fmt: on From cbc2990335bfe73d383eccf6f6889e31cb9034b8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 21 Feb 2026 04:40:36 +0900 Subject: [PATCH 1845/2155] test-network: drop old kernel support and workaround for fixed kernel bug - l3mdev in fib rules is supported since https://github.com/torvalds/linux/commit/96c63fa7393d0a346acfe5a91e0c7d4c7782641b (v4.8). - uid range in fib rules is supported since https://github.com/torvalds/linux/commit/622ec2c9d52405973c9f1ca5116eb1c393adfc7d (v4.10). - port range and ip proto in fib rules is supported since https://github.com/torvalds/linux/commit/bfff4862653bb96001ab57c1edd6d03f48e5f035 (v4.17). - interface alternative name is supported since https://github.com/torvalds/linux/commit/36fbf1e52bd3ff8a5cb604955eedfc9350c2e6cc (v5.5). - SRIOV on netdevsim is supported since https://github.com/torvalds/linux/commit/79579220566cd33fe3b15ce8249c57e10251b258 (4.16). - nexthop is supported since https://github.com/torvalds/linux/commit/65ee00a9409f751188a8cdc0988167858eb4a536 (v5.3). - bridge MDB entries on bridge master is supported since https://github.com/torvalds/linux/commit/1bc844ee0faa1b92e3ede00bdd948021c78d7088 (v5.4). --- test/test-network/systemd-networkd-tests.py | 156 +++----------------- 1 file changed, 19 insertions(+), 137 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index a6d4ff033c26b..329cbe1e1daca 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -178,114 +178,6 @@ def f(func): return f -def expectedFailureIfERSPANv0IsNotSupported(): - # erspan version 0 is supported since f989d546a2d5a9f001f6f8be49d98c10ab9b1897 (v5.8) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 0') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfERSPANv2IsNotSupported(): - # erspan version 2 is supported since f551c91de262ba36b20c3ac19538afb4f4507441 (v4.16) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 2') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyPortRangeIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - call_quiet('ip rule del from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): - def f(func): - # IP protocol name is parsed by getprotobyname(), and it requires /etc/protocols. - # Hence. here we use explicit number: 6 == tcp. - rc = call_quiet('ip rule add not from 192.168.100.19 ipproto 6 table 7') - call_quiet('ip rule del not from 192.168.100.19 ipproto 6 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable(): - def f(func): - supported = False - if call_quiet('ip rule add from 192.168.100.19 table 7 uidrange 200-300') == 0: - ret = run('ip rule list from 192.168.100.19 table 7') - supported = ret.returncode == 0 and 'uidrange 200-300' in ret.stdout - call_quiet('ip rule del from 192.168.100.19 table 7 uidrange 200-300') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev') - call_quiet('ip rule del not from 192.168.100.19 l3mdev') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNexthopIsNotAvailable(): - def f(func): - rc = call_quiet('ip nexthop list') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfAlternativeNameIsNotAvailable(): - def f(func): - call_quiet('ip link add dummy98 type dummy') - supported = \ - call_quiet('ip link prop add dev dummy98 altname hogehogehogehogehoge') == 0 and \ - call_quiet('ip link show dev hogehogehogehogehoge') == 0 - remove_link('dummy98') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNetdevsimWithSRIOVIsNotAvailable(): - def f(func): - def finalize(func, supported): - call_quiet('rmmod netdevsim') - return func if supported else unittest.expectedFailure(func) - - call_quiet('rmmod netdevsim') - if call_quiet('modprobe netdevsim') != 0: - return finalize(func, False) - - try: - with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f: - f.write('99 1') - except OSError: - return finalize(func, False) - - return finalize(func, os.path.exists('/sys/bus/netdevsim/devices/netdevsim99/sriov_numvfs')) - - return f - -def expectedFailureIfKernelReturnsInvalidFlags(): - ''' - This checks the kernel bug caused by 3ddc2231c8108302a8229d3c5849ee792a63230d (v6.9), - fixed by 1af7f88af269c4e06a4dc3bc920ff6cdf7471124 (v6.10, backported to 6.9.3). - ''' - def f(func): - call_quiet('ip link add dummy98 type dummy') - call_quiet('ip link set up dev dummy98') - call_quiet('ip address add 192.0.2.1/24 dev dummy98 noprefixroute') - output = check_output('ip address show dev dummy98') - remove_link('dummy98') - return func if 'noprefixroute' in output else unittest.expectedFailure(func) - - return f - # pylint: disable=C0415 def compare_kernel_version(min_kernel_version): try: @@ -1454,7 +1346,6 @@ def setUp(self): def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_altname(self): copy_network_unit('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -1463,7 +1354,6 @@ def test_altname(self): output = networkctl_status('dummy98') self.assertRegex(output, 'hogehogehogehogehogehoge') - @expectedFailureIfAlternativeNameIsNotAvailable() def test_rename_to_altname(self): copy_network_unit('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy-rename-to-altname.link') @@ -1666,7 +1556,6 @@ def setUp(self): def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_match(self): copy_network_unit('12-dummy-mac.netdev', '12-dummy-match-mac-01.network', @@ -2689,9 +2578,13 @@ def test_ip6gre_tunnel(self): '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') start_networkd() - # Old kernels seem not to support IPv6LL address on ip6gre tunnel, So please do not use wait_online() here. - - self.wait_links('dummy98', 'ip6gretun99', 'ip6gretun98', 'ip6gretun97', 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ip6gretun99') print(output) @@ -2712,12 +2605,13 @@ def test_ip6gre_tunnel(self): '25-ip6gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-any-any.netdev') networkctl_reload() - self.wait_links( - 'dummy98', - 'ip6gretun99', - 'ip6gretun98', - 'ip6gretun97', - 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) def test_gretap_tunnel(self): copy_network_unit('12-dummy.netdev', '25-gretap.network', @@ -3036,7 +2930,6 @@ def test_6rd_tunnel(self): networkctl_reload() self.wait_online('sittun99:routable', 'dummy98:degraded') - @expectedFailureIfERSPANv0IsNotSupported() def test_erspan_tunnel_v0(self): copy_network_unit('12-dummy.netdev', '25-erspan.network', '25-erspan0-tunnel.netdev', '25-tunnel.network', @@ -3119,7 +3012,6 @@ def test_erspan_tunnel_v1(self): 'erspan98:routable', 'dummy98:degraded') - @expectedFailureIfERSPANv2IsNotSupported() def test_erspan_tunnel_v2(self): copy_network_unit('12-dummy.netdev', '25-erspan.network', '25-erspan2-tunnel.netdev', '25-tunnel.network', @@ -3696,7 +3588,6 @@ def verify_address_static( check_json(networkctl_json()) - @expectedFailureIfKernelReturnsInvalidFlags() def test_address_static(self): copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False) self.setup_nftset('addr4', 'ipv4_addr') @@ -4289,7 +4180,6 @@ def test_routing_policy_rule_manual(self): else: self.assertFalse(True) - @expectedFailureIfRoutingPolicyPortRangeIsNotAvailable() def test_routing_policy_rule_port_range(self): copy_network_unit('25-fibrule-port-range.network', '11-dummy.netdev') start_networkd() @@ -4304,7 +4194,6 @@ def test_routing_policy_rule_port_range(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyIPProtoIsNotAvailable() def test_routing_policy_rule_invert(self): copy_network_unit('25-fibrule-invert.network', '11-dummy.netdev') start_networkd() @@ -4318,7 +4207,6 @@ def test_routing_policy_rule_invert(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable() def test_routing_policy_rule_l3mdev(self): copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev') start_networkd() @@ -4329,7 +4217,6 @@ def test_routing_policy_rule_l3mdev(self): self.assertIn('1500: from all lookup [l3mdev-table]', output) self.assertIn('2000: from all lookup [l3mdev-table] unreachable', output) - @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() def test_routing_policy_rule_uidrange(self): copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev') start_networkd() @@ -5610,7 +5497,6 @@ def _test_nexthop(self, manage_foreign_nexthops): print(output) self.assertEqual(output, '') - @expectedFailureIfNexthopIsNotAvailable() def test_nexthop(self): first = True for manage_foreign_nexthops in [True, False]: @@ -6354,13 +6240,10 @@ def test_bridge_mdb(self): print(output) self.assertRegex(output, 'dev bridge99 port test1 grp ff02:aaaa:fee5::1:3 permanent *vid 4064') self.assertRegex(output, 'dev bridge99 port test1 grp 224.0.1.1 permanent *vid 4065') + self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') + self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - # Old kernel may not support bridge MDB entries on bridge master - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 224.0.1.3 temp vid 4068') == 0: - self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') - self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - - # Old kernel may not support L2 bridge MDB entries + # The kernels older than 955062b03fa62b802a1ee34fbb04e39f7a70ae73 (v5.11) do not support L2 bridge MDB entries if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: self.assertRegex(output, 'dev bridge99 port bridge99 grp 01:80:c2:00:00:0e permanent *vid 4069') @@ -6625,7 +6508,7 @@ def setup_netdevsim(self, id=99, num_ports=1, num_vfs=0): with open(f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8') as f: f.write(f'{num_vfs}') - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov(self): copy_network_unit('25-netdevsim.link', '25-sriov.network') @@ -6642,7 +6525,7 @@ def test_sriov(self): 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' ) - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov_udev(self): copy_network_unit('25-sriov.link', '25-sriov-udev.network') @@ -7785,7 +7668,6 @@ def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): self.assertIn('DHCPREPLY(veth-peer)', output) self.assertIn('sent size: 0 option: 14 rapid-commit', output) - @expectedFailureIfKernelReturnsInvalidFlags() def test_dhcp_client_ipv4_only(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network', '25-sit-dhcp4.netdev', '25-sit-dhcp4.network') From 9c0c203db4f989f9c66c59d34afa9842713db73d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Feb 2026 05:15:44 +0900 Subject: [PATCH 1846/2155] test-network: assume the test is running with newer udevd --- test/test-network/systemd-networkd-tests.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 329cbe1e1daca..7123f052ac148 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1529,10 +1529,6 @@ def test_unit_file(self): self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).') - # This test may be run on the system that has older udevd than 70f32a260b5ebb68c19ecadf5d69b3844896ba55 (v249). - # In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property. - # Let's reprocess the interface and drop the property. - udevadm_trigger('/sys/class/net/lo') output = networkctl_status('lo') print(output) self.assertIn('Link File: n/a', output) From 59199ec3bdacef79e366761a5c5a31c187a608a7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Feb 2026 05:25:49 +0900 Subject: [PATCH 1847/2155] test-network: drop old ip command support --- test/test-network/systemd-networkd-tests.py | 28 +++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 7123f052ac148..d68305f1fa644 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2174,13 +2174,12 @@ def check_tuntap(self, attached): output = check_output('ip -d link show testtun99') print(output) - # Old ip command does not support IFF_ flags - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) else: @@ -2190,7 +2189,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtun99') print(output) - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -2200,7 +2199,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -4313,24 +4312,21 @@ def _check_route_static(self, test1_is_managed: bool): print('### ip route show 192.168.10.2') output = check_output('ip route show 192.168.10.2') print(output) - # old ip command does not show IPv6 gateways... self.assertIn('192.168.10.2 proto static', output) - self.assertIn('nexthop', output) - self.assertIn('dev test1 weight 20', output) - self.assertIn('dev test1 weight 30', output) - self.assertIn('dev dummy98 weight 10', output) - self.assertIn('dev dummy98 weight 5', output) + self.assertIn('nexthop via inet6 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via inet6 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via inet6 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via inet6 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') output = check_output('ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') print(output) - # old ip command does not show 'nexthop' keyword and weight... self.assertIn('2001:1234:5:bfff:ff:ff:ff:ff', output) if test1_is_managed: - self.assertIn('via 2001:1234:5:6fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:7fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98', output) - self.assertIn('via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98', output) + self.assertIn('nexthop via 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip route show 192.168.20.0/24') output = check_output('ip route show 192.168.20.0/24') From 1b94d7d7ecbfac9698c199e198722258299e6940 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:02:04 +0900 Subject: [PATCH 1848/2155] test-network: apply "ruff format" and "ruff check --fix" --- test/test-network/systemd-networkd-tests.py | 3484 +++++++++++++------ 1 file changed, 2464 insertions(+), 1020 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index d68305f1fa644..3d3931adec7b3 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -3,7 +3,8 @@ # systemd-networkd tests # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM, -# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. +# simply run this file which can be found in the VM at +# /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. # # To run an individual test, specify it as a command line argument in the form # of .. E.g. the NetworkdMTUTests class has a test @@ -27,8 +28,8 @@ import datetime import enum import errno -import itertools import ipaddress +import itertools import json import os import pathlib @@ -85,8 +86,8 @@ asan_options = os.getenv('ASAN_OPTIONS') lsan_options = os.getenv('LSAN_OPTIONS') ubsan_options = os.getenv('UBSAN_OPTIONS') -with_coverage = os.getenv('COVERAGE_BUILD_DIR') != None -show_journal = True # When true, show journal on stopping networkd. +with_coverage = os.getenv('COVERAGE_BUILD_DIR') is not None +show_journal = True # When true, show journal on stopping networkd. active_units = [] protected_links = { @@ -108,54 +109,98 @@ saved_ipv6_rules = None saved_timezone = None + def rm_f(path): if os.path.exists(path): os.remove(path) + def rm_rf(path): shutil.rmtree(path, ignore_errors=True) + def cp(src, dst): shutil.copy(src, dst) + def cp_r(src, dst): shutil.copytree(src, dst, copy_function=shutil.copy, dirs_exist_ok=True) + def mkdir_p(path): os.makedirs(path, exist_ok=True) + def touch(path): pathlib.Path(path).touch() + # pylint: disable=R1710 def check_output(*command, **kwargs): # This checks the result and returns stdout (and stderr) on success. command = command[0].split() + list(command[1:]) - ret = subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) + ret = subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + **kwargs, + ) if ret.returncode == 0: return ret.stdout.rstrip() # When returncode != 0, print stdout and stderr, then trigger CalledProcessError. print(ret.stdout) ret.check_returncode() + def call(*command, **kwargs): # This returns returncode. stdout and stderr are merged and shown in console command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).returncode + def call_check(*command, **kwargs): # Same as call() above, but it triggers CalledProcessError if rc != 0 command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).check_returncode() + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).check_returncode() + def call_quiet(*command, **kwargs): command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + **kwargs, + ).returncode + def run(*command, **kwargs): # This returns CompletedProcess instance. command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + return subprocess.run( + command, + check=False, + text=True, + capture_output=True, + **kwargs, + ) + def check_json(string): try: @@ -164,6 +209,7 @@ def check_json(string): print(f"String is not a valid JSON: '{string}'") raise + def is_module_available(*module_names): for module_name in module_names: lsmod_output = check_output('lsmod') @@ -172,16 +218,19 @@ def is_module_available(*module_names): return False return True + def expectedFailureIfModuleIsNotAvailable(*module_names): def f(func): return func if is_module_available(*module_names) else unittest.expectedFailure(func) return f + # pylint: disable=C0415 def compare_kernel_version(min_kernel_version): try: import platform + from packaging import version except ImportError: print('Failed to import either platform or packaging module, assuming the comparison failed') @@ -195,6 +244,7 @@ def compare_kernel_version(min_kernel_version): return version.parse(kver) >= version.parse(min_kernel_version) + def copy_network_unit(*units, copy_dropins=True): """ Copy networkd unit files into the testbed. @@ -210,7 +260,10 @@ def copy_network_unit(*units, copy_dropins=True): mkdir_p(network_unit_dir) for unit in units: if copy_dropins and os.path.exists(os.path.join(networkd_ci_temp_dir, unit + '.d')): - cp_r(os.path.join(networkd_ci_temp_dir, unit + '.d'), os.path.join(network_unit_dir, unit + '.d')) + cp_r( + os.path.join(networkd_ci_temp_dir, unit + '.d'), + os.path.join(network_unit_dir, unit + '.d'), + ) if unit.endswith('.conf'): dropin = unit @@ -227,10 +280,11 @@ def copy_network_unit(*units, copy_dropins=True): if has_link: udevadm_reload() + def copy_credential(src, target): - mkdir_p(credstore_dir) - cp(os.path.join(networkd_ci_temp_dir, src), - os.path.join(credstore_dir, target)) + mkdir_p(credstore_dir) + cp(os.path.join(networkd_ci_temp_dir, src), os.path.join(credstore_dir, target)) + def remove_network_unit(*units): """ @@ -249,10 +303,12 @@ def remove_network_unit(*units): if has_link: udevadm_reload() + def touch_network_unit(*units): for unit in units: touch(os.path.join(network_unit_dir, unit)) + def clear_network_units(): has_link = False if os.path.exists(network_unit_dir): @@ -266,20 +322,24 @@ def clear_network_units(): if has_link: udevadm_reload() + def copy_networkd_conf_dropin(*dropins): """Copy networkd.conf dropin files into the testbed.""" mkdir_p(networkd_conf_dropin_dir) for dropin in dropins: cp(os.path.join(networkd_ci_temp_dir, dropin), networkd_conf_dropin_dir) + def remove_networkd_conf_dropin(*dropins): """Remove previously copied networkd.conf dropin files from the testbed.""" for dropin in dropins: rm_f(os.path.join(networkd_conf_dropin_dir, dropin)) + def clear_networkd_conf_dropins(): rm_rf(networkd_conf_dropin_dir) + def setup_systemd_udev_rules(): if not build_dir and not source_dir: return @@ -290,49 +350,55 @@ def setup_systemd_udev_rules(): if not path: continue - path = os.path.join(path, "rules.d") - print(f"Copying udev rules from {path} to {udev_rules_dir}") + path = os.path.join(path, 'rules.d') + print(f'Copying udev rules from {path} to {udev_rules_dir}') for rule in os.listdir(path): - if not rule.endswith(".rules"): + if not rule.endswith('.rules'): continue cp(os.path.join(path, rule), udev_rules_dir) + def clear_networkd_state_files(): rm_rf('/var/lib/systemd/network/') + def copy_udev_rule(*rules): """Copy udev rules""" mkdir_p(udev_rules_dir) for rule in rules: cp(os.path.join(networkd_ci_temp_dir, rule), udev_rules_dir) + def remove_udev_rule(*rules): """Remove previously copied udev rules""" for rule in rules: rm_f(os.path.join(udev_rules_dir, rule)) + def clear_udev_rules(): rm_rf(udev_rules_dir) + def save_active_units(): for u in [ - 'systemd-networkd.socket', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-networkd.service', - 'systemd-resolved-monitor.socket', - 'systemd-resolved-varlink.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'firewalld.service', - 'nftables.service', + 'systemd-networkd.socket', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-networkd.service', + 'systemd-resolved-monitor.socket', + 'systemd-resolved-varlink.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'firewalld.service', + 'nftables.service', ]: if call(f'systemctl is-active --quiet {u}') == 0: call(f'systemctl stop {u}') active_units.append(u) + def restore_active_units(): has_network_socket = False has_resolve_socket = False @@ -370,11 +436,13 @@ def restore_active_units(): for u in active_units: call(f'systemctl restart {u}') + def create_unit_dropin(unit, contents): mkdir_p(f'/run/systemd/system/{unit}.d') with open(f'/run/systemd/system/{unit}.d/00-override.conf', mode='w', encoding='utf-8') as f: f.write('\n'.join(contents)) + def create_service_dropin(service, command, additional_settings=None): drop_in = ['[Service]'] if command: @@ -412,37 +480,43 @@ def create_service_dropin(service, command, additional_settings=None): create_unit_dropin(f'{service}.service', drop_in) + def setup_system_units(): if build_dir or source_dir: mkdir_p('/run/systemd/system/') for unit in [ - 'systemd-networkd.service', - 'systemd-networkd.socket', - 'systemd-networkd-persistent-storage.service', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'systemd-udevd.service', + 'systemd-networkd.service', + 'systemd-networkd.socket', + 'systemd-networkd-persistent-storage.service', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'systemd-udevd.service', ]: for path in [build_dir, source_dir]: if not path: continue - fullpath = os.path.join(os.path.join(path, "units"), unit) + fullpath = os.path.join(os.path.join(path, 'units'), unit) if os.path.exists(fullpath): - print(f"Copying unit file from {fullpath} to /run/systemd/system/") + print(f'Copying unit file from {fullpath} to /run/systemd/system/') cp(fullpath, '/run/systemd/system/') break - create_service_dropin('systemd-networkd', networkd_bin, - ['[Service]', - 'Restart=no', - 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', - '[Unit]', - 'StartLimitIntervalSec=0']) + create_service_dropin( + 'systemd-networkd', + networkd_bin, + [ + '[Service]', + 'Restart=no', + 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', + '[Unit]', + 'StartLimitIntervalSec=0', + ], + ) create_service_dropin('systemd-resolved', resolved_bin) create_service_dropin('systemd-timesyncd', timesyncd_bin) @@ -452,7 +526,7 @@ def setup_system_units(): [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-persistent-storage.service', @@ -465,28 +539,28 @@ def setup_system_units(): 'ExecStop=', f'ExecStop={networkctl_bin} persistent-storage no', 'Environment=SYSTEMD_LOG_LEVEL=debug' if enable_debug else '', - ] + ], ) create_unit_dropin( 'systemd-networkd-resolve-hook.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink-metrics.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-udevd.service', @@ -494,7 +568,7 @@ def setup_system_units(): '[Service]', 'ExecStart=', f'ExecStart=@{udevadm_bin} systemd-udevd', - ] + ], ) check_output('systemctl daemon-reload') @@ -507,6 +581,7 @@ def setup_system_units(): check_output('systemctl restart systemd-timesyncd.service') check_output('systemctl restart systemd-udevd.service') + def clear_system_units(): def rm_unit(name): rm_f(f'/run/systemd/system/{name}') @@ -524,15 +599,18 @@ def rm_unit(name): check_output('systemctl daemon-reload') check_output('systemctl restart systemd-udevd.service') + def link_exists(*links): for link in links: if call_quiet(f'ip link show {link}') != 0: return False return True + def link_resolve(link): return check_output(f'ip link show {link}').split(':')[1].strip().split('@')[0] + def remove_link(*links, protect=False): for link in links: if protect and link in protected_links: @@ -540,6 +618,7 @@ def remove_link(*links, protect=False): if link_exists(link): call(f'ip link del dev {link}') + def save_existing_links(): links = os.listdir('/sys/class/net') for link in links: @@ -549,6 +628,7 @@ def save_existing_links(): print('### The following links will be protected:') print(', '.join(sorted(list(protected_links)))) + def unmanage_existing_links(): mkdir_p(network_unit_dir) @@ -558,16 +638,19 @@ def unmanage_existing_links(): f.write(f'Name={link}\n') f.write('\n[Link]\nUnmanaged=yes\n') + def flush_links(): links = os.listdir('/sys/class/net') remove_link(*links, protect=True) + def flush_nexthops(): # Currently, the 'ip nexthop' command does not have 'save' and 'restore'. # Hence, we cannot restore nexthops in a simple way. # Let's assume there is no nexthop used in the system call_quiet('ip nexthop flush') + def save_routes(): # pylint: disable=global-statement global saved_routes @@ -575,6 +658,7 @@ def save_routes(): print('### The following routes will be protected:') print(saved_routes) + def flush_routes(): have = False output = check_output('ip route show table all') @@ -583,7 +667,7 @@ def flush_routes(): continue if 'proto kernel' in line: continue - if ' dev ' in line and not ' dev lo ' in line: + if ' dev ' in line and ' dev lo ' not in line: continue if not have: have = True @@ -591,9 +675,11 @@ def flush_routes(): print(f'# {line}') call(f'ip route del {line}') + def save_routing_policy_rules(): # pylint: disable=global-statement global saved_ipv4_rules, saved_ipv6_rules + def save(ipv): output = check_output(f'ip -{ipv} rule show') print(f'### The following IPv{ipv} routing policy rules will be protected:') @@ -603,6 +689,7 @@ def save(ipv): saved_ipv4_rules = save(4) saved_ipv6_rules = save(6) + def flush_routing_policy_rules(): def flush(ipv, saved_rules): have = False @@ -612,7 +699,7 @@ def flush(ipv, saved_rules): continue if not have: have = True - print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') + print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') # fmt: skip print(f'# {line}') words = line.replace('lookup [l3mdev-table]', 'l3mdev').replace('[detached]', '').split() priority = words[0].rstrip(':') @@ -621,19 +708,21 @@ def flush(ipv, saved_rules): flush(4, saved_ipv4_rules) flush(6, saved_ipv6_rules) + def flush_fou_ports(): ret = run('ip fou show') if ret.returncode != 0: - return # fou may not be supported + return # fou may not be supported for line in ret.stdout.splitlines(): port = line.split()[1] call(f'ip fou del port {port}') + def flush_l2tp_tunnels(): tids = [] ret = run('ip l2tp show tunnel') if ret.returncode != 0: - return # l2tp may not be supported + return # l2tp may not be supported for line in ret.stdout.splitlines(): words = line.split() if words[0] == 'Tunnel': @@ -647,10 +736,11 @@ def flush_l2tp_tunnels(): r = run(f'ip l2tp show tunnel tunnel_id {tid}') if r.returncode != 0 or len(r.stdout.rstrip()) == 0: break - time.sleep(.2) + time.sleep(0.2) else: print(f'Cannot remove L2TP tunnel {tid}, ignoring.') + def save_timezone(): # pylint: disable=global-statement global saved_timezone @@ -659,66 +749,79 @@ def save_timezone(): saved_timezone = r.stdout.rstrip() print(f'### Saved timezone: {saved_timezone}') + def restore_timezone(): if saved_timezone: call(*timedatectl_cmd, 'set-timezone', f'{saved_timezone}', env=env) + def read_link_attr(*args): with open(os.path.join('/sys/class/net', *args), encoding='utf-8') as f: return f.readline().strip() + def read_manager_state_file(): with open('/run/systemd/netif/state', encoding='utf-8') as f: return f.read() + def read_link_state_file(link): ifindex = read_link_attr(link, 'ifindex') path = os.path.join('/run/systemd/netif/links', ifindex) with open(path, encoding='utf-8') as f: return f.read() + def read_ip_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ip_neigh_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ipv6_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv6') + def read_ipv6_neigh_sysctl_attr(link, attribute): return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6') + def read_ipv4_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv4') + def read_mpls_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'mpls') + def stop_by_pid_file(pid_file): if not os.path.exists(pid_file): return - with open(pid_file, 'r', encoding='utf-8') as f: + with open(pid_file, encoding='utf-8') as f: pid = f.read().rstrip(' \t\r\n\0') os.kill(int(pid), signal.SIGTERM) for _ in range(25): try: os.kill(int(pid), 0) - print(f"PID {pid} is still alive, waiting...") - time.sleep(.2) + print(f'PID {pid} is still alive, waiting...') + time.sleep(0.2) except OSError as e: if e.errno == errno.ESRCH: break - print(f"Unexpected exception when waiting for {pid} to die: {e.errno}") + print(f'Unexpected exception when waiting for {pid} to die: {e.errno}') rm_f(pid_file) -def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): + b = b'' + pack = lambda c, w=1: struct.pack('>' + '_BH_I'[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + '_BH_I'[w], n) ipv4 = ipaddress.IPv4Address + class SvcParam(enum.Enum): ALPN = 1 DOHPATH = 7 @@ -728,7 +831,7 @@ class SvcParam(enum.Enum): adn = adn.rstrip('.') + '.' data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.'))) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return pack(data, 2) data += pack(b.join(ipv4(addr).packed for addr in addrs)) @@ -738,11 +841,13 @@ class SvcParam(enum.Enum): return pack(data, 2) -def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): + b = b'' + pack = lambda c, w=1: struct.pack('>' + '_BH_I'[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + '_BH_I'[w], n) ipv6 = ipaddress.IPv6Address + class SvcParam(enum.Enum): ALPN = 1 DOHPATH = 7 @@ -752,7 +857,7 @@ class SvcParam(enum.Enum): adn = adn.rstrip('.') + '.' data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')), 2) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return data data += pack(b.join(ipv6(addr).packed for addr in addrs), 2) @@ -762,7 +867,16 @@ class SvcParam(enum.Enum): return data -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'): + +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: @@ -773,49 +887,67 @@ def start_dnsmasq(*additional_options, namespace=None, interface='veth-peer', ra else: ns_command = () - command = ns_command + ( - 'dnsmasq', - f'--log-facility={dnsmasq_log_file}', - '--log-queries=extra', - '--log-dhcp', - f'--pid-file={dnsmasq_pid_file}', - '--conf-file=/dev/null', - '--bind-interfaces', - f'--interface={interface}', - f'--dhcp-leasefile={dnsmasq_lease_file}', - '--enable-ra', - f'--dhcp-range={ipv6_range}{ra_mode},2m', - f'--dhcp-range={ipv4_range},2m', - '--dhcp-option=option:mtu,1492', - f'--dhcp-option=option:router,{ipv4_router}', - '--port=0', - '--no-resolv', - ) + additional_options + command = ( + ns_command + + ( + 'dnsmasq', + f'--log-facility={dnsmasq_log_file}', + '--log-queries=extra', + '--log-dhcp', + f'--pid-file={dnsmasq_pid_file}', + '--conf-file=/dev/null', + '--bind-interfaces', + f'--interface={interface}', + f'--dhcp-leasefile={dnsmasq_lease_file}', + '--enable-ra', + f'--dhcp-range={ipv6_range}{ra_mode},2m', + f'--dhcp-range={ipv4_range},2m', + '--dhcp-option=option:mtu,1492', + f'--dhcp-option=option:router,{ipv4_router}', + '--port=0', + '--no-resolv', + ) + + additional_options + ) check_output(*command) + def stop_dnsmasq(): stop_by_pid_file(dnsmasq_pid_file) rm_f(dnsmasq_lease_file) rm_f(dnsmasq_log_file) + def read_dnsmasq_log_file(): with open(dnsmasq_log_file, encoding='utf-8') as f: return f.read() + def start_isc_dhcpd(conf_file, ipv, interface='veth-peer'): conf_file_path = os.path.join(networkd_ci_temp_dir, conf_file) - isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' + isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' # fmt: skip touch(isc_dhcpd_lease_file) check_output(isc_dhcpd_command) + def stop_isc_dhcpd(): stop_by_pid_file(isc_dhcpd_pid_file) rm_f(isc_dhcpd_lease_file) + def get_dbus_link_path(link): - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - '/org/freedesktop/network1', 'org.freedesktop.network1.Manager', - 'GetLinkByName', 's', link]) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + '/org/freedesktop/network1', + 'org.freedesktop.network1.Manager', + 'GetLinkByName', + 's', + link, + ] + ) assert out.startswith(b'io ') out = out.strip() @@ -823,42 +955,72 @@ 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]) + 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) - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.network1', - link_path, f'org.freedesktop.network1.DHCPv{family}Client', 'State']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.network1', + link_path, + f'org.freedesktop.network1.DHCPv{family}Client', + 'State', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') return out[3:-1].decode() + def get_dhcp4_client_state(link): return get_dhcp_client_state(link, '4') + def get_dhcp6_client_state(link): return get_dhcp_client_state(link, '6') + def get_link_description(link): link_path = get_dbus_link_path(link) - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - link_path, 'org.freedesktop.network1.Link', 'Describe']) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + link_path, + 'org.freedesktop.network1.Link', + 'Describe', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') json_raw = out[2:].decode() check_json(json_raw) - description = json.loads(json_raw) # Convert from escaped sequences to json + description = json.loads(json_raw) # Convert from escaped sequences to json check_json(description) - return json.loads(description) # Now parse the json + return json.loads(description) # Now parse the json + def start_radvd(*additional_options, config_file): config_file_path = os.path.join(networkd_ci_temp_dir, 'radvd', config_file) @@ -870,9 +1032,11 @@ def start_radvd(*additional_options, config_file): ) + additional_options check_output(*command) + 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') @@ -880,21 +1044,25 @@ def start_modem_manager_mock(*additional_options): 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') + 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') @@ -905,12 +1073,15 @@ def radvd_check_config(config_file): config_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf/radvd', config_file) return call(f'radvd --config={config_file_path} --configtest') == 0 + def networkd_invocation_id(): return check_output('systemctl show --value -p InvocationID systemd-networkd.service') + def networkd_pid(): 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: invocation_id = networkd_invocation_id() @@ -925,9 +1096,11 @@ def read_networkd_log(invocation_id=None, since=None): check_output('journalctl --sync') return check_output(*command) + def networkd_is_failed(): return call_quiet('systemctl is-failed -q systemd-networkd.service') != 1 + def stop_networkd(show_logs=True, check_failed=True): global show_journal show_logs = show_logs and show_journal @@ -954,12 +1127,14 @@ def stop_networkd(show_logs=True, check_failed=True): if check_failed: assert not networkd_is_failed() + def start_networkd(): check_output('systemctl start systemd-networkd') invocation_id = networkd_invocation_id() pid = networkd_pid() print(f'Started systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') + def restart_networkd(show_logs=True): global show_journal show_logs = show_logs and show_journal @@ -973,39 +1148,50 @@ def restart_networkd(show_logs=True): pid = networkd_pid() print(f'Restarted systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') + def networkctl(*args): # Do not call networkctl if networkd is in failed state. # Otherwise, networkd may be restarted and we may get wrong results. assert not networkd_is_failed() return check_output(*(networkctl_cmd + list(args)), env=env) + def networkctl_status(*args): return networkctl('-n', '0', 'status', *args) + def networkctl_json(*args): return networkctl('--json=short', 'status', *args) + def networkctl_reconfigure(*links): networkctl('reconfigure', *links) + def networkctl_reload(): networkctl('reload') + def resolvectl(*args): return check_output(*(resolvectl_cmd + list(args)), env=env) + def timedatectl(*args): return check_output(*(timedatectl_cmd + list(args)), env=env) + def udevadm(*args): return check_output(*(udevadm_cmd + list(args))) + def udevadm_reload(): udevadm('control', '--reload') + def udevadm_trigger(*args, action='add'): udevadm('trigger', '--settle', f'--action={action}', *args) + def setup_common(): # Protect existing links unmanage_existing_links() @@ -1016,6 +1202,7 @@ def setup_common(): sys.stdout.flush() check_output('journalctl --sync') + def tear_down_common(): # 1. stop DHCP/RA servers stop_dnsmasq() @@ -1057,6 +1244,7 @@ def tear_down_common(): # 9. check the status of networkd assert not networkd_is_failed() + def setUpModule(): rm_rf(networkd_ci_temp_dir) cp_r(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'), networkd_ci_temp_dir) @@ -1078,6 +1266,7 @@ def setUpModule(): setup_system_units() + def tearDownModule(): rm_rf(networkd_ci_temp_dir) clear_udev_rules() @@ -1094,7 +1283,8 @@ def tearDownModule(): sys.stdout.flush() check_output('journalctl --sync') -class Utilities(): + +class Utilities: # pylint: disable=no-member def check_link_exists(self, *link, expected=True): @@ -1146,7 +1336,9 @@ def wait_activated(self, link, state='down', trial=40): else: self.fail(f'Timed out waiting for {link} activated.') - def wait_operstate(self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True): + def wait_operstate( + self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True + ): """Wait for the link to reach the specified operstate and/or setup state. Specify None or '' for either operstate or setup_state to ignore that state. @@ -1184,7 +1376,17 @@ def wait_operstate(self, link, operstate='degraded', setup_state='configured', s self.fail(f'Timed out waiting for {link} to reach state {operstate}/{setup_state}') return False - def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4=False, ipv6=False, setup_state='configured', setup_timeout=5, bool_dns=False): + def wait_online( + self, + *links_with_operstate, + timeout='20s', + bool_any=False, + ipv4=False, + ipv6=False, + setup_state='configured', + setup_timeout=5, + bool_dns=False, + ): """Wait for the links to reach the specified operstate and/or setup state. This is similar to wait_operstate() but can be used for multiple links, @@ -1200,8 +1402,8 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 Set 'bool_dns' to True to wait for DNS servers to be accessible. - Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the given links. - This is applied only for the operational state 'degraded' or above. + Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the + given links. This is applied only for the operational state 'degraded' or above. Note that this function waits for the links to reach *or exceed* the given operstate. However, the setup_state, if specified, must be matched *exactly*. @@ -1209,7 +1411,12 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 This returns if the links reached the requested operstate/setup_state; otherwise it raises CalledProcessError or fails test assertion. """ - args = wait_online_cmd + [f'--timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate] + [f'--ignore={link}' for link in protected_links] + args = ( + wait_online_cmd + + [f'--timeout={timeout}'] + + [f'--interface={link}' for link in links_with_operstate] + + [f'--ignore={link}' for link in protected_links] + ) if bool_any: args += ['--any'] if bool_dns: @@ -1278,7 +1485,7 @@ def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0' print('## Checking NetLabel skipped: selinuxenabled command not found.') elif call_quiet('selinuxenabled') != 0: print('## Checking NetLabel skipped: SELinux disabled.') - elif not shutil.which('netlabelctl'): # not packaged by all distros + elif not shutil.which('netlabelctl'): # not packaged by all distros print('## Checking NetLabel skipped: netlabelctl command not found.') else: output = check_output('netlabelctl unlbl list') @@ -1289,7 +1496,7 @@ def setup_nftset(self, filter_name, filter_type, flags=''): if not shutil.which('nft'): print('## Setting up NFT sets skipped: nft command not found.') else: - if call(f'nft add table inet sd_test') != 0: + if call('nft add table inet sd_test') != 0: print('## Setting up NFT table failed.') self.fail() if call(f'nft add set inet sd_test {filter_name} {{ type {filter_type}; {flags} }}') != 0: @@ -1304,7 +1511,7 @@ def teardown_nftset(self, *filters): if call(f'nft delete set inet sd_test {filter_name}') != 0: print('## Tearing down NFT sets failed.') self.fail() - if call(f'nft delete table inet sd_test') != 0: + if call('nft delete table inet sd_test') != 0: print('## Tearing down NFT table failed.') self.fail() @@ -1338,8 +1545,8 @@ def networkctl_check_unit(self, ifname, netdev_file=None, network_file=None, lin if link_file: self.assertRegex(output, rf'Link File: .*/{link_file}\.link') -class NetworkctlTests(unittest.TestCase, Utilities): +class NetworkctlTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -1355,8 +1562,11 @@ def test_altname(self): self.assertRegex(output, 'hogehogehogehogehogehoge') def test_rename_to_altname(self): - copy_network_unit('26-netdev-link-local-addressing-yes.network', - '12-dummy.netdev', '12-dummy-rename-to-altname.link') + copy_network_unit( + '26-netdev-link-local-addressing-yes.network', + '12-dummy.netdev', + '12-dummy-rename-to-altname.link', + ) start_networkd() self.wait_online('dummyalt:degraded') @@ -1527,7 +1737,10 @@ def test_unit_file(self): self.assertIn('Network File: /run/systemd/network/11-test-unit-file.network', output) self.assertIn('/run/systemd/network/11-test-unit-file.network.d/dropin.conf', output) - self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).') + self.check_networkd_log( + 'test1: Configuring with /run/systemd/network/11-test-unit-file.network ' + '(dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).' + ) output = networkctl_status('lo') print(output) @@ -1544,8 +1757,8 @@ def test_delete_links(self): def test_label(self): networkctl('label') -class NetworkdMatchTests(unittest.TestCase, Utilities): +class NetworkdMatchTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -1553,12 +1766,14 @@ def tearDown(self): tear_down_common() def test_match(self): - copy_network_unit('12-dummy-mac.netdev', - '12-dummy-match-mac-01.network', - '12-dummy-match-mac-02.network', - '12-dummy-match-renamed.network', - '12-dummy-match-altname.network', - '12-dummy-altname.link') + copy_network_unit( + '12-dummy-mac.netdev', + '12-dummy-match-mac-01.network', + '12-dummy-match-mac-02.network', + '12-dummy-match-renamed.network', + '12-dummy-match-altname.network', + '12-dummy-altname.link', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1593,7 +1808,11 @@ def test_match(self): self.assertIn('Network File: /run/systemd/network/12-dummy-match-altname.network', output) def test_match_udev_property(self): - copy_network_unit('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network') + copy_network_unit( + '12-dummy.netdev', + '13-not-match-udev-property.network', + '14-match-udev-property.network', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1601,8 +1820,8 @@ def test_match_udev_property(self): print(output) self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property') -class WaitOnlineTests(unittest.TestCase, Utilities): +class WaitOnlineTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -1630,7 +1849,7 @@ def do_test_wait_online_dns( if network_dropin is not None: network_dropin_path = os.path.join( network_unit_dir, - '25-dhcp-client-use-dns-ipv4.network.d/test.conf' + '25-dhcp-client-use-dns-ipv4.network.d/test.conf', ) mkdir_p(os.path.dirname(network_dropin_path)) with open(network_dropin_path, 'w') as f: @@ -1639,7 +1858,7 @@ def do_test_wait_online_dns( copy_network_unit( '25-veth.netdev', '25-dhcp-client-use-dns-ipv4.network', - '25-dhcp-server.network' + '25-dhcp-server.network', ) start_networkd() self.wait_online('veth-peer:routable') @@ -1648,11 +1867,11 @@ def do_test_wait_online_dns( resolved_dropin = '/run/systemd/resolved.conf.d/global-dns.conf' mkdir_p(os.path.dirname(resolved_dropin)) with open(resolved_dropin, 'w') as f: - f.write(( - '[Resolve]\n' - f'DNS={global_dns}\n' - f'FallbackDNS={fallback_dns}\n' - )) + f.write(f''' +[Resolve] +DNS={global_dns} +FallbackDNS={fallback_dns} +''') self.addCleanup(os.remove, resolved_dropin) check_output('systemctl reload systemd-resolved') @@ -1666,61 +1885,61 @@ def do_test_wait_online_dns( if expect_timeout: # The above should have thrown an exception. - self.fail( - 'Expected systemd-networkd-wait-online to time out' - ) + self.fail('Expected systemd-networkd-wait-online to time out') except subprocess.CalledProcessError as e: if expect_timeout: self.assertRegex( e.output, - f'veth99: No link-specific DNS server is accessible', - f'Missing expected log message:\n{e.output}' + 'veth99: No link-specific DNS server is accessible', + f'Missing expected log message:\n{e.output}', ) else: - self.fail( - f'Command timed out:\n{e.output}' - ) + self.fail(f'Command timed out:\n{e.output}') finally: wait_online_env = wait_online_env_copy def test_wait_online_dns(self): - ''' test systemd-networkd-wait-online with --dns ''' + """test systemd-networkd-wait-online with --dns""" self.do_test_wait_online_dns() def test_wait_online_dns_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, expect pass due to global DNS - ''' + """ # Set UseDNS=no, and allow global DNS to be used. self.do_test_wait_online_dns( global_dns='192.168.5.1', network_dropin=( - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout(self): - ''' test systemd-networkd-wait-online with --dns, and expect timeout ''' + """test systemd-networkd-wait-online with --dns, and expect timeout""" # Explicitly set DNSDefaultRoute=yes, and require link-specific DNS to be used. self.do_test_wait_online_dns( expect_timeout=True, network_dropin=( - '[Network]\n' - 'DNSDefaultRoute=yes\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +DNSDefaultRoute=yes +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, and expect timeout despite global DNS - ''' + """ # Configure Domains=~., and expect timeout despite global DNS servers # being available. @@ -1728,16 +1947,17 @@ def test_wait_online_dns_expect_timeout_global(self): expect_timeout=True, global_dns='192.168.5.1', network_dropin=( - '[Network]\n' - 'Domains=~.\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +Domains=~. +[DHCPv4] +UseDNS=no +''' + ), ) class NetworkdNetDevTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -1800,12 +2020,12 @@ def test_bridge(self): self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'max_age')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'forward_delay')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'ageing_time')) / tick)) - self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) - self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) + self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) + self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) output = networkctl_status('bridge99') print(output) @@ -1832,20 +2052,20 @@ def test_bond(self): self.networkctl_check_unit('bond98', '25-bond-balanced-tlb') self.networkctl_check_unit('bond97', '25-bond-property') - self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') - self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') - self.check_link_attr('bond99', 'bonding', 'miimon', '1000') - self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') - self.check_link_attr('bond99', 'bonding', 'updelay', '2000') - self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') - self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') - self.check_link_attr('bond99', 'bonding', 'min_links', '1') + self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') + self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') + self.check_link_attr('bond99', 'bonding', 'miimon', '1000') + self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') + self.check_link_attr('bond99', 'bonding', 'updelay', '2000') + self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') + self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') + self.check_link_attr('bond99', 'bonding', 'min_links', '1') self.check_link_attr('bond99', 'bonding', 'ad_actor_sys_prio', '1218') - self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') - self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') + self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') + self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') - self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') - self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') + self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') + self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') output = networkctl_status('bond99') print(output) @@ -1861,7 +2081,7 @@ def test_bond(self): output = networkctl_status('bond97') print(output) - self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') + self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') self.check_link_attr('bond97', 'bonding', 'peer_notif_delay', '300000') def check_vlan(self, id, flags): @@ -1900,26 +2120,36 @@ def check_vlan(self, id, flags): self.assertRegex(output, 'inet 192.168.23.5/24 brd 192.168.23.255 scope global vlan99') def test_vlan(self): - copy_network_unit('21-vlan.netdev', '11-dummy.netdev', - '21-vlan.network', '21-vlan-test1.network') + copy_network_unit( + '21-vlan.netdev', + '11-dummy.netdev', + '21-vlan.network', + '21-vlan-test1.network', + ) start_networkd() self.check_vlan(id=99, flags=True) # Test for reloading .netdev file. See issue #34907. - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: f.write('[VLAN]\nId=42\n') # VLAN ID cannot be changed, so we need to remove the existing netdev. - check_output("ip link del vlan99") + check_output('ip link del vlan99') networkctl_reload() self.check_vlan(id=42, flags=True) - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: - f.write('[VLAN]\n' - 'GVRP=no\n' - 'MVRP=no\n' - 'LooseBinding=no\n' - 'ReorderHeader=no\n') + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[VLAN] +GVRP=no +MVRP=no +LooseBinding=no +ReorderHeader=no +''') # flags can be changed, hence it is not necessary to remove the existing netdev. networkctl_reload() @@ -1929,8 +2159,12 @@ def test_vlan_on_bond(self): # For issue #24377 (https://github.com/systemd/systemd/issues/24377), # which is fixed by b05e52000b4eee764b383cc3031da0a3739e996e (PR#24020). - copy_network_unit('21-bond-802.3ad.netdev', '21-bond-802.3ad.network', - '21-vlan-on-bond.netdev', '21-vlan-on-bond.network') + copy_network_unit( + '21-bond-802.3ad.netdev', + '21-bond-802.3ad.network', + '21-vlan-on-bond.netdev', + '21-vlan-on-bond.network', + ) start_networkd() self.wait_online('bond99:off') self.wait_operstate('vlan99', operstate='off', setup_state='configuring', setup_timeout=10) @@ -1953,14 +2187,22 @@ def test_macvtap(self): print(f'### test_macvtap(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvtap.network') - with open(os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvtap.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVTAP]\nMode=' + mode) start_networkd() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvtap99', '21-macvtap', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvtap') @@ -1970,8 +2212,10 @@ def test_macvtap(self): touch_network_unit('21-macvtap.netdev') networkctl_reload() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('macvlan') def test_macvlan(self): @@ -1984,14 +2228,22 @@ def test_macvlan(self): print(f'### test_macvlan(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvlan.network') - with open(os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvlan.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVLAN]\nMode=' + mode) start_networkd() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvlan99', '21-macvlan', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvlan') @@ -2007,9 +2259,11 @@ def test_macvlan(self): remove_link('test1') time.sleep(1) - check_output("ip link add test1 type dummy") - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + check_output('ip link add test1 type dummy') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) output = check_output('ip -d link show test1') print(output) @@ -2020,13 +2274,15 @@ def test_macvlan(self): self.assertIn(' mtu 2000 ', output) self.assertIn(f' macvlan mode {mode} ', output) self.assertIn(' bcqueuelen 1234 ', output) - if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 + if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 self.assertIn(' bclim 2147483647 ', output) touch_network_unit('21-macvlan.netdev') networkctl_reload() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('ipvlan') def test_ipvlan(self): @@ -2039,9 +2295,15 @@ def test_ipvlan(self): print(f'### test_ipvlan(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvlan.network') - with open(os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvlan.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVLAN]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2068,9 +2330,14 @@ def test_hsr(self): print(f'### test_hsr(proto={proto}, supervision={supervision})') with self.subTest(proto=proto, supervision=supervision): - copy_network_unit('25-hsr.netdev', '25-hsr.network', - '11-dummy.netdev', '11-dummy.network', - '12-dummy.netdev', '12-dummy-no-address.network') + copy_network_unit( + '25-hsr.netdev', + '25-hsr.network', + '11-dummy.netdev', + '11-dummy.network', + '12-dummy.netdev', + '12-dummy-no-address.network', + ) with open(os.path.join(network_unit_dir, '25-hsr.netdev'), mode='a', encoding='utf-8') as f: f.write('Protocol=' + proto + '\nSupervision=' + str(supervision)) @@ -2101,9 +2368,15 @@ def test_ipvtap(self): print(f'### test_ipvtap(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvtap.network') - with open(os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvtap.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVTAP]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2120,11 +2393,19 @@ def test_ipvtap(self): self.wait_online('ipvtap99:degraded', 'test1:degraded') def test_veth(self): - copy_network_unit('25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + copy_network_unit( + '25-veth.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth-mtu.netdev', + ) start_networkd() - self.wait_online('veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', 'veth-mtu-peer:degraded') + self.wait_online( + 'veth99:degraded', + 'veth-peer:degraded', + 'veth-mtu:degraded', + 'veth-mtu-peer:degraded', + ) self.networkctl_check_unit('veth99', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-peer', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-mtu', '25-veth-mtu', '26-netdev-link-local-addressing-yes') @@ -2149,13 +2430,15 @@ def test_veth(self): touch_network_unit( '25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + '25-veth-mtu.netdev', + ) networkctl_reload() self.wait_online( 'veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', - 'veth-mtu-peer:degraded') + 'veth-mtu-peer:degraded', + ) def check_tuntap(self, attached): pid = networkd_pid() @@ -2163,12 +2446,14 @@ def check_tuntap(self, attached): output = check_output('ip -d -oneline tuntap show') print(output) + # fmt: off self.assertRegex(output, r'testtap99: tap pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') self.assertRegex(output, r'testtun99: tun pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') + # fmt: on if attached: - self.assertRegex(output, fr'testtap99: .*{name}\({pid}\)') - self.assertRegex(output, fr'testtun99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtap99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtun99: .*{name}\({pid}\)') self.assertRegex(output, r'testtap99: .*systemd\(1\)') self.assertRegex(output, r'testtun99: .*systemd\(1\)') @@ -2252,8 +2537,12 @@ def test_vrf(self): @expectedFailureIfModuleIsNotAvailable('vcan') def test_vcan(self): - copy_network_unit('25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', - '25-vcan98.netdev', '25-vcan98.network') + copy_network_unit( + '25-vcan.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-vcan98.netdev', + '25-vcan98.network', + ) start_networkd() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2275,7 +2564,8 @@ def test_vcan(self): '25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', '25-vcan98.netdev', - '25-vcan98.network') + '25-vcan98.network', + ) networkctl_reload() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2296,14 +2586,30 @@ def test_vxcan(self): @expectedFailureIfModuleIsNotAvailable('wireguard') def test_wireguard(self): - copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint') - copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk') - copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer') + copy_credential( + '25-wireguard-endpoint-peer0-cred.txt', + 'network.wireguard.peer0.endpoint', + ) + copy_credential( + '25-wireguard-preshared-key-peer2-cred.txt', + 'network.wireguard.peer2.psk', + ) + copy_credential( + '25-wireguard-no-peer-private-key-cred.txt', + 'network.wireguard.private.25-wireguard-no-peer', + ) - copy_network_unit('25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + copy_network_unit( + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) start_networkd() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') self.networkctl_check_unit('wg99', '25-wireguard', '25-wireguard') @@ -2397,10 +2703,12 @@ def test_wireguard(self): output = check_output('wg show wg99 private-key') self.assertEqual(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=') output = check_output('wg show wg99 allowed-ips') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t192.168.124.3/32', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\t192.168.124.2/32', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tfdbc:bae2:7871:e1fe:793:8636::/96 fdbc:bae2:7871:500:e1fe:793:8636:dad1/128', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.26.0/24 fd31:bf08:57cb::/48', output) + # fmt: on output = check_output('wg show wg99 persistent-keepalive') self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\toff', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\toff', output) @@ -2412,10 +2720,12 @@ def test_wireguard(self): self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\t(none)', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.27.3:51820', output) output = check_output('wg show wg99 preshared-keys') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\tit7nd33chCT/tKT2ZZWfYyp43Zs+6oif72hexnSNMqA=', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tcPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\tIIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=', output) + # fmt: on output = check_output('wg show wg98 private-key') self.assertEqual(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=') @@ -2426,10 +2736,16 @@ def test_wireguard(self): self.assertEqual(output, '0x4d3') touch_network_unit( - '25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) networkctl_reload() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') @@ -2452,24 +2768,39 @@ def test_geneve(self): self.wait_online('geneve99:degraded') def _test_ipip_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ipip.network', - '25-ipip-tunnel.netdev', '25-tunnel.network', - '25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ipip.network', + '25-ipip-tunnel.netdev', + '25-tunnel.network', + '25-ipip-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ipip-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ipip-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-ipip-tunnel.netdev', - '25-ipip-tunnel-local-any.netdev', - '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev']: + for netdev in [ + '25-ipip-tunnel.netdev', + '25-ipip-tunnel-local-any.netdev', + '25-ipip-tunnel-remote-any.netdev', + '25-ipip-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ipip' # kernel default + mode = 'ipip' # kernel default start_networkd() - self.wait_online('ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', 'dummy98:degraded') + self.wait_online( + 'ipiptun99:routable', + 'ipiptun98:routable', + 'ipiptun97:routable', + 'ipiptun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ipiptun99') print(output) @@ -2488,14 +2819,16 @@ def _test_ipip_tunnel(self, mode): '25-ipip-tunnel.netdev', '25-ipip-tunnel-local-any.netdev', '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev') + '25-ipip-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ipip_tunnel(self): first = True @@ -2510,13 +2843,26 @@ def test_ipip_tunnel(self): self._test_ipip_tunnel(mode) def test_gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretun.network', - '25-gre-tunnel.netdev', '25-tunnel.network', - '25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretun.network', + '25-gre-tunnel.netdev', + '25-tunnel.network', + '25-gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', 'dummy98:degraded') + self.wait_online( + 'gretun99:routable', + 'gretun98:routable', + 'gretun97:routable', + 'gretun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('gretun99', '25-gre-tunnel', '25-tunnel') self.networkctl_check_unit('gretun98', '25-gre-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('gretun97', '25-gre-tunnel-remote-any', '25-tunnel-remote-any') @@ -2556,21 +2902,30 @@ def test_gre_tunnel(self): '25-gre-tunnel.netdev', '25-gre-tunnel-local-any.netdev', '25-gre-tunnel-remote-any.netdev', - '25-gre-tunnel-any-any.netdev') + '25-gre-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretun.network', - '25-ip6gre-tunnel.netdev', '25-tunnel.network', - '25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretun.network', + '25-ip6gre-tunnel.netdev', + '25-tunnel.network', + '25-ip6gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ip6gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() self.wait_online( @@ -2583,7 +2938,7 @@ def test_ip6gre_tunnel(self): output = check_output('ip -d link show ip6gretun99') print(output) - self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretun98') print(output) self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local any dev dummy98') @@ -2598,7 +2953,8 @@ def test_ip6gre_tunnel(self): '25-ip6gre-tunnel.netdev', '25-ip6gre-tunnel-local-any.netdev', '25-ip6gre-tunnel-remote-any.netdev', - '25-ip6gre-tunnel-any-any.netdev') + '25-ip6gre-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'ip6gretun99:routable', @@ -2609,9 +2965,14 @@ def test_ip6gre_tunnel(self): ) def test_gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretap.network', - '25-gretap-tunnel.netdev', '25-tunnel.network', - '25-gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretap.network', + '25-gretap-tunnel.netdev', + '25-tunnel.network', + '25-gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('gretap99:routable', 'gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('gretap99', '25-gretap-tunnel', '25-tunnel') @@ -2637,17 +2998,24 @@ def test_gretap_tunnel(self): touch_network_unit( '25-gretap-tunnel.netdev', - '25-gretap-tunnel-local-any.netdev') + '25-gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'gretap99:routable', 'gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretap.network', - '25-ip6gretap-tunnel.netdev', '25-tunnel.network', - '25-ip6gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretap.network', + '25-ip6gretap-tunnel.netdev', + '25-tunnel.network', + '25-ip6gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('ip6gretap99:routable', 'ip6gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('ip6gretap99', '25-ip6gretap-tunnel', '25-tunnel') @@ -2656,28 +3024,43 @@ def test_ip6gretap_tunnel(self): output = check_output('ip -d link show ip6gretap99') print(output) - self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretap98') print(output) self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local any dev dummy98') touch_network_unit( '25-ip6gretap-tunnel.netdev', - '25-ip6gretap-tunnel-local-any.netdev') + '25-ip6gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'ip6gretap99:routable', 'ip6gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti.network', - '25-vti-tunnel.netdev', '25-tunnel.network', - '25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti.network', + '25-vti-tunnel.netdev', + '25-tunnel.network', + '25-vti-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-vti-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', 'dummy98:degraded') + self.wait_online( + 'vtitun99:routable', + 'vtitun98:routable', + 'vtitun97:routable', + 'vtitun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vtitun99', '25-vti-tunnel', '25-tunnel') self.networkctl_check_unit('vtitun98', '25-vti-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vtitun97', '25-vti-tunnel-remote-any', '25-tunnel-remote-any') @@ -2701,22 +3084,35 @@ def test_vti_tunnel(self): '25-vti-tunnel.netdev', '25-vti-tunnel-local-any.netdev', '25-vti-tunnel-remote-any.netdev', - '25-vti-tunnel-any-any.netdev') + '25-vti-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti6_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti6.network', - '25-vti6-tunnel.netdev', '25-tunnel.network', - '25-vti6-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti6-tunnel-remote-any.netdev', '25-tunnel-remote-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti6.network', + '25-vti6-tunnel.netdev', + '25-tunnel.network', + '25-vti6-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti6-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + ) start_networkd() - self.wait_online('vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', 'dummy98:degraded') + self.wait_online( + 'vti6tun99:routable', + 'vti6tun98:routable', + 'vti6tun97:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vti6tun99', '25-vti6-tunnel', '25-tunnel') self.networkctl_check_unit('vti6tun98', '25-vti6-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vti6tun97', '25-vti6-tunnel-remote-any', '25-tunnel-remote-any') @@ -2724,7 +3120,7 @@ def test_vti6_tunnel(self): output = check_output('ip -d link show vti6tun99') print(output) - self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show vti6tun98') print(output) self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local (any|::) dev dummy98') @@ -2735,50 +3131,103 @@ def test_vti6_tunnel(self): touch_network_unit( '25-vti6-tunnel.netdev', '25-vti6-tunnel-local-any.netdev', - '25-vti6-tunnel-remote-any.netdev') + '25-vti6-tunnel-remote-any.netdev', + ) networkctl_reload() self.wait_online( 'vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def _test_ip6tnl_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ip6tnl.network', - '25-ip6tnl-tunnel.netdev', '25-tunnel.network', - '25-ip6tnl-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6tnl-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-veth.netdev', '25-ip6tnl-slaac.network', '25-ipv6-prefix.network', - '25-ip6tnl-tunnel-local-slaac.netdev', '25-ip6tnl-tunnel-local-slaac.network', - '25-ip6tnl-tunnel-external.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6tnl.network', + '25-ip6tnl-tunnel.netdev', + '25-tunnel.network', + '25-ip6tnl-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-veth.netdev', + '25-ip6tnl-slaac.network', + '25-ipv6-prefix.network', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-local-slaac.network', + '25-ip6tnl-tunnel-external.netdev', + '26-netdev-link-local-addressing-yes.network', + ) if mode: - for netdev in ['25-ip6tnl-tunnel.netdev', - '25-ip6tnl-tunnel-local-any.netdev', - '25-ip6tnl-tunnel-remote-any.netdev', - '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev']: + for netdev in [ + '25-ip6tnl-tunnel.netdev', + '25-ip6tnl-tunnel-local-any.netdev', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-external.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'any' # kernel default - - start_networkd() - self.wait_online('ip6tnl99:routable', 'ip6tnl98:routable', 'ip6tnl97:routable', - 'ip6tnl-slaac:degraded', 'ip6tnl-external:degraded', - 'dummy98:degraded', 'veth99:routable', 'veth-peer:degraded') - self.networkctl_check_unit('ip6tnl99', '25-ip6tnl-tunnel', '25-tunnel') - self.networkctl_check_unit('ip6tnl98', '25-ip6tnl-tunnel-local-any', '25-tunnel-local-any') - self.networkctl_check_unit('ip6tnl97', '25-ip6tnl-tunnel-remote-any', '25-tunnel-remote-any') - self.networkctl_check_unit('ip6tnl-slaac', '25-ip6tnl-tunnel-local-slaac', '25-ip6tnl-tunnel-local-slaac') - self.networkctl_check_unit('ip6tnl-external', '25-ip6tnl-tunnel-external', '26-netdev-link-local-addressing-yes') - self.networkctl_check_unit('dummy98', '12-dummy', '25-ip6tnl') - self.networkctl_check_unit('veth99', '25-veth', '25-ip6tnl-slaac') - self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') + mode = 'any' # kernel default + + start_networkd() + self.wait_online( + 'ip6tnl99:routable', + 'ip6tnl98:routable', + 'ip6tnl97:routable', + 'ip6tnl-slaac:degraded', + 'ip6tnl-external:degraded', + 'dummy98:degraded', + 'veth99:routable', + 'veth-peer:degraded', + ) + self.networkctl_check_unit( + 'ip6tnl99', + '25-ip6tnl-tunnel', + '25-tunnel', + ) + self.networkctl_check_unit( + 'ip6tnl98', + '25-ip6tnl-tunnel-local-any', + '25-tunnel-local-any', + ) + self.networkctl_check_unit( + 'ip6tnl97', + '25-ip6tnl-tunnel-remote-any', + '25-tunnel-remote-any', + ) + self.networkctl_check_unit( + 'ip6tnl-slaac', + '25-ip6tnl-tunnel-local-slaac', + '25-ip6tnl-tunnel-local-slaac', + ) + self.networkctl_check_unit( + 'ip6tnl-external', + '25-ip6tnl-tunnel-external', + '26-netdev-link-local-addressing-yes', + ) + self.networkctl_check_unit( + 'dummy98', + '12-dummy', + '25-ip6tnl', + ) + self.networkctl_check_unit( + 'veth99', + '25-veth', + '25-ip6tnl-slaac', + ) + self.networkctl_check_unit( + 'veth-peer', + '25-veth', + '25-ipv6-prefix', + ) output = check_output('ip -d link show ip6tnl99') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) # fmt: skip output = check_output('ip -d link show ip6tnl98') print(output) self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local any dev dummy98', output) @@ -2791,7 +3240,7 @@ def _test_ip6tnl_tunnel(self, mode): self.assertIn('ip6tnl external ', output) output = check_output('ip -d link show ip6tnl-slaac') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) # fmt: skip output = check_output('ip -6 address show veth99') print(output) @@ -2806,7 +3255,8 @@ def _test_ip6tnl_tunnel(self, mode): '25-ip6tnl-tunnel-local-any.netdev', '25-ip6tnl-tunnel-remote-any.netdev', '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev') + '25-ip6tnl-tunnel-external.netdev', + ) networkctl_reload() self.wait_online( 'ip6tnl99:routable', @@ -2816,7 +3266,8 @@ def _test_ip6tnl_tunnel(self, mode): 'ip6tnl-external:degraded', 'dummy98:degraded', 'veth99:routable', - 'veth-peer:degraded') + 'veth-peer:degraded', + ) def test_ip6tnl_tunnel(self): first = True @@ -2831,24 +3282,39 @@ def test_ip6tnl_tunnel(self): self._test_ip6tnl_tunnel(mode) def _test_sit_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-sit.network', - '25-sit-tunnel.netdev', '25-tunnel.network', - '25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-sit.network', + '25-sit-tunnel.netdev', + '25-tunnel.network', + '25-sit-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-sit-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-sit-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-sit-tunnel.netdev', - '25-sit-tunnel-local-any.netdev', - '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev']: + for netdev in [ + '25-sit-tunnel.netdev', + '25-sit-tunnel-local-any.netdev', + '25-sit-tunnel-remote-any.netdev', + '25-sit-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ip6ip' # kernel default + mode = 'ip6ip' # kernel default start_networkd() - self.wait_online('sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', 'dummy98:degraded') + self.wait_online( + 'sittun99:routable', + 'sittun98:routable', + 'sittun97:routable', + 'sittun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('sittun99', '25-sit-tunnel', '25-tunnel') self.networkctl_check_unit('sittun98', '25-sit-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('sittun97', '25-sit-tunnel-remote-any', '25-tunnel-remote-any') @@ -2872,14 +3338,16 @@ def _test_sit_tunnel(self, mode): '25-sit-tunnel.netdev', '25-sit-tunnel-local-any.netdev', '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev') + '25-sit-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_sit_tunnel(self): first = True @@ -2894,8 +3362,12 @@ def test_sit_tunnel(self): self._test_sit_tunnel(mode) def test_isatap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-isatap.network', - '25-isatap-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-isatap.network', + '25-isatap-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('isataptun99:routable', 'dummy98:degraded') self.networkctl_check_unit('isataptun99', '25-isatap-tunnel', '25-tunnel') @@ -2903,15 +3375,19 @@ def test_isatap_tunnel(self): output = check_output('ip -d link show isataptun99') print(output) - self.assertRegex(output, "isatap ") + self.assertRegex(output, 'isatap ') touch_network_unit('25-isatap-tunnel.netdev') networkctl_reload() self.wait_online('isataptun99:routable', 'dummy98:degraded') def test_6rd_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-6rd.network', - '25-6rd-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-6rd.network', + '25-6rd-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('sittun99:routable', 'dummy98:degraded') self.networkctl_check_unit('sittun99', '25-6rd-tunnel', '25-tunnel') @@ -2926,9 +3402,14 @@ def test_6rd_tunnel(self): self.wait_online('sittun99:routable', 'dummy98:degraded') def test_erspan_tunnel_v0(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan0-tunnel.netdev', '25-tunnel.network', - '25-erspan0-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan0-tunnel.netdev', + '25-tunnel.network', + '25-erspan0-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan0-tunnel', '25-tunnel') @@ -2958,17 +3439,24 @@ def test_erspan_tunnel_v0(self): touch_network_unit( '25-erspan0-tunnel.netdev', - '25-erspan0-tunnel-local-any.netdev') + '25-erspan0-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_erspan_tunnel_v1(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan1-tunnel.netdev', '25-tunnel.network', - '25-erspan1-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan1-tunnel.netdev', + '25-tunnel.network', + '25-erspan1-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan1-tunnel', '25-tunnel') @@ -3000,17 +3488,24 @@ def test_erspan_tunnel_v1(self): touch_network_unit( '25-erspan1-tunnel.netdev', - '25-erspan1-tunnel-local-any.netdev') + '25-erspan1-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_erspan_tunnel_v2(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan2-tunnel.netdev', '25-tunnel.network', - '25-erspan2-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan2-tunnel.netdev', + '25-tunnel.network', + '25-erspan2-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan2-tunnel', '25-tunnel') @@ -3042,32 +3537,52 @@ def test_erspan_tunnel_v2(self): touch_network_unit( '25-erspan2-tunnel.netdev', - '25-erspan2-tunnel-local-any.netdev') + '25-erspan2-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_tunnel_independent(self): - copy_network_unit('25-ipip-tunnel-independent.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent', + '26-netdev-link-local-addressing-yes', + ) def test_tunnel_independent_loopback(self): - copy_network_unit('25-ipip-tunnel-independent-loopback.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent-loopback.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent-loopback', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent-loopback', + '26-netdev-link-local-addressing-yes', + ) @expectedFailureIfModuleIsNotAvailable('xfrm_interface') def test_xfrm(self): - copy_network_unit('12-dummy.netdev', '25-xfrm.network', - '25-xfrm.netdev', '25-xfrm-independent.netdev', - '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-xfrm.network', + '25-xfrm.netdev', + '25-xfrm-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('dummy98:degraded', 'xfrm98:degraded', 'xfrm99:degraded') @@ -3091,12 +3606,23 @@ def test_xfrm(self): @expectedFailureIfModuleIsNotAvailable('fou') def test_fou(self): - copy_network_unit('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + copy_network_unit( + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) start_networkd() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) self.networkctl_check_unit('ipiptun96', '25-fou-ipip') self.networkctl_check_unit('sittun96', '25-fou-sit') self.networkctl_check_unit('gretun96', '25-fou-gre') @@ -3121,25 +3647,52 @@ def test_fou(self): self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55556') touch_network_unit( - '25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) networkctl_reload() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) def test_vxlan(self): - copy_network_unit('11-dummy.netdev', '25-vxlan-test1.network', - '25-vxlan.netdev', '25-vxlan.network', - '25-vxlan-ipv6.netdev', '25-vxlan-ipv6.network', - '25-vxlan-independent.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth.netdev', '25-vxlan-veth99.network', '25-ipv6-prefix.network', - '25-vxlan-local-slaac.netdev', '25-vxlan-local-slaac.network', - '25-vxlan-external.netdev', '25-vxlan-external.network') - start_networkd() - - self.wait_online('test1:degraded', 'veth99:routable', 'veth-peer:degraded', - 'vxlan99:degraded', 'vxlan98:degraded', 'vxlan97:degraded', 'vxlan-slaac:degraded', - 'vxlan-external:degraded') + copy_network_unit( + '11-dummy.netdev', + '25-vxlan-test1.network', + '25-vxlan.netdev', + '25-vxlan.network', + '25-vxlan-ipv6.netdev', + '25-vxlan-ipv6.network', + '25-vxlan-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth.netdev', + '25-vxlan-veth99.network', + '25-ipv6-prefix.network', + '25-vxlan-local-slaac.netdev', + '25-vxlan-local-slaac.network', + '25-vxlan-external.netdev', + '25-vxlan-external.network', + ) + start_networkd() + + self.wait_online( + 'test1:degraded', + 'veth99:routable', + 'veth-peer:degraded', + 'vxlan99:degraded', + 'vxlan98:degraded', + 'vxlan97:degraded', + 'vxlan-slaac:degraded', + 'vxlan-external:degraded', + ) self.networkctl_check_unit('test1', '11-dummy', '25-vxlan-test1') self.networkctl_check_unit('veth99', '25-veth', '25-vxlan-veth99') self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') @@ -3198,10 +3751,18 @@ def test_vxlan(self): self.assertIn('external', output) self.assertIn('vnifilter', output) - @unittest.skipUnless(compare_kernel_version("6"), reason="Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315") + @unittest.skipUnless( + compare_kernel_version('6'), + reason='Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315', + ) def test_macsec(self): - copy_network_unit('25-macsec.netdev', '25-macsec.network', '25-macsec.key', - '26-macsec.network', '12-dummy.netdev') + copy_network_unit( + '25-macsec.netdev', + '25-macsec.network', + '25-macsec.key', + '26-macsec.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:degraded', 'macsec99:routable') @@ -3256,7 +3817,7 @@ def test_ifb(self): networkctl_reload() self.wait_online('ifb99:degraded') - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-1.link') start_networkd() @@ -3268,7 +3829,7 @@ def test_rps_cpu_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 2) - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_0_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-0-1.link') start_networkd() @@ -3280,7 +3841,7 @@ def test_rps_cpu_0_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 3) - @unittest.skipUnless(os.cpu_count() >= 4, reason="CPU count should be >= 4 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 4, reason='CPU count should be >= 4 to pass this test') def test_rps_cpu_multi(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-multi.link') start_networkd() @@ -3316,7 +3877,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # disable @@ -3334,7 +3895,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # empty -> unchanged @@ -3343,7 +3904,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '24-rps-cpu-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('24-rps-cpu-empty.link') # 0, then empty -> unchanged @@ -3352,7 +3913,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-0-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-0-empty.link') # 0, then invalid -> 0 @@ -3373,8 +3934,8 @@ def test_rps_cpu(self): self.assertEqual(int(output.replace(',', ''), base=16), 1) remove_network_unit('24-rps-cpu-invalid.link') -class NetworkdL2TPTests(unittest.TestCase, Utilities): +class NetworkdL2TPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3383,8 +3944,12 @@ def tearDown(self): @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_netlink') def test_l2tp_udp(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @@ -3394,34 +3959,41 @@ def test_l2tp_udp(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap UDP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 11") - self.assertRegex(output, "UDP source / dest ports: 3000/4000") - self.assertRegex(output, "UDP checksum: enabled") + self.assertRegex(output, 'Tunnel 10, encap UDP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 11') + self.assertRegex(output, 'UDP source / dest ports: 3000/4000') + self.assertRegex(output, 'UDP checksum: enabled') output = check_output('ip l2tp show session tid 10 session_id 15') print(output) - self.assertRegex(output, "Session 15 in tunnel 10") - self.assertRegex(output, "Peer session 16, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses1") + self.assertRegex(output, 'Session 15 in tunnel 10') + self.assertRegex(output, 'Peer session 16, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses1') output = check_output('ip l2tp show session tid 10 session_id 17') print(output) - self.assertRegex(output, "Session 17 in tunnel 10") - self.assertRegex(output, "Peer session 18, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses2") + self.assertRegex(output, 'Session 17 in tunnel 10') + self.assertRegex(output, 'Peer session 18, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses2') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_ip', 'l2tp_netlink') def test_l2tp_ip(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') @@ -3431,30 +4003,33 @@ def test_l2tp_ip(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap IP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 12") + self.assertRegex(output, 'Tunnel 10, encap IP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 12') output = check_output('ip l2tp show session tid 10 session_id 25') print(output) - self.assertRegex(output, "Session 25 in tunnel 10") - self.assertRegex(output, "Peer session 26, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses3") + self.assertRegex(output, 'Session 25 in tunnel 10') + self.assertRegex(output, 'Peer session 26, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses3') output = check_output('ip l2tp show session tid 10 session_id 27') print(output) - self.assertRegex(output, "Session 27 in tunnel 10") - self.assertRegex(output, "Peer session 28, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses4") + self.assertRegex(output, 'Session 27 in tunnel 10') + self.assertRegex(output, 'Peer session 28, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses4') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') -class NetworkdNetworkTests(unittest.TestCase, Utilities): +class NetworkdNetworkTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3473,34 +4048,34 @@ def test_ID_NET_MANAGED_BY(self): self.wait_online('test1:off', setup_state='unmanaged') def verify_address_static( - self, - label1: str, - label2: str, - label3: str, - broadcast1: str, - broadcast2: str, - broadcast3: str, - peer1: str, - peer2: str, - peer3: str, - peer4: str, - peer5: str, - peer6: str, - scope1: str, - scope2: str, - deprecated1: str, - deprecated2: str, - deprecated3: str, - deprecated4: str, - route_metric: int, - flag1: str, - flag2: str, - flag3: str, - flag4: str, - ip4_null_16: str, - ip4_null_24: str, - ip6_null_73: str, - ip6_null_74: str, + self, + label1: str, + label2: str, + label3: str, + broadcast1: str, + broadcast2: str, + broadcast3: str, + peer1: str, + peer2: str, + peer3: str, + peer4: str, + peer5: str, + peer6: str, + scope1: str, + scope2: str, + deprecated1: str, + deprecated2: str, + deprecated3: str, + deprecated4: str, + route_metric: int, + flag1: str, + flag2: str, + flag3: str, + flag4: str, + ip4_null_16: str, + ip4_null_24: str, + ip6_null_73: str, + ip6_null_74: str, ): output = check_output('ip address show dev dummy98') print(output) @@ -3542,12 +4117,12 @@ def verify_address_static( self.assertIn(f'inet6 2001:db8:0:f104::2/64 scope global{deprecated4}', output) # route metric - self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') + self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') # fmt: skip self.assertRegex(output, rf'inet6 2001:db8:0:f105::1/64 (metric {route_metric} |)scope global') output_route = check_output('ip -4 route show dev dummy98 10.8.1.0/24') print(output_route) - self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) + self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) # fmt: skip output_route = check_output('ip -6 route show dev dummy98 2001:db8:0:f105::/64') print(output_route) @@ -3561,9 +4136,9 @@ def verify_address_static( # null address self.assertTrue(ip4_null_16.endswith('.0.1')) - prefix16 = ip4_null_16[:-len('.0.1')] + prefix16 = ip4_null_16[: -len('.0.1')] self.assertTrue(ip4_null_24.endswith('.1')) - prefix24 = ip4_null_24[:-len('.1')] + prefix24 = ip4_null_24[: -len('.1')] self.assertIn(f'inet {ip4_null_16}/16 brd {prefix16}.255.255 scope global subnet16', output) self.assertIn(f'inet {ip4_null_24}/24 brd {prefix24}.255 scope global subnet24', output) self.assertIn(f'inet6 {ip6_null_73}/73 scope global', output) @@ -3824,9 +4399,14 @@ def test_address_peer_ipv4(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_prefix_route(self): - copy_network_unit('25-prefix-route-with-vrf.network', '12-dummy.netdev', - '25-prefix-route-without-vrf.network', '11-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-prefix-route-with-vrf.network', + '12-dummy.netdev', + '25-prefix-route-without-vrf.network', + '11-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) for trial in range(2): if trial == 0: start_networkd() @@ -3851,7 +4431,7 @@ def test_prefix_route(self): if trial == 0: # Kernel's bug? self.assertRegex(output, 'local fdde:11:22::1 proto kernel metric 0 pref medium') - #self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') + # self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:33::1 proto kernel metric 0 pref medium') self.assertRegex(output, 'fdde:11:33::/64 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:44::1 proto kernel metric 0 pref medium') @@ -3925,7 +4505,7 @@ def test_configure_without_carrier_yes_ignore_carrier_loss_no(self): carrier_map = {'on': '1', 'off': '0'} routable_map = {'on': 'routable', 'off': 'no-carrier'} - for (carrier, have_config) in [('off', True), ('on', True), ('off', False)]: + for carrier, have_config in [('off', True), ('on', True), ('off', False)]: with self.subTest(carrier=carrier, have_config=have_config): if carrier_map[carrier] != read_link_attr('test1', 'carrier'): check_output(f'ip link set dev test1 carrier {carrier}') @@ -3946,7 +4526,7 @@ def check_routing_policy_rule_test1(self): output = check_output('ip rule list iif test1 priority 111') print(output) - self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') + self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') # fmt: skip output = check_output('ip -6 rule list iif test1 priority 100') print(output) @@ -4005,7 +4585,7 @@ def check_routing_policy_rule_dummy98(self): output = check_output('ip rule list priority 112') print(output) - self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') + self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') # fmt: skip def _test_routing_policy_rule(self, manage_foreign_routes): if not manage_foreign_routes: @@ -4052,8 +4632,12 @@ def test_routing_policy_rule(self): self._test_routing_policy_rule(manage_foreign_routes) def test_routing_policy_rule_restart_and_reconfigure(self): - copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev', - '25-routing-policy-rule-dummy98.network', '12-dummy.netdev') + copy_network_unit( + '25-routing-policy-rule-test1.network', + '11-dummy.netdev', + '25-routing-policy-rule-dummy98.network', + '12-dummy.netdev', + ) # For #11280 and #34068. @@ -4140,7 +4724,8 @@ def test_routing_policy_rule_manual(self): # For issue #36244. copy_network_unit( '11-dummy.netdev', - '25-routing-policy-rule-manual.network') + '25-routing-policy-rule-manual.network', + ) start_networkd() self.wait_operstate('test1', operstate='off', setup_state='configuring', setup_timeout=20) @@ -4374,8 +4959,12 @@ def _test_route_static(self, manage_foreign_routes): if not manage_foreign_routes: copy_networkd_conf_dropin('networkd-manage-foreign-routes-no.conf') - copy_network_unit('25-route-static.network', '12-dummy.netdev', - '25-route-static-test1.network', '11-dummy.netdev') + copy_network_unit( + '25-route-static.network', + '12-dummy.netdev', + '25-route-static-test1.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') self._check_route_static(test1_is_managed=True) @@ -4432,7 +5021,8 @@ def test_route_static_issue_35047(self): '25-route-static-issue-35047.network', '25-route-static-issue-35047.network.d/step1.conf', '12-dummy.netdev', - copy_dropins=False) + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:routable') @@ -4444,9 +5034,27 @@ def test_route_static_issue_35047(self): self.assertIn('198.51.100.0/24 via 192.0.2.2 proto static', output) check_output('ip link set dev dummy98 down') - self.wait_route_dropped('dummy98', '192.0.2.2 proto kernel scope link src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', '198.51.100.0/24 via 192.0.2.2 proto static', ipv='-4', table='all', timeout_sec=10) + self.wait_route_dropped( + 'dummy98', + '192.0.2.2 proto kernel scope link src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + '198.51.100.0/24 via 192.0.2.2 proto static', + ipv='-4', + table='all', + timeout_sec=10, + ) print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') @@ -4485,6 +5093,7 @@ def test_route_static_issue_37714(self): print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('default via 192.168.0.193 table 249 proto static src 192.168.0.227 metric 128 onlink', output) self.assertIn('192.168.0.192/26 table 249 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('10.1.2.2 via 192.168.0.193 proto static src 192.168.0.227 metric 128 onlink', output) @@ -4492,16 +5101,19 @@ def test_route_static_issue_37714(self): self.assertIn('192.168.0.193 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('local 192.168.0.227 table local proto kernel scope host src 192.168.0.227', output) self.assertIn('broadcast 192.168.0.255 table local proto kernel scope link src 192.168.0.227', output) + # fmt: on print('### ip -6 route show table all dev dummy98') output = check_output('ip -6 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('2000:f00::/64 table 249 proto static src 2000:f00::227 metric 128 pref medium', output) self.assertIn('default via 2000:f00::1 table 249 proto static src 2000:f00::227 metric 128 onlink pref medium', output) self.assertIn('fe80::/64 proto kernel metric 256 pref medium', output) self.assertIn('local 2000:f00::227 table local proto kernel metric 0 pref medium', output) - self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium', output) + self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium') self.assertIn('multicast ff00::/8 table local proto kernel metric 256 pref medium', output) + # fmt: on def test_route_via_ipv6(self): copy_network_unit('25-route-via-ipv6.network', '12-dummy.netdev') @@ -4585,8 +5197,12 @@ def test_route_congctl(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_route_vrf(self): - copy_network_unit('25-route-vrf.network', '12-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-route-vrf.network', + '12-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('dummy98:routable', 'vrf99:carrier') @@ -4637,7 +5253,12 @@ def test_ip_route_ipv6_src_route(self): # a dummy device does not make the addresses go through tentative state, so we # reuse a bond from an earlier test, which does make the addresses go through # tentative state, and do our test on that - copy_network_unit('23-active-slave.network', '25-route-ipv6-src.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '25-route-ipv6-src.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:routable') @@ -4658,7 +5279,7 @@ def test_route_preferred_source_with_existing_address(self): output = check_output('ip -6 route list dev dummy98') print(output) - self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) + self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) # fmt: skip output = check_output('ip -4 route list dev dummy98') print(output) @@ -4702,7 +5323,7 @@ def test_ipv6_proxy_ndp(self): self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy') def test_ipv6_neigh_retrans_time(self): - link='test25' + link = 'test25' copy_network_unit('25-dummy.netdev', '25-dummy.network') start_networkd() @@ -4752,10 +5373,16 @@ def test_ipv6_neigh_retrans_time(self): remove_network_unit('25-ipv6-neigh-retrans-time-4s.network') def test_neighbor(self): - copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf', - '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-neighbor-ipv6.network', - copy_dropins=False) + copy_network_unit( + '12-dummy.netdev', + '25-neighbor-dummy.network', + '25-neighbor-dummy.network.d/10-step1.conf', + '25-gre-tunnel-remote-any.netdev', + '25-neighbor-ip.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-neighbor-ipv6.network', + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable') @@ -4768,7 +5395,7 @@ def test_neighbor(self): print('### ip neigh list dev ip6gretun97') output = check_output('ip neigh list dev ip6gretun97') print(output) - self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') + self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') # fmt: skip self.assertNotIn('2001:db8:0:f102::18', output) print('### ip neigh list dev dummy98') @@ -4799,8 +5426,10 @@ def test_neighbor(self): check_json(networkctl_json()) - remove_network_unit('25-neighbor-dummy.network.d/10-step1.conf', - '25-neighbor-dummy.network.d/10-step2.conf') + remove_network_unit( + '25-neighbor-dummy.network.d/10-step1.conf', + '25-neighbor-dummy.network.d/10-step2.conf', + ) copy_network_unit('25-neighbor-dummy.network.d/10-step3.conf') networkctl_reload() self.wait_online('dummy98:degraded') @@ -4815,8 +5444,12 @@ def test_neighbor(self): self.assertNotIn('2004:da8:1::1', output) def test_link_local_addressing(self): - copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev', - '25-link-local-addressing-no.network', '12-dummy.netdev') + copy_network_unit( + '25-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-link-local-addressing-no.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('test1:degraded', 'dummy98:carrier') @@ -5039,7 +5672,7 @@ def _test_activation_policy(self, interface, test): start_networkd() always = test.startswith('always') - initial_up = test != 'manual' and not test.endswith('down') # note: default is up + initial_up = test != 'manual' and not test.endswith('down') # note: default is up expect_up = initial_up next_up = not expect_up @@ -5128,7 +5761,7 @@ def test_activation_policy_required_for_online(self): else: self.tearDown() - print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') + print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') # fmt: skip with self.subTest(policy=policy, required=required): self._test_activation_policy_required_for_online(policy, required) @@ -5211,20 +5844,28 @@ def test_keep_configuration_on_restart(self): call('ip -6 addr add 2001:db8:9999:f101::15/64 dev unmanaged0') # Wait for all addresses + # fmt: off self.wait_address('unmanaged0', 'inet 10.20.30.40/32', scope='global', ipv='-4', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 2001:db8:9999:f101::15/64', scope='global', ipv='-6', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 fe80::[0-9a-f:]*/64', scope='link', ipv='-6', timeout_sec=10) + # fmt: on # Wait for all routes + # fmt: off self.wait_route('unmanaged0', 'local 10.20.30.40 proto kernel', table='local', ipv='-4', timeout_sec=10) self.wait_route('unmanaged0', 'local fe80::[0-9a-f:]* proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'multicast ff00::/8 proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', '2001:db8:9999:f101::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'fe80::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) + # fmt: on # Start `ip monitor` with output to a temporary file with tempfile.TemporaryFile(mode='r+', prefix='ip_monitor_u') as logfile_unmanaged: - process_u = subprocess.Popen(['ip', 'monitor', 'dev', 'unmanaged0'], stdout=logfile_unmanaged, text=True) + process_u = subprocess.Popen( + ['ip', 'monitor', 'dev', 'unmanaged0'], + stdout=logfile_unmanaged, + text=True, + ) start_networkd() self.check_keep_configuration_on_restart() @@ -5368,10 +6009,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1') print(output) + # fmt: off if first: self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) else: self.assertEqual('2001:1234:5:8f62::1 nhid 7 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.13') print(output) @@ -5382,10 +6025,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show 2001:1234:5:8f62::2') print(output) + # fmt: off if first: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output) else: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 2 dev lo proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.14') print(output) @@ -5410,8 +6055,13 @@ def _test_nexthop(self, manage_foreign_nexthops): check_output('ip address add 192.168.20.20/24 dev dummy98') check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98') - copy_network_unit('25-nexthop-1.network', '25-veth.netdev', '25-veth-peer.network', - '12-dummy.netdev', '25-nexthop-dummy-1.network') + copy_network_unit( + '25-nexthop-1.network', + '25-veth.netdev', + '25-veth-peer.network', + '12-dummy.netdev', + '25-nexthop-dummy-1.network', + ) start_networkd() self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5438,8 +6088,10 @@ def _test_nexthop(self, manage_foreign_nexthops): # Of course, networkctl_reconfigure() below is unnecessary in normal operation, but it is intentional # here to test reconfiguring with different .network files does not trigger race. # See also comments in link_drop_requests(). - networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network - networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: off + networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network + networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: on self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5501,8 +6153,8 @@ def test_nexthop(self): with self.subTest(manage_foreign_nexthops=manage_foreign_nexthops): self._test_nexthop(manage_foreign_nexthops) -class NetworkdTCTests(unittest.TestCase, Utilities): +class NetworkdTCTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5553,7 +6205,7 @@ def test_qdisc_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc codel 33: root') - self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') + self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_drr') def test_qdisc_drr(self): @@ -5605,7 +6257,7 @@ def test_qdisc_fq_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc fq_codel 34: root') - self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') + self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_fq_pie') def test_qdisc_fq_pie(self): @@ -5679,8 +6331,12 @@ def test_qdisc_htb_fifo(self): @expectedFailureIfModuleIsNotAvailable('sch_ingress') def test_qdisc_ingress(self): - copy_network_unit('25-qdisc-clsact.network', '12-dummy.netdev', - '25-qdisc-ingress.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-clsact.network', + '12-dummy.netdev', + '25-qdisc-ingress.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5713,8 +6369,12 @@ def test_qdisc_multiq(self): @expectedFailureIfModuleIsNotAvailable('sch_netem') def test_qdisc_netem(self): - copy_network_unit('25-qdisc-netem.network', '12-dummy.netdev', - '25-qdisc-netem-compat.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-netem.network', + '12-dummy.netdev', + '25-qdisc-netem-compat.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5784,7 +6444,7 @@ def test_qdisc_tbf(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc tbf 35: root') - self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') + self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_teql') def test_qdisc_teql(self): @@ -5812,13 +6472,13 @@ def test_qdisc_drop(self): self.assertFalse(networkd_is_failed()) check_output('tc qdisc replace dev dummy98 root fq pacing') self.assertFalse(networkd_is_failed()) - check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') + check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') # fmt: skip self.assertFalse(networkd_is_failed()) check_output('tc qdisc add dev dummy98 parent 10:1 handle 100: sfq') self.assertFalse(networkd_is_failed()) -class NetworkdStateFileTests(unittest.TestCase, Utilities): +class NetworkdStateFileTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5846,7 +6506,7 @@ def test_state_file(self): self.assertIn('REQUIRED_FAMILY_FOR_ONLINE=both', output) self.assertIn('ACTIVATION_POLICY=up', output) self.assertIn('NETWORK_FILE=/run/systemd/network/25-state-file-tests.network', output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5893,7 +6553,7 @@ def test_state_file(self): output = read_link_state_file('dummy98') print(output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5931,8 +6591,8 @@ def test_address_state(self): self.assertIn('IPV4_ADDRESS_STATE=off', output) self.assertIn('IPV6_ADDRESS_STATE=routable', output) -class NetworkdBondTests(unittest.TestCase, Utilities): +class NetworkdBondTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5972,7 +6632,12 @@ def test_bond_keep_master(self): self.wait_online('dummy98:enslaved') def test_bond_active_slave(self): - copy_network_unit('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -5991,12 +6656,18 @@ def test_bond_active_slave(self): '23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', - '12-dummy.netdev') + '12-dummy.netdev', + ) networkctl_reload() self.wait_online('dummy98:enslaved', 'bond199:degraded') def test_bond_primary_slave(self): - copy_network_unit('23-primary-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-primary-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -6007,7 +6678,9 @@ def test_bond_primary_slave(self): # for issue #25627 mkdir_p(os.path.join(network_unit_dir, '23-bond199.network.d')) for mac in ['00:11:22:33:44:55', '00:11:22:33:44:56']: - with open(os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8' + ) as f: f.write(f'[Link]\nMACAddress={mac}\n') networkctl_reload() @@ -6018,8 +6691,13 @@ def test_bond_primary_slave(self): self.assertIn(f'link/ether {mac}', output) def test_bond_operstate(self): - copy_network_unit('25-bond.netdev', '11-dummy.netdev', '12-dummy.netdev', - '25-bond99.network', '25-bond-slave.network') + copy_network_unit( + '25-bond.netdev', + '11-dummy.netdev', + '12-dummy.netdev', + '25-bond99.network', + '25-bond-slave.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bond99:routable') @@ -6068,8 +6746,8 @@ def test_bond_operstate(self): print(output) self.assertNotRegex(output, 'NO-CARRIER') -class NetworkdBridgeTests(unittest.TestCase, Utilities): +class NetworkdBridgeTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6077,8 +6755,13 @@ def tearDown(self): tear_down_common() def test_bridge_mac_none(self): - copy_network_unit('12-dummy-mac.netdev', '26-bridge-mac-slave.network', - '26-bridge-mac.netdev', '26-bridge-mac-master.network', '26-bridge-mac.link') + copy_network_unit( + '12-dummy-mac.netdev', + '26-bridge-mac-slave.network', + '26-bridge-mac.netdev', + '26-bridge-mac-master.network', + '26-bridge-mac.link', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bridge99:degraded') @@ -6091,9 +6774,13 @@ def test_bridge_mac_none(self): self.assertIn('link/ether 12:34:56:78:9a:01', output) def test_bridge_vlan(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network', - '26-bridge.netdev', '26-bridge-vlan-master.network', - copy_dropins=False) + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave.network', + '26-bridge.netdev', + '26-bridge-vlan-master.network', + copy_dropins=False, + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6126,8 +6813,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Change vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/10-override.conf', - '26-bridge-vlan-master.network.d/10-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/10-override.conf', + '26-bridge-vlan-master.network.d/10-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6156,8 +6845,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove several vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/20-override.conf', - '26-bridge-vlan-master.network.d/20-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/20-override.conf', + '26-bridge-vlan-master.network.d/20-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6186,8 +6877,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove all vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/30-override.conf', - '26-bridge-vlan-master.network.d/30-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/30-override.conf', + '26-bridge-vlan-master.network.d/30-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6204,9 +6897,14 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) def test_bridge_vlan_issue_20373(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave-issue-20373.network', - '26-bridge-issue-20373.netdev', '26-bridge-vlan-master-issue-20373.network', - '21-vlan.netdev', '21-vlan.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave-issue-20373.network', + '26-bridge-issue-20373.netdev', + '26-bridge-vlan-master-issue-20373.network', + '21-vlan.netdev', + '21-vlan.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded', 'vlan99:routable') @@ -6223,8 +6921,12 @@ def test_bridge_vlan_issue_20373(self): self.assertIn('600', output) def test_bridge_mdb(self): - copy_network_unit('11-dummy.netdev', '26-bridge-mdb-slave.network', - '26-bridge.netdev', '26-bridge-mdb-master.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-mdb-slave.network', + '26-bridge.netdev', + '26-bridge-mdb-master.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6236,7 +6938,7 @@ def test_bridge_mdb(self): self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') # The kernels older than 955062b03fa62b802a1ee34fbb04e39f7a70ae73 (v5.11) do not support L2 bridge MDB entries - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: + if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: # fmt: skip self.assertRegex(output, 'dev bridge99 port bridge99 grp 01:80:c2:00:00:0e permanent *vid 4069') def test_bridge_keep_master(self): @@ -6256,18 +6958,18 @@ def test_bridge_keep_master(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') def check_bridge_property(self): self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6294,31 +6996,38 @@ def check_bridge_property(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') output = check_output('bridge -d link show test1') print(output) - self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') + self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') self.assertIn('locked on', output) - if ' mab ' in output: # This is new in kernel and iproute2 v6.2 + if ' mab ' in output: # This is new in kernel and iproute2 v6.2 self.assertIn('mab on', output) def test_bridge_property(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99.network', '14-dummy.netdev', '26-bridge-vlan-tunnel.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99.network', + '14-dummy.netdev', + '26-bridge-vlan-tunnel.network', + ) start_networkd() self.check_bridge_property() @@ -6330,7 +7039,8 @@ def test_bridge_property(self): '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', '26-bridge-vlan-tunnel.network', - '25-bridge99.network') + '25-bridge99.network', + ) networkctl_reload() self.check_bridge_property() @@ -6373,7 +7083,7 @@ def test_bridge_property(self): output = check_output('ip address show bridge99') print(output) self.assertNotIn('192.168.0.15/24', output) - self.assertIn('192.168.0.16/24', output) # foreign address is kept + self.assertIn('192.168.0.16/24', output) # foreign address is kept print('### ip -6 route list table all dev bridge99') output = check_output('ip -6 route list table all dev bridge99') @@ -6394,8 +7104,11 @@ def test_bridge_property(self): self.assertIn('mtu 9000 ', output) def test_bridge_configure_without_carrier(self): - copy_network_unit('26-bridge.netdev', '26-bridge-configure-without-carrier.network', - '11-dummy.netdev') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-configure-without-carrier.network', + '11-dummy.netdev', + ) start_networkd() # With ConfigureWithoutCarrier=yes, the bridge should remain configured for all these situations @@ -6403,13 +7116,18 @@ def test_bridge_configure_without_carrier(self): with self.subTest(test=test): if test == 'no-slave': # bridge has no slaves; it's up but *might* not have carrier - self.wait_operstate('bridge99', operstate=r'(no-carrier|routable)', setup_state=None, setup_timeout=30) + self.wait_operstate( + 'bridge99', + operstate=r'(no-carrier|routable)', + setup_state=None, + setup_timeout=30, + ) # due to a bug in the kernel, newly-created bridges are brought up # *with* carrier, unless they have had any setting changed; e.g. # their mac set, priority set, etc. Then, they will lose carrier # as soon as a (down) slave interface is added, and regain carrier # again once the slave interface is brought up. - #self.check_link_attr('bridge99', 'carrier', '0') + # self.check_link_attr('bridge99', 'carrier', '0') elif test == 'add-slave': # add slave to bridge, but leave it down; bridge is definitely no-carrier self.check_link_attr('test1', 'operstate', 'down') @@ -6442,9 +7160,14 @@ def test_bridge_configure_without_carrier(self): self.assertRegex(output, '10.1.2.1') def test_bridge_ignore_carrier_loss(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6459,8 +7182,11 @@ def test_bridge_ignore_carrier_loss(self): self.assertRegex(output, 'inet 192.168.0.16/24 scope global secondary bridge99') def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): - copy_network_unit('26-bridge.netdev', '26-bridge-slave-interface-1.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('bridge99:no-carrier') @@ -6480,8 +7206,8 @@ def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): print(output) self.assertIn('from all to 8.8.8.8 lookup 100', output) -class NetworkdSRIOVTests(unittest.TestCase, Utilities): +class NetworkdSRIOVTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6497,7 +7223,9 @@ def setup_netdevsim(self, id=99, num_ports=1, num_vfs=0): # Create VF. if num_vfs > 0: - with open(f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8') as f: + with open( + f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8' + ) as f: f.write(f'{num_vfs}') @expectedFailureIfModuleIsNotAvailable('netdevsim') @@ -6511,11 +7239,12 @@ def test_sriov(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov_udev(self): @@ -6531,11 +7260,12 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6547,12 +7277,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6563,12 +7294,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6579,10 +7311,11 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off', + ) self.assertNotIn('vf 2', output) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6595,16 +7328,17 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) -class NetworkdLLDPTests(unittest.TestCase, Utilities): +class NetworkdLLDPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6626,12 +7360,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') @@ -6678,12 +7412,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') @@ -6715,13 +7449,13 @@ def test_lldp(self): # Compare the json output from sender and receiver sender_json = get_link_description('veth-peer')['LLDP'] - receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] + receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] # fmt: skip print(sender_json) print(receiver_json) self.assertEqual(sender_json, receiver_json) -class NetworkdRATests(unittest.TestCase, Utilities): +class NetworkdRATests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6777,7 +7511,11 @@ def check_ipv6_token_static(self): self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe') def test_ipv6_token_static(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() @@ -6798,35 +7536,63 @@ def test_ipv6_token_static(self): def test_ndisc_redirect(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() # Introduce three redirect routes. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:1:1a:2b:3c:4d --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:2:1a:2b:3c:4d --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:3:1a:2b:3c:4d --redirect-destination 2002:da8:1:3:1a:2b:3c:4d') + # fmt: on self.wait_route('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:3:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) # Change the target address of the redirects. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::1 --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::2 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') - self.wait_route_dropped('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route_dropped('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', ipv='-6', timeout_sec=10) + # fmt: on + self.wait_route_dropped( + 'veth99', + '2002:da8:1:1:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route_dropped( + 'veth99', + '2002:da8:1:2:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', + ipv='-6', + timeout_sec=10, + ) - # Send Neighbor Advertisement without the router flag to announce the default router is not available anymore. - # Then, verify that all redirect routes and the default route are dropped. + # Send Neighbor Advertisement without the router flag to announce the default router is not available + # anymore. Then, verify that all redirect routes and the default route are dropped. output = check_output('ip -6 address show dev veth-peer scope link') veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group() print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}') - check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') + check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') # fmt: skip self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10) @@ -6834,7 +7600,10 @@ def test_ndisc_redirect(self): # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306 since = datetime.datetime.now() check_output(f'{test_ndisc_send} --interface veth-peer --type rs --dest {veth_peer_ipv6ll}') - self.check_networkd_log('veth-peer: RADV: Received RS from the same interface, ignoring.', since=since) + self.check_networkd_log( + 'veth-peer: RADV: Received RS from the same interface, ignoring.', + since=since, + ) def check_ndisc_mtu(self, mtu): for _ in range(20): @@ -6847,11 +7616,13 @@ def check_ndisc_mtu(self, mtu): def test_ndisc_mtu(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', - '25-veth-peer-no-address.network', - '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-veth-peer-no-address.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.wait_online('veth-peer:degraded') @@ -6875,16 +7646,24 @@ def test_ndisc_mtu(self): self.check_ndisc_mtu(1700) def test_ipv6_token_prefixstable(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 - with open(os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('\n[IPv6AcceptRA]\nPrefixAllowList=2002:da8:1:0::/64\n') networkctl_reload() @@ -6892,28 +7671,57 @@ def test_ipv6_token_prefixstable(self): output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 check_output('ip address del 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth99') check_output('ip address add 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable check_output('ip address del 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth99') check_output('ip address add 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', ipv='-6', timeout_sec=10) # the 3rd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', + ipv='-6', + timeout_sec=10, + ) # the 3rd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable def test_ipv6_token_prefixstable_without_address(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable-without-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -6923,21 +7731,29 @@ def test_ipv6_token_prefixstable_without_address(self): self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output) def test_router_hop_limit(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-hop-limit.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client:routable', 'client-p:enslaved', - 'router:degraded', 'router-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-hop-limit.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client:routable', + 'client-p:enslaved', + 'router:degraded', + 'router-p:enslaved', + 'bridge99:routable', + ) self.check_ipv6_sysctl_attr('client', 'hop_limit', '42') - with open(os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[IPv6SendRA]\nHopLimit=43\n') networkctl_reload() @@ -6954,14 +7770,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.wait_online('client:routable') self.wait_address('client', f'2002:da8:1:99:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) self.wait_address('client', f'2002:da8:1:98:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', ipv='-6', timeout_sec=10) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) + # fmt: off self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1} expires [0-9]*sec pref {preference_1}') self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2} expires [0-9]*sec pref {preference_2}') + # fmt: on for i in [100, 200, 300, 512, 1024, 2048]: if i not in [metric_1, metric_2]: @@ -6972,20 +7800,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.assertNotIn(f'pref {i}', output) def test_router_preference(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-high.netdev', - '25-veth-router-low.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-high.network', - '25-veth-router-low.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client-p:enslaved', - 'router-high:degraded', 'router-high-p:enslaved', - 'router-low:degraded', 'router-low-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-high.netdev', + '25-veth-router-low.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-high.network', + '25-veth-router-low.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client-p:enslaved', + 'router-high:degraded', + 'router-high-p:enslaved', + 'router-low:degraded', + 'router-low-p:enslaved', + 'bridge99:routable', + ) networkctl_reconfigure('client') self.wait_online('client:routable') @@ -6993,64 +7827,135 @@ def test_router_preference(self): # change the map from preference to metric. with open(os.path.join(network_unit_dir, '25-veth-client.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Link]\nMACAddress=12:34:56:78:9a:01\n[IPv6AcceptRA]\nRouteMetric=100:200:300\n') + f.write(''' +[Link] +MACAddress=12:34:56:78:9a:01 +[IPv6AcceptRA] +RouteMetric=100:200:300 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # swap the preference (for issue #28439) - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Use the same preference, and check if the two routes are not coalesced. See issue #33470. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Use route options to configure default routes. # The preference specified in the RA header should be ignored. See issue #33468. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Set zero lifetime to the route options. # The preference specified in the RA header should be used. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # Use route options with preference to configure default routes. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Set zero lifetime again to the route options. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): if not manage_foreign_nexthops: copy_networkd_conf_dropin('networkd-manage-foreign-nexthops-no.conf') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-static-route.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -7060,7 +7965,7 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): print(output) self.assertIn('via fe80::1034:56ff:fe78:9abd proto static metric 256 pref medium', output) if manage_foreign_nexthops: - self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') # fmt: skip else: self.assertNotIn('proto ra', output) @@ -7068,15 +7973,23 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): output = check_output('ip -6 nexthop show dev veth99') print(output) if manage_foreign_nexthops: - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') # fmt: skip else: self.assertEqual(output, '') # Also check if the static route is protected from RA with zero lifetime with open(os.path.join(network_unit_dir, '25-ipv6-prefix.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Network]\nIPv6SendRA=no\n') - networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime - self.wait_route_dropped('veth99', r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', ipv='-6', timeout_sec=10) + f.write(''' +[Network] +IPv6SendRA=no +''') + networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime + self.wait_route_dropped( + 'veth99', + r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev veth99 default') output = check_output('ip -6 route show dev veth99 default') @@ -7104,18 +8017,27 @@ def test_ndisc_vs_static_route(self): # radvd supports captive portal since v2.20. # https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_captive_portal(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) start_radvd(config_file='captive-portal.conf') networkctl_reconfigure('client') @@ -7126,31 +8048,53 @@ def test_captive_portal(self): print(output) self.assertIn('Captive Portal: http://systemd.io', output) - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_invalid_captive_portal(self): def radvd_write_config(captive_portal_uri): - with open(os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), mode='w', encoding='utf-8') as f: - f.write(f'interface router-captive {{ AdvSendAdvert on; AdvCaptivePortalAPI "{captive_portal_uri}"; prefix 2002:da8:1:99::/64 {{ AdvOnLink on; AdvAutonomous on; }}; }};') + with open( + os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), + mode='w', + encoding='utf-8', + ) as f: + f.write(f''' +interface router-captive {{ + AdvSendAdvert on; + AdvCaptivePortalAPI "{captive_portal_uri}"; + prefix 2002:da8:1:99::/64 {{ + AdvOnLink on; + AdvAutonomous on; + }}; +}}; +''') captive_portal_uris = [ - "42ěščěškd ěšč ě s", - " ", - "🤔", + '42ěščěškd ěšč ě s', + ' ', + '🤔', ] - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) for uri in captive_portal_uris: - print(f"Captive portal: {uri}") + print(f'Captive portal: {uri}') radvd_write_config(uri) stop_radvd() start_radvd(config_file='bogus-captive-portal.conf') @@ -7162,8 +8106,8 @@ def radvd_write_config(captive_portal_uri): print(output) self.assertNotIn('Captive Portal:', output) -class NetworkdDHCPServerTests(unittest.TestCase, Utilities): +class NetworkdDHCPServerTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -7180,7 +8124,7 @@ def check_dhcp_server(self, persist_leases='yes'): output = networkctl_status('veth-peer') print(output) - self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + 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') @@ -7210,7 +8154,7 @@ def test_dhcp_server(self): output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env) if 'Offered DHCP leases: 192.168.5.' in output: break - time.sleep(.2) + time.sleep(0.2) else: self.fail() @@ -7247,7 +8191,11 @@ def test_dhcp_server_persist_leases_runtime(self): self.check_dhcp_server(persist_leases='runtime') def test_dhcp_server_null_server_address(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-null-server-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7292,8 +8240,13 @@ def test_dhcp_server_null_server_address(self): self.assertIn(f'Offered DHCP leases: {client_address}', output) def test_dhcp_server_with_uplink(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network', - '12-dummy.netdev', '25-dhcp-server-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-downstream.network', + '12-dummy.netdev', + '25-dhcp-server-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7305,7 +8258,11 @@ def test_dhcp_server_with_uplink(self): self.assertIn('NTP: 192.168.5.1', output) def test_emit_router_timezone(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-timezone-router.network', '25-dhcp-server-timezone-router.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-timezone-router.network', + '25-dhcp-server-timezone-router.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7316,7 +8273,11 @@ def test_emit_router_timezone(self): self.assertIn('Time Zone: Europe/Berlin', output) def test_dhcp_server_static_lease_mac_by_network(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-static-lease.network', '25-dhcp-server-static-lease.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-static-lease.network', + '25-dhcp-server-static-lease.network', + ) copy_networkd_conf_dropin('10-dhcp-client-id-duid.conf') start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7348,9 +8309,11 @@ def test_dhcp_server_static_lease_duid(self): self.assertRegex(output, 'DHCPv4 Client ID: IAID:[0-9a-z]*/DUID') def test_dhcp_server_static_lease_hostname_simple(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-simple-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-simple-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7360,9 +8323,11 @@ def test_dhcp_server_static_lease_hostname_simple(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'simple-host') def test_dhcp_server_static_lease_hostname_fqdn(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-fqdn-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-fqdn-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7372,7 +8337,11 @@ def test_dhcp_server_static_lease_hostname_fqdn(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com') def test_dhcp_server_resolve_hook(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-resolve-hook.network', '25-dhcp-server-resolve-hook.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-resolve-hook.network', + '25-dhcp-server-resolve-hook.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7382,7 +8351,6 @@ def test_dhcp_server_resolve_hook(self): class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -7390,12 +8358,14 @@ def tearDown(self): tear_down_common() def test_relay_agent(self): - copy_network_unit('25-agent-veth-client.netdev', - '25-agent-veth-server.netdev', - '25-agent-client.network', - '25-agent-server.network', - '25-agent-client-peer.network', - '25-agent-server-peer.network') + copy_network_unit( + '25-agent-veth-client.netdev', + '25-agent-veth-server.netdev', + '25-agent-client.network', + '25-agent-server.network', + '25-agent-client-peer.network', + '25-agent-server-peer.network', + ) start_networkd() self.wait_online('client:routable') @@ -7405,19 +8375,21 @@ def test_relay_agent(self): self.assertRegex(output, r'Address: 192.168.5.150 \(DHCPv4 via 192.168.5.1\)') def test_relay_agent_on_bridge(self): - copy_network_unit('25-agent-bridge.netdev', - '25-agent-veth-client.netdev', - '25-agent-bridge.network', - '25-agent-bridge-port.network', - '25-agent-client.network') + copy_network_unit( + '25-agent-bridge.netdev', + '25-agent-veth-client.netdev', + '25-agent-bridge.network', + '25-agent-bridge-port.network', + '25-agent-client.network', + ) start_networkd() self.wait_online('bridge-relay:routable', 'client-peer:enslaved') # For issue #30763. self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') -class NetworkdDHCPClientTests(unittest.TestCase, Utilities): +class NetworkdDHCPClientTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -7425,7 +8397,11 @@ def tearDown(self): tear_down_common() def test_dhcp_client_ipv6_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7433,10 +8409,12 @@ def test_dhcp_client_ipv6_only(self): # information request mode # The name ipv6-only option may not be supported by older dnsmasq # start_dnsmasq('--dhcp-option=option:ipv6-only,300') - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]', - ra_mode='ra-stateless') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ra_mode='ra-stateless', + ) self.wait_online('veth99:routable', 'veth-peer:routable') # DHCPv6 REPLY for INFORMATION-REQUEST may be received after the link entered configured state. @@ -7445,7 +8423,7 @@ def test_dhcp_client_ipv6_only(self): output = read_link_state_file('veth99') if 'DNS=2600::ee' in output: break - time.sleep(.2) + time.sleep(0.2) # Check link state file print('## link state file') @@ -7475,9 +8453,11 @@ def test_dhcp_client_ipv6_only(self): # solicit mode stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reconfigure('veth99') self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7528,13 +8508,17 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) # Testing without rapid commit support - with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[DHCPv6]\nRapidCommit=no\n') stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reload() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7581,25 +8565,29 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) def test_dhcp_client_ipv6_dbus_status(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') # Note that at this point the DHCPv6 client has not been started because no RA (with managed # bit set) has yet been received and the configuration does not include WithoutRA=true state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'stopped') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'selecting') start_dnsmasq('--dhcp-option=108,00:00:02:00') self.wait_online('veth99:routable', 'veth-peer:routable') state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'bound') # DHCPv4 client will stop after an DHCPOFFER message received, so we need to wait for a while. @@ -7607,9 +8595,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') # restart dnsmasq to clear log @@ -7624,9 +8612,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') print('## dnsmasq log') @@ -7638,7 +8626,11 @@ def test_dhcp_client_ipv6_dbus_status(self): self.assertNotIn('DHCPACK(veth-peer)', output) def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only-custom-client-identifier.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only-custom-client-identifier.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7661,8 +8653,13 @@ def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): self.assertIn('sent size: 0 option: 14 rapid-commit', output) def test_dhcp_client_ipv4_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network', - '25-sit-dhcp4.netdev', '25-sit-dhcp4.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + '25-sit-dhcp4.netdev', + '25-sit-dhcp4.network', + ) self.setup_nftset('addr4', 'ipv4_addr') self.setup_nftset('network4', 'ipv4_addr', 'flags interval;') @@ -7670,11 +8667,13 @@ def test_dhcp_client_ipv4_only(self): start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable', 'sit-dhcp4:carrier') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') @@ -7683,7 +8682,7 @@ def test_dhcp_client_ipv4_only(self): print(output) self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) - self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7758,7 +8757,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address1} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address1} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7770,11 +8769,13 @@ def test_dhcp_client_ipv4_only(self): # change address range, DNS servers, and Domains stop_dnsmasq() - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', - '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', - '--dhcp-option=option:domain-search,foo.example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.120,192.168.5.129',) + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', + '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', + '--dhcp-option=option:domain-search,foo.example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.120,192.168.5.129', + ) # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 print('Wait for the DHCP lease to be expired') @@ -7789,7 +8790,7 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) self.assertNotIn(f'{address1}', output) - self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7865,7 +8866,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address2} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address2} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7941,7 +8942,7 @@ def _test_dhcp_client_send_release_one(self, stop=True) -> bool: try: output = read_dnsmasq_log_file() except FileNotFoundError: - output = "" + output = '' if 'DHCPRELEASE' in output: success = True break @@ -8007,48 +9008,71 @@ def test_dhcp_client_send_release(self): 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') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'rebooting') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_allow_list(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-allow-list.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-allow-list.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') since = datetime.datetime.now() start_dnsmasq() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/00-allow-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/10-deny-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', + since=since, + ) - @unittest.skipUnless("--dhcp-rapid-commit" in run("dnsmasq --help").stdout, reason="dnsmasq is missing dhcp-rapid-commit support") + @unittest.skipUnless( + '--dhcp-rapid-commit' in run('dnsmasq --help').stdout, + reason='dnsmasq is missing dhcp-rapid-commit support', + ) def test_dhcp_client_rapid_commit(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network') start_networkd() @@ -8059,7 +9083,7 @@ def test_dhcp_client_rapid_commit(self): self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') output = read_dnsmasq_log_file() @@ -8075,7 +9099,7 @@ def check_bootp_client(self, check_log): self.assertRegex(output, r'inet 192.168.5.[0-9]*/24') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') if check_log: @@ -8098,7 +9122,9 @@ def test_bootp_client(self): networkctl_reload() self.check_bootp_client(check_log=True) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=no\n') networkctl_reload() @@ -8113,7 +9139,9 @@ def test_bootp_client(self): self.assertIn('DHCPREQUEST(veth-peer)', output) self.assertIn('DHCPACK(veth-peer)', output) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=yes\n') since = datetime.datetime.now() @@ -8125,30 +9153,36 @@ def test_bootp_client(self): self.check_networkd_log('veth99: DHCPv4 client: RELEASE', since=since) def test_dhcp_client_ipv6_only_mode_without_ipv6_connectivity(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-ipv6-only-mode.network', - '25-dhcp-client-ipv6-only-mode.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-ipv6-only-mode.network', + '25-dhcp-client-ipv6-only-mode.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', timeout='40s') self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_ipv4_use_routes_gateway(self): first = True - for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4): + for routes, gateway, dns_and_ntp_routes, classless in itertools.product([True, False], repeat=4): if first: first = False else: self.tearDown() + # fmt: off print(f'### test_dhcp_client_ipv4_use_routes_gateway(routes={routes}, gateway={gateway}, dns_and_ntp_routes={dns_and_ntp_routes}, classless={classless})') with self.subTest(routes=routes, gateway=gateway, dns_and_ntp_routes=dns_and_ntp_routes, classless=classless): self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dns_and_ntp_routes, classless) + # fmt: on - def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns_and_ntp_routes, classless): + def _test_dhcp_client_ipv4_use_routes_gateway( + self, use_routes, use_gateway, dns_and_ntp_routes, classless + ): testunit = '25-dhcp-client-ipv4-use-routes-use-gateway.network' testunits = ['25-veth.netdev', '25-dhcp-server-veth-peer.network', testunit] testunits.append(f'{testunit}.d/use-routes-{use_routes}.conf') @@ -8161,7 +9195,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns additional_options = [ '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8', '--dhcp-option=option:ntp-server,192.168.5.11,9.9.9.9', - '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3' + '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3', ] if classless: additional_options += [ @@ -8174,6 +9208,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns print(output) # Check UseRoutes= + # fmt: off if use_routes: if classless: self.assertRegex(output, r'default via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024') @@ -8195,20 +9230,22 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'8.0.0.0/8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.2 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.3 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + # fmt: on # Check UseGateway= if use_gateway and (not classless or not use_routes): self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check route to gateway if (use_gateway or dns_and_ntp_routes) and (not classless or not use_routes): self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check RoutesToDNS= and RoutesToNTP= + # fmt: off if dns_and_ntp_routes: self.assertRegex(output, r'192.168.5.10 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') @@ -8227,11 +9264,16 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'9.9.9.9 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') + # fmt: on check_json(networkctl_json()) def test_dhcp_client_settings_anonymize(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-anonymize.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8245,9 +9287,11 @@ def test_dhcp_client_settings_anonymize(self): self.assertNotIn('26:mtu', output) def test_dhcp_keep_configuration_dynamic(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8255,8 +9299,7 @@ def test_dhcp_keep_configuration_dynamic(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip # Stopping dnsmasq as networkd won't be allowed to renew the DHCP lease. stop_dnsmasq() @@ -8268,18 +9311,20 @@ def test_dhcp_keep_configuration_dynamic(self): # The lease address should be kept after the lease expired output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip stop_networkd() # The lease address should be kept after networkd stopped output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip - with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('[Network]\nDHCP=no\n') start_networkd() @@ -8288,13 +9333,14 @@ def test_dhcp_keep_configuration_dynamic(self): # Still the lease address should be kept after networkd restarted output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip def test_dhcp_keep_configuration_dynamic_on_stop(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic-on-stop.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic-on-stop.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8302,14 +9348,14 @@ def test_dhcp_keep_configuration_dynamic_on_stop(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip stop_dnsmasq() stop_networkd() output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip start_networkd() self.wait_online('veth-peer:routable') @@ -8326,13 +9372,28 @@ def test_dhcp_client_reuse_address_as_static(self): self.wait_online('veth99:routable', 'veth-peer:routable') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = check_output('ip address show dev veth99 scope global') ipv4_address = re.search(r'192.168.5.[0-9]*/24', output).group() ipv6_address = re.search(r'2600::[0-9a-f:]*/128', output).group() - static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address, 'Address=' + ipv6_address]) + static_network = f''' +[Match] +Name=veth99 +[Network] +IPv6AcceptRA=no +Address={ipv4_address} +Address={ipv6_address} +''' print(static_network) remove_network_unit('25-dhcp-client.network') @@ -8345,26 +9406,37 @@ def test_dhcp_client_reuse_address_as_static(self): output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip output = check_output('ip -6 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *valid_lft forever preferred_lft forever') # fmt: skip @expectedFailureIfModuleIsNotAvailable('vrf') def test_dhcp_client_vrf(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-vrf.network', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-vrf.network', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() self.wait_online('veth99:routable', 'veth-peer:routable', 'vrf99:carrier') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) print('## ip -d link show dev vrf99') output = check_output('ip -d link show dev vrf99') @@ -8374,16 +9446,20 @@ def test_dhcp_client_vrf(self): print('## ip address show vrf vrf99') output = check_output('ip address show vrf vrf99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip address show dev veth99') output = check_output('ip address show dev veth99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip route show vrf vrf99') output = check_output('ip route show vrf vrf99') @@ -8398,8 +9474,11 @@ def test_dhcp_client_vrf(self): self.assertEqual(output, '') def test_dhcp_client_gateway_onlink_implicit(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-gateway-onlink-implicit.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-gateway-onlink-implicit.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8417,8 +9496,11 @@ def test_dhcp_client_gateway_onlink_implicit(self): self.assertRegex(output, 'onlink') def test_dhcp_client_with_ipv4ll(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-with-ipv4ll.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-with-ipv4ll.network', + ) start_networkd() # we need to increase timeout above default, as this will need to wait for # systemd-networkd to get the dhcpv4 transient failure event @@ -8431,20 +9513,39 @@ def test_dhcp_client_with_ipv4ll(self): start_dnsmasq() print('Wait for a DHCP lease to be acquired and the IPv4LL address to be dropped') - self.wait_address('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4') - self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + ) + self.wait_address_dropped( + 'veth99', + r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) self.wait_online('veth99:routable') output = check_output('ip -4 address show dev veth99') print(output) - self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') # fmt: skip self.assertNotIn('169.254.', output) self.assertNotIn('scope link', output) stop_dnsmasq() print('Wait for the DHCP lease to be expired and an IPv4LL address to be acquired') - self.wait_address_dropped('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4', timeout_sec=130) - self.wait_address('veth99', r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address_dropped( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + timeout_sec=130, + ) + self.wait_address( + 'veth99', + r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) output = check_output('ip -4 address show dev veth99') print(output) @@ -8454,7 +9555,11 @@ def test_dhcp_client_with_ipv4ll(self): def test_dhcp_client_use_dns(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8466,9 +9571,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8486,12 +9600,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + ) check(self, True, True) check(self, True, False) @@ -8501,7 +9622,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_default_use_domains(self): def check(self, common, ipv4, ipv6): mkdir_p(networkd_conf_dropin_dir) - with open(os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[Network]\nUseDomains=') f.write('yes\n' if common else 'no\n') f.write('[DHCPv4]\nUseDomains=') @@ -8511,16 +9636,27 @@ def check(self, common, ipv4, ipv6): restart_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-option=option6:domain-search,example.com') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-option=option6:domain-search,example.com', + ) self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) for _ in range(20): output = resolvectl('domain', 'veth99') @@ -8539,7 +9675,12 @@ def check(self, common, ipv4, ipv6): stop_dnsmasq() remove_networkd_conf_dropin('default_use_domains.conf') - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) check(self, True, False, False) check(self, False, True, True) check(self, False, True, False) @@ -8549,7 +9690,11 @@ def check(self, common, ipv4, ipv6): def test_dhcp_client_use_dnr(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8561,9 +9706,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8582,16 +9736,31 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"]) - dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}") - dnr_v6 = dnr_v6_instance_data(adn = "dns.google", addrs = ["2001:4860:4860::8888", "2001:4860:4860::8844"]) - masq = lambda bs: ':'.join(f"{b:02x}" for b in bs) - start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', - f'--dhcp-option=option6:144,{masq(dnr_v6)}') + dnr_v4 = dnr_v4_instance_data( + adn='dns.google', + addrs=['8.8.8.8', '8.8.4.4'], + ) + dnr_v4 += dnr_v4_instance_data( + adn='homer.simpson', + addrs=['0.7.4.2'], + alpns=('dot', 'h2', 'h3'), + dohpath='/springfield{?dns}', + ) + dnr_v6 = dnr_v6_instance_data( + adn='dns.google', + addrs=['2001:4860:4860::8888', '2001:4860:4860::8844'], + ) + masq = lambda bs: ':'.join(f'{b:02x}' for b in bs) + start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', f'--dhcp-option=option6:144,{masq(dnr_v6)}') check(self, True, True) check(self, True, False) @@ -8601,7 +9770,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_sip(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseSIP=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseSIP=') @@ -8612,9 +9785,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8631,13 +9813,20 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:sip-server,192.168.5.1', - '--dhcp-option=option6:sip-server,[2600::1]', - '--dhcp-option=option6:sip-server-domain,foo.example.com') + start_dnsmasq( + '--dhcp-option=option:sip-server,192.168.5.1', + '--dhcp-option=option6:sip-server,[2600::1]', + '--dhcp-option=option6:sip-server-domain,foo.example.com', + ) check(self, True, True) check(self, True, False) @@ -8647,7 +9836,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_captive_portal(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8659,9 +9852,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8672,12 +9874,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://systemd.io', - '--dhcp-option=option6:103,http://systemd.io') + start_dnsmasq( + '--dhcp-option=114,http://systemd.io', + '--dhcp-option=option6:103,http://systemd.io', + ) check(self, True, True) check(self, True, False) @@ -8687,7 +9896,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_reject_captive_portal(self): def check(self, ipv4, ipv6): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8697,9 +9910,18 @@ def check(self, ipv4, ipv6): networkctl_reload() self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8708,20 +9930,27 @@ def check(self, ipv4, ipv6): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://|invalid/url', - '--dhcp-option=option6:103,http://|invalid/url') + start_dnsmasq( + '--dhcp-option=114,http://|invalid/url', + '--dhcp-option=option6:103,http://|invalid/url', + ) check(self, True, True) check(self, True, False) check(self, False, True) check(self, False, False) -class NetworkdDHCPPDTests(unittest.TestCase, Utilities): +class NetworkdDHCPPDTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -8748,10 +9977,14 @@ def check_dhcp6_prefix(self, link): self.assertGreater(prefixInfo[0]['PreferredLifetimeUSec'], 0) self.assertGreater(prefixInfo[0]['ValidLifetimeUSec'], 0) - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_address(self): # For issue #29979. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-address.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8770,11 +10003,15 @@ def test_dhcp6pd_no_address(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_assign(self): # Similar to test_dhcp6pd_no_assign(), but in this case UseAddress=yes (default), # However, the server does not provide IA_NA. For issue #31349. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-assign.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-assign.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8793,21 +10030,40 @@ def test_dhcp6pd_no_assign(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd(self): - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + ) start_networkd() self.wait_online('veth-peer:routable') start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) self.setup_nftset('addr6', 'ipv6_addr') self.setup_nftset('network6', 'ipv6_addr', 'flags interval;') @@ -8833,31 +10089,45 @@ def test_dhcp6pd(self): print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) + # fmt: off # IA_NA self.assertRegex(output, 'inet6 3ffe:501:ffff:100::[0-9]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') # address in IA_PD (Token=static) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic') # address in IA_PD (Token=eui64) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic') + # fmt: on # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -8869,41 +10139,57 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -8958,9 +10244,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -8975,9 +10265,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -9023,34 +10317,46 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print('### ip -4 address show dev veth99 scope global') output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, fr'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') + self.assertRegex(output, rf'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') # fmt: skip print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -9062,41 +10368,57 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -9147,9 +10469,13 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -9168,8 +10494,8 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print(f'### ip -6 address show dev {tunnel_name}') output = check_output(f'ip -6 address show dev {tunnel_name}') print(output) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') - self.assertRegex(output, fr'inet6 ::{address_prefix_re}/96 scope global') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') # fmt: skip + self.assertRegex(output, rf'inet6 ::{address_prefix_re}/96 scope global') print(f'### ip -6 route show dev {tunnel_name}') output = check_output(f'ip -6 route show dev {tunnel_name}') @@ -9181,7 +10507,7 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 route show default') print(output) self.assertIn('default', output) - self.assertRegex(output, fr'via ::{border_router} dev {tunnel_name}') + self.assertRegex(output, rf'via ::{border_router} dev {tunnel_name}') def test_dhcp4_6rd(self): def get_dhcp_6rd_prefix(link): @@ -9198,14 +10524,25 @@ def get_dhcp_6rd_prefix(link): return prefixInfo - copy_network_unit('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network', - '80-6rd-tunnel.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp4-6rd-server.network', + '25-dhcp4-6rd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + '80-6rd-tunnel.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -9214,19 +10551,29 @@ def get_dhcp_6rd_prefix(link): # 6rd-prefix: 2001:db8::/32 # br-address: 10.0.0.1 - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', - ipv4_range='10.100.100.100,10.100.100.200', - ipv4_router='10.0.0.1') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', + ipv4_range='10.100.100.100,10.100.100.200', + ipv4_router='10.0.0.1', + ) + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) # Check the DBus interface for assigned prefix information prefixInfo = get_dhcp_6rd_prefix('veth99') - self.assertEqual(prefixInfo['Prefix'], [32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,0]) # 2001:db8:: + self.assertEqual(prefixInfo['Prefix'], [32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # 2001:db8:: # fmt: skip self.assertEqual(prefixInfo['PrefixLength'], 32) self.assertEqual(prefixInfo['IPv4MaskLength'], 8) - self.assertEqual(prefixInfo['BorderRouters'], [[10,0,0,1]]) + self.assertEqual(prefixInfo['BorderRouters'], [[10, 0, 0, 1]]) # Test case for a downstream which appears later check_output('ip link add dummy97 type dummy') @@ -9241,30 +10588,56 @@ def get_dhcp_6rd_prefix(link): self.wait_online(f'{tunnel_name}:routable') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Test case for reconfigure networkctl_reconfigure('dummy98', 'dummy99') self.wait_online('dummy98:routable', 'dummy99:degraded') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Change the address range and (border) router, then if check the same tunnel is reused. stop_dnsmasq() - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', - ipv4_range='10.100.100.200,10.100.100.250', - ipv4_router='10.0.0.2') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', + ipv4_range='10.100.100.200,10.100.100.250', + ipv4_router='10.0.0.2', + ) print('Wait for the DHCP lease to be renewed/rebind') time.sleep(120) - self.wait_online('veth99:routable', 'test1:routable', 'dummy97:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy97:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) + + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.2', + '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', + '(10.0.0.2|a00:2)', + ) - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.2', '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', '(10.0.0.2|a00:2)') class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -9272,8 +10645,13 @@ def tearDown(self): tear_down_common() def test_ipv6_route_prefix(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9298,7 +10676,7 @@ def test_ipv6_route_prefix(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9331,8 +10709,13 @@ def test_ipv6_route_prefix(self): self.assertEqual(prefix_length, 96) def test_ipv6_route_prefix_deny_list(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client-deny-list.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client-deny-list.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9354,7 +10737,7 @@ def test_ipv6_route_prefix_deny_list(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9370,8 +10753,8 @@ def test_ipv6_route_prefix_deny_list(self): print(output) self.assertIn('example.com', output) -class NetworkdMTUTests(unittest.TestCase, Utilities): +class NetworkdMTUTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -9398,7 +10781,7 @@ def check_mtu(self, mtu, ipv6_mtu=None, reset=True): self.reset_check_mtu(mtu, ipv6_mtu) def reset_check_mtu(self, mtu, ipv6_mtu=None): - ''' test setting mtu/ipv6_mtu with interface already up ''' + """test setting mtu/ipv6_mtu with interface already up""" stop_networkd() # note - changing the device mtu resets the ipv6 mtu @@ -9424,39 +10807,46 @@ def test_mtu_link(self): self.check_mtu('1600', reset=False) def test_ipv6_mtu(self): - ''' set ipv6 mtu without setting device mtu ''' + """set ipv6 mtu without setting device mtu""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1400.conf') self.check_mtu('1500', '1400') def test_ipv6_mtu_toolarge(self): - ''' try set ipv6 mtu over device mtu (it shouldn't work) ''' + """try set ipv6 mtu over device mtu (it shouldn't work)""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1500', '1500') def test_mtu_network_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via network file ''' - copy_network_unit('12-dummy.netdev', '12-dummy.network.d/mtu.conf', '12-dummy.network.d/ipv6-mtu-1550.conf') + """set ipv6 mtu and set device mtu via network file""" + copy_network_unit( + '12-dummy.netdev', + '12-dummy.network.d/mtu.conf', + '12-dummy.network.d/ipv6-mtu-1550.conf', + ) self.check_mtu('1600', '1550') def test_mtu_netdev_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via netdev file ''' + """set ipv6 mtu and set device mtu via netdev file""" copy_network_unit('12-dummy-mtu.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) def test_mtu_link_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via link file ''' + """set ipv6 mtu and set device mtu via link file""" copy_network_unit('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) -class NetworkdSysctlTest(unittest.TestCase, Utilities): +class NetworkdSysctlTest(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @unittest.skipUnless(compare_kernel_version("6.12"), reason="On kernels <= 6.12, bpf_current_task_under_cgroup() isn't available for program types BPF_PROG_TYPE_CGROUP_SYSCTL") + @unittest.skipUnless( + compare_kernel_version('6.12'), + reason='On kernels <= 6.12, bpf_current_task_under_cgroup() is not available for program types BPF_PROG_TYPE_CGROUP_SYSCTL', + ) def test_sysctl_monitor(self): copy_network_unit('12-dummy.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -9473,17 +10863,19 @@ def test_sysctl_monitor(self): call('sysctl -w net.ipv6.conf.dummy98.hop_limit=4') call('sysctl -w net.ipv6.conf.dummy98.max_addresses=10') - log=read_networkd_log() + log = read_networkd_log() + # fmt: off self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/accept_ra' from '0' to '1', conflicting with our setting to '0'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/mtu' from '1550' to '1360', conflicting with our setting to '1550'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv4/conf/dummy98/promote_secondaries' from '1' to '0', conflicting with our setting to '1'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/proxy_ndp' from '0' to '1', conflicting with our setting to '0'") + # fmt: on self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/hop_limit'", log) self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log) - self.assertNotIn("Sysctl monitor BPF returned error", log) + self.assertNotIn('Sysctl monitor BPF returned error', log) -class NetworkdWWANTests(unittest.TestCase, Utilities): +class NetworkdWWANTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -9508,7 +10900,7 @@ def test_wwan_ipv4v6_static(self): '--ipv6-address', '2001:db8::1', '--ipv6-gateway', '2001:db8::2', '--ipv6-prefix', '64', - ) + ) # fmt: skip except (subprocess.CalledProcessError, PermissionError, OSError) as e: self.skipTest(f'Failed to start mock ModemManager: {e}') start_networkd() @@ -9530,17 +10922,67 @@ def test_wwan_ipv4v6_static(self): 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') - parser.add_argument('--source-dir', help='Path to source dir/git tree', dest='source_dir') - parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind) - parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug) - parser.add_argument('--asan-options', help='ASAN options', dest='asan_options') - parser.add_argument('--lsan-options', help='LSAN options', dest='lsan_options') - parser.add_argument('--ubsan-options', help='UBSAN options', dest='ubsan_options') - parser.add_argument('--with-coverage', help='Loosen certain sandbox restrictions to make gcov happy', dest='with_coverage', type=bool, nargs='?', const=True, default=with_coverage) - parser.add_argument('--no-journal', help='Do not show journal of systemd-networkd on stop', dest='show_journal', action='store_false') + parser.add_argument( + '--build-dir', + help='Path to build dir', + dest='build_dir', + ) + parser.add_argument( + '--source-dir', + help='Path to source dir/git tree', + dest='source_dir', + ) + parser.add_argument( + '--valgrind', + help='Enable valgrind', + dest='use_valgrind', + type=bool, + nargs='?', + const=True, + default=use_valgrind, + ) + parser.add_argument( + '--debug', + help='Generate debugging logs', + dest='enable_debug', + type=bool, + nargs='?', + const=True, + default=enable_debug, + ) + parser.add_argument( + '--asan-options', + help='ASAN options', + dest='asan_options', + ) + parser.add_argument( + '--lsan-options', + help='LSAN options', + dest='lsan_options', + ) + parser.add_argument( + '--ubsan-options', + help='UBSAN options', + dest='ubsan_options', + ) + parser.add_argument( + '--with-coverage', + help='Loosen certain sandbox restrictions to make gcov happy', + dest='with_coverage', + type=bool, + nargs='?', + const=True, + default=with_coverage, + ) + parser.add_argument( + '--no-journal', + help='Do not show journal of systemd-networkd on stop', + dest='show_journal', + action='store_false', + ) ns, unknown_args = parser.parse_known_args(namespace=unittest) if ns.build_dir: @@ -9554,16 +10996,18 @@ def test_wwan_ipv4v6_static(self): udevadm_bin = os.path.join(ns.build_dir, 'udevadm') build_dir = ns.build_dir + # fmt: off if ns.source_dir: source_dir = ns.source_dir - assert os.path.exists(os.path.join(source_dir, "meson_options.txt")), f"{source_dir} doesn't appear to be a systemd source tree." - elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../meson_options.txt"))): - source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) + assert os.path.exists(os.path.join(source_dir, 'meson_options.txt')), f"{source_dir} doesn't appear to be a systemd source tree." + elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../meson_options.txt'))): + source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')) else: source_dir = None + # fmt: on if networkd_bin is None or resolved_bin is None or timesyncd_bin is None: - print("networkd tests require networkd/resolved/timesyncd to be enabled") + print('networkd tests require networkd/resolved/timesyncd to be enabled') sys.exit(77) use_valgrind = ns.use_valgrind @@ -9571,7 +11015,7 @@ def test_wwan_ipv4v6_static(self): asan_options = ns.asan_options lsan_options = ns.lsan_options ubsan_options = ns.ubsan_options - with_coverage = ns.with_coverage or "COVERAGE_BUILD_DIR" in os.environ + with_coverage = ns.with_coverage or 'COVERAGE_BUILD_DIR' in os.environ show_journal = ns.show_journal if use_valgrind: @@ -9612,6 +11056,6 @@ def test_wwan_ipv4v6_static(self): argv=[ sys.argv[0], *unknown_args, - *(["-k", match] if (match := os.getenv("TEST_MATCH_TESTCASE")) else []) + *(['-k', match] if (match := os.getenv('TEST_MATCH_TESTCASE')) else []), ], ) From 6e4c7f91f90aa02cacba9ee09a07f1c00b403240 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:02:20 +0900 Subject: [PATCH 1849/2155] test-shutdown: apply "ruff format" --- test/test-shutdown.py | 81 ++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/test/test-shutdown.py b/test/test-shutdown.py index d19a03742c246..1fe96c8828d73 100755 --- a/test/test-shutdown.py +++ b/test/test-shutdown.py @@ -13,27 +13,34 @@ def run(args): ret = 1 - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') logfile = None if args.logfile: - logger.debug("Logging pexpect IOs to %s", args.logfile) + logger.debug('Logging pexpect IOs to %s', args.logfile) logfile = open(args.logfile, 'w') elif args.verbose: logfile = sys.stdout - logger.info("spawning test") - console = pexpect.spawn(args.command, args.arg, logfile=logfile, env={ - "TERM": "dumb", - }, encoding='utf-8', timeout=60) + logger.info('spawning test') + console = pexpect.spawn( + args.command, + args.arg, + logfile=logfile, + env={ + 'TERM': 'dumb', + }, + encoding='utf-8', + timeout=60, + ) - logger.debug("child pid %d", console.pid) + logger.debug('child pid %d', console.pid) try: - logger.info("waiting for login prompt") + logger.info('waiting for login prompt') console.expect('H login: ', 10) - logger.info("log in and start screen") + logger.info('log in and start screen') console.sendline('root') console.expect('bash.*# ', 10) console.sendline('screen') @@ -46,42 +53,42 @@ def run(args): console.sendline('systemctl is-system-running --wait') console.expect(r'\b(running|degraded)\b', 60) -# console.interact() + # console.interact() console.sendline('tty') console.expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") + logger.info('schedule reboot') console.sendline('shutdown -r') console.expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) date = console.match.group('date') - logger.info("reboot scheduled for %s", date) + logger.info('reboot scheduled for %s', date) console.sendcontrol('a') console.send('0') - logger.info("verify broadcast message") + logger.info('verify broadcast message') console.expect(f'Broadcast message from root@H on {pty}', 2) console.expect(f'The system will reboot at {date}', 2) - logger.info("check show output") + logger.info('check show output') console.sendline('shutdown --show') console.expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") + logger.info('cancel shutdown') console.sendline('shutdown -c') console.sendcontrol('a') console.send('1') console.expect('System shutdown has been cancelled', 2) - logger.info("call for reboot") + logger.info('call for reboot') console.sendline('sleep 10; shutdown -r now') console.sendcontrol('a') console.send('0') - console.expect("The system will reboot now!", 12) + console.expect('The system will reboot now!', 12) - logger.info("waiting for reboot") + logger.info('waiting for reboot') console.expect('H login: ', 60) console.sendline('root') @@ -89,16 +96,16 @@ def run(args): console.sendline('> /testok') - logger.info("power off") + logger.info('power off') console.sendline('poweroff') - logger.info("expect termination now") + logger.info('expect termination now') console.expect(pexpect.EOF) ret = 0 except Exception as e: logger.error(e) - logger.info("killing child pid %d", console.pid) + logger.info('killing child pid %d', console.pid) # Ask systemd-nspawn to stop and release the container's resources properly. console.kill(signal.SIGTERM) @@ -116,12 +123,31 @@ def run(args): return ret + def main(): - parser = argparse.ArgumentParser(description='test logind shutdown feature') - parser.add_argument("-v", "--verbose", action="store_true", help="verbose") - parser.add_argument("--logfile", metavar='FILE', help="Save all test input/output to the given path") - parser.add_argument("command", help="command to run") - parser.add_argument("arg", nargs='*', help="args for command") + parser = argparse.ArgumentParser( + description='test logind shutdown feature', + ) + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='verbose', + ) + parser.add_argument( + '--logfile', + metavar='FILE', + help='Save all test input/output to the given path', + ) + parser.add_argument( + 'command', + help='command to run', + ) + parser.add_argument( + 'arg', + nargs='*', + help='args for command', + ) args = parser.parse_args() @@ -134,6 +160,7 @@ def main(): return run(args) + if __name__ == '__main__': sys.exit(main()) From f8a3f9d13506c3aa0dc6a161ddf866203508213e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:02:36 +0900 Subject: [PATCH 1850/2155] test-tmpfiles: apply "ruff format" and "ruff check --fix" This also dropped unnecessary existence check for subprocess.run. --- test/test-systemd-tmpfiles.py | 119 +++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/test/test-systemd-tmpfiles.py b/test/test-systemd-tmpfiles.py index 37a5e9ef3e4b2..03022a9d5dedf 100755 --- a/test/test-systemd-tmpfiles.py +++ b/test/test-systemd-tmpfiles.py @@ -6,13 +6,13 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +import grp import os -import sys +import pwd import socket import subprocess +import sys import tempfile -import pwd -import grp from pathlib import Path try: @@ -20,40 +20,41 @@ except ImportError: id128 = None -EX_DATAERR = 65 # from sysexits.h +EX_DATAERR = 65 # from sysexits.h EXIT_TEST_SKIP = 77 -try: - subprocess.run -except AttributeError: - sys.exit(EXIT_TEST_SKIP) - exe_with_args = sys.argv[1:] temp_dir = tempfile.TemporaryDirectory(prefix='test-systemd-tmpfiles.') # If /tmp isn't owned by either 'root' or the current user # systemd-tmpfiles will exit with "Detected unsafe path transition" # breaking this test -tmpowner = os.stat("/tmp").st_uid +tmpowner = os.stat('/tmp').st_uid if tmpowner != 0 and tmpowner != os.getuid(): print("Skip: /tmp is not owned by 'root' or current user") sys.exit(EXIT_TEST_SKIP) + def test_line(line, *, user, returncode=EX_DATAERR, extra={}): args = ['--user'] if user else [] print('Running {} on {!r}'.format(' '.join(exe_with_args + args), line)) - c = subprocess.run(exe_with_args + ['--create', '-'] + args, - input=line, stdout=subprocess.PIPE, universal_newlines=True, - **extra) + c = subprocess.run( + exe_with_args + ['--create', '-'] + args, + input=line, + stdout=subprocess.PIPE, + text=True, + **extra, + ) assert c.returncode == returncode, c + def test_invalids(*, user): test_line('asdfa', user=user) test_line('f "open quote', user=user) test_line('f closed quote""', user=user) test_line('Y /unknown/letter', user=user) test_line('w non/absolute/path', user=user) - test_line('s', user=user) # s is for short + test_line('s', user=user) # s is for short test_line('f!! /too/many/bangs', user=user) test_line('f++ /too/many/plusses', user=user) test_line('f+!+ /too/many/plusses', user=user) @@ -77,12 +78,18 @@ def test_invalids(*, user): test_line('h - - -', user=user) test_line('H - - -', user=user) + def test_uninitialized_t(): if os.getuid() == 0: return - test_line('w /foo - - - - "specifier for --user %t"', - user=True, returncode=0, extra={'env':{'HOME': os.getenv('HOME')}}) + test_line( + 'w /foo - - - - "specifier for --user %t"', + user=True, + returncode=0, + extra={'env': {'HOME': os.getenv('HOME')}}, + ) + def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None): d = tempfile.TemporaryDirectory(prefix='test-content.', dir=temp_dir.name) @@ -92,24 +99,25 @@ def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None spec = line.format(arg) test_line(spec, user=user, returncode=0, extra=extra) content = open(arg).read() - print('expect: {!r}\nactual: {!r}'.format(expected, content)) + print(f'expect: {expected!r}\nactual: {content!r}') assert content == expected + def test_valid_specifiers(*, user): test_content('f {} - - - - two words', 'two words', user=user) if id128 and os.path.isfile('/etc/machine-id'): try: - test_content('f {} - - - - %m', '{}'.format(id128.get_machine().hex), user=user) + test_content('f {} - - - - %m', f'{id128.get_machine().hex}', user=user) except AssertionError as e: print(e) print('/etc/machine-id: {!r}'.format(open('/etc/machine-id').read())) print('/proc/cmdline: {!r}'.format(open('/proc/cmdline').read())) print('skipping') - test_content('f {} - - - - %b', '{}'.format(id128.get_boot().hex), user=user) - test_content('f {} - - - - %H', '{}'.format(socket.gethostname()), user=user) - test_content('f {} - - - - %v', '{}'.format(os.uname().release), user=user) - test_content('f {} - - - - %U', '{}'.format(os.getuid() if user else 0), user=user) - test_content('f {} - - - - %G', '{}'.format(os.getgid() if user else 0), user=user) + test_content('f {} - - - - %b', f'{id128.get_boot().hex}', user=user) + test_content('f {} - - - - %H', f'{socket.gethostname()}', user=user) + test_content('f {} - - - - %v', f'{os.uname().release}', user=user) + test_content('f {} - - - - %U', f'{os.getuid() if user else 0}', user=user) + test_content('f {} - - - - %G', f'{os.getgid() if user else 0}', user=user) try: puser = pwd.getpwuid(os.getuid() if user else 0) @@ -117,7 +125,7 @@ def test_valid_specifiers(*, user): puser = None if puser: - test_content('f {} - - - - %u', '{}'.format(puser.pw_name), user=user) + test_content('f {} - - - - %u', f'{puser.pw_name}', user=user) try: pgroup = grp.getgrgid(os.getgid() if user else 0) @@ -125,52 +133,55 @@ def test_valid_specifiers(*, user): pgroup = None if pgroup: - test_content('f {} - - - - %g', '{}'.format(pgroup.gr_name), user=user) + test_content('f {} - - - - %g', f'{pgroup.gr_name}', user=user) # Note that %h is the only specifier in which we look the environment, # because we check $HOME. Should we even be doing that? - home = os.path.expanduser("~") - test_content('f {} - - - - %h', '{}'.format(home), user=user) + home = os.path.expanduser('~') + test_content('f {} - - - - %h', f'{home}', user=user) xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') if xdg_runtime_dir is not None or not user: test_content('f {} - - - - %t', xdg_runtime_dir if user else '/run', - user=user) + user=user) # fmt: skip xdg_state_home = os.getenv('XDG_STATE_HOME') if xdg_state_home is None and user: - xdg_state_home = os.path.join(home, ".local/state") + xdg_state_home = os.path.join(home, '.local/state') test_content('f {} - - - - %S', xdg_state_home if user else '/var/lib', - user=user) + user=user) # fmt: skip xdg_cache_home = os.getenv('XDG_CACHE_HOME') if xdg_cache_home is None and user: - xdg_cache_home = os.path.join(home, ".cache") + xdg_cache_home = os.path.join(home, '.cache') test_content('f {} - - - - %C', xdg_cache_home if user else '/var/cache', - user=user) + user=user) # fmt: skip test_content('f {} - - - - %L', os.path.join(xdg_state_home, 'log') if user else '/var/log', - user=user) + user=user) # fmt: skip test_content('f {} - - - - %%', '%', user=user) + def mkfifo(parent, subpath): os.makedirs(parent, mode=0o755, exist_ok=True) first_component = subpath.split('/')[1] path = parent + '/' + first_component - print('path: {}'.format(path)) + print(f'path: {path}') os.mkfifo(path) + def mkdir(parent, subpath): first_component = subpath.split('/')[1] path = parent + '/' + first_component os.makedirs(path, mode=0o755, exist_ok=True) os.symlink(path, path + '/self', target_is_directory=True) + def symlink(parent, subpath): link_path = parent + '/link-target' os.makedirs(parent, mode=0o755, exist_ok=True) @@ -180,6 +191,7 @@ def symlink(parent, subpath): path = parent + '/' + first_component os.symlink(link_path, path, target_is_directory=True) + def file(parent, subpath): content = 'file-' + subpath.split('/')[1] path = parent + subpath @@ -187,6 +199,7 @@ def file(parent, subpath): with open(path, 'wb') as f: f.write(content.encode()) + def valid_symlink(parent, subpath): target = 'link-target' link_path = parent + target @@ -195,6 +208,7 @@ def valid_symlink(parent, subpath): path = parent + '/' + first_component os.symlink(target, path, target_is_directory=True) + def test_hard_cleanup(*, user): type_cbs = [None, file, mkdir, symlink] if 'mkfifo' in dir(os): @@ -209,29 +223,44 @@ def test_hard_cleanup(*, user): label = 'valid_symlink-deep' test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink) + def test_base64(): - test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', "Piff\nPaff\nPuff \n", user=False) + test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', 'Piff\nPaff\nPuff \n', user=False) + def test_conditionalized_execute_bit(): - c = subprocess.run(exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], shell=True, stdout=subprocess.DEVNULL) + c = subprocess.run( + exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], + shell=True, + stdout=subprocess.DEVNULL, + ) if c.returncode != 0: return 0 d = tempfile.TemporaryDirectory(prefix='test-acl.', dir=temp_dir.name) - temp = Path(d.name) / "cond_exec" + temp = Path(d.name) / 'cond_exec' temp.touch() temp.chmod(0o644) - test_line(f"a {temp} - - - - u:root:Xwr", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rw-" in c.stdout + test_line(f'a {temp} - - - - u:root:Xwr', user=False, returncode=0) + c = subprocess.run( + ['getfacl', '-Ec', temp], + stdout=subprocess.PIPE, + check=True, + text=True, + ) + assert 'user:root:rw-' in c.stdout temp.chmod(0o755) - test_line(f"a+ {temp} - - - - u:root:Xwr,g:root:rX", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rwx" in c.stdout and "group:root:r-x" in c.stdout + test_line(f'a+ {temp} - - - - u:root:Xwr,g:root:rX', user=False, returncode=0) + c = subprocess.run( + ['getfacl', '-Ec', temp], + stdout=subprocess.PIPE, + check=True, + text=True, + ) + assert 'user:root:rwx' in c.stdout and 'group:root:r-x' in c.stdout + if __name__ == '__main__': test_invalids(user=False) From 81b1f3dd9fc11b52ee839b301195ec6b5b2e7eeb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:02:48 +0900 Subject: [PATCH 1851/2155] test-udev: apply "ruff format" and "ruff check --fix" --- test/test-udev.py | 2378 +++++++++++++++++++++++---------------------- 1 file changed, 1194 insertions(+), 1184 deletions(-) diff --git a/test/test-udev.py b/test/test-udev.py index 20b55de7d679d..da5d41efe0179 100755 --- a/test/test-udev.py +++ b/test/test-udev.py @@ -19,8 +19,9 @@ import dataclasses import functools +import grp import os -import pwd, grp +import pwd import re import stat import subprocess @@ -37,11 +38,11 @@ sys.exit(77) -SYS_SCRIPT = Path(__file__).with_name('sys-script.py') +SYS_SCRIPT = Path(__file__).with_name('sys-script.py') try: - UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) + UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) except KeyError: - UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' + UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' UDEV_BIN = UDEV_BIN.absolute() # Those will be set by the udev_setup() fixture @@ -53,11 +54,12 @@ rules_10k_tags = \ '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"' - for i in range(10_000)) + for i in range(10_000)) # fmt: skip rules_10k_tags_continuation = \ ',\\\n'.join(('KERNEL=="sda"', - *(f'TAG+="test{i + 1}"' for i in range(10_000)))) + *(f'TAG+="test{i + 1}"' for i in range(10_000)))) # fmt: skip + @dataclasses.dataclass class Device: @@ -149,8 +151,10 @@ def check_remove(self) -> None: def listify(f): def wrap(*args, **kwargs): return list(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @listify def all_block_devs(exp_func) -> list[Device]: # Create a device list with all block devices under /sys @@ -167,9 +171,7 @@ def all_block_devs(exp_func) -> list[Device]: tgt = tgt[5:] exp, not_exp = exp_func(tgt) - yield Device(devpath=tgt, - exp_links=exp, - not_exp_links=not_exp) + yield Device(devpath=tgt, exp_links=exp, not_exp_links=not_exp) @dataclasses.dataclass @@ -200,71 +202,71 @@ def create_rules_file(self) -> None: UDEV_RULES.parent.mkdir(exist_ok=True, parents=True) UDEV_RULES.write_text(self.rules) + RULES = [ Rules.new( 'no rules', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', ), - rules = r""" + rules=r''' # - """), - + ''', + ), Rules.new( 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi partition", + 'label test of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of pattern match", + 'label test of pattern match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_disk1-4", "boot_disk1-5"], - not_exp_links = ["boot_disk1-1", "boot_disk1-2", "boot_disk1-3", "boot_disk1-6", "boot_disk1-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_disk1-4', 'boot_disk1-5'], + not_exp_links=['boot_disk1-1', 'boot_disk1-2', 'boot_disk1-3', 'boot_disk1-6', 'boot_disk1-7'], ), - - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2" SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n" @@ -277,48 +279,61 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", GOTO="skip-7" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-7" LABEL="skip-7" - """), - + ''', + ), Rules.new( - "label test of multiple sysfs files", + 'label test of multiple sysfs files', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], - not_exp_links = ["boot_diskX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], + not_exp_links=['boot_diskX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of max sysfs files (skip invalid rule)", + 'label test of max sysfs files (skip invalid rule)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_diskXY1"], - not_exp_links = ["boot_diskXX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_diskXY1'], + not_exp_links=['boot_diskXX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n" - """), - - Rules.new( - "SYMLINK tests", - Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["link1", "link2/foo", "link3/aaa/bbb", - "abs1", "abs2/foo", "abs3/aaa/bbb", - "default___replace_test/foo_aaa", - "string_escape___replace/foo_bbb", - "env_with_space", - "default/replace/mode_foo__hoge", - "replace_env_harder_foo__hoge", - "match", "unmatch"], - not_exp_links = ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"], - ), - rules = r""" + ''', + ), + Rules.new( + 'SYMLINK tests', + Device( + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=[ + 'link1', + 'link2/foo', + 'link3/aaa/bbb', + 'abs1', + 'abs2/foo', + 'abs3/aaa/bbb', + 'default___replace_test/foo_aaa', + 'string_escape___replace/foo_bbb', + 'env_with_space', + 'default/replace/mode_foo__hoge', + 'replace_env_harder_foo__hoge', + 'match', + 'unmatch', + ], + not_exp_links=[ + 'removed1', + 'removed2', + 'removed3', + 'unsafe/../../path', + '/nondev/path/will/be/refused', + ], + ), + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2" @@ -336,98 +351,98 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK=="link1", SYMLINK+="match" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK!="removed1", SYMLINK+="unmatch" - """), - + ''', + ), Rules.new( - "catch device by *", + 'catch device by *', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0", "catch-all"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0', 'catch-all'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="modem/%n" KERNEL=="*", SYMLINK+="catch-all" - """), - + ''', + ), Rules.new( - "catch device by * - take 2", + 'catch device by * - take 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="*ACM1", SYMLINK+="bad" KERNEL=="*ACM0", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by ?", + 'catch device by ?', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM??", SYMLINK+="modem/%n-2" KERNEL=="ttyACM?", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by character class", + 'catch device by character class', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2" KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( "don't replace kernel name", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file with whitespace (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment with whitespace before the comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "whitespace only lines (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["whitespace"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['whitespace'], ), - rules = r""" + rules=r''' @@ -436,49 +451,49 @@ def create_rules_file(self) -> None: - """), - + ''', + ), Rules.new( "empty lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "preserve backslashes, if they are not for a newline", + 'preserve backslashes, if they are not for a newline', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["aaa"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['aaa'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \101", RESULT=="A", SYMLINK+="aaa" - """), - + ''', + ), Rules.new( "stupid backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # \ @@ -490,1364 +505,1361 @@ def create_rules_file(self) -> None: KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "subdirectory handling", + 'subdirectory handling', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["sub/direct/ory/modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['sub/direct/ory/modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" - """), - + ''', + ), Rules.new( - "parent device name match of scsi partition", + 'parent device name match of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["first_disk5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['first_disk5'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" - """), - + ''', + ), Rules.new( - "test substitution chars", + 'test substitution chars', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8:minor:5:kernelnumber:5:id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8:minor:5:kernelnumber:5:id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b" - """), - + ''', + ), Rules.new( - "import of shell-value returned from program", + 'import of shell-value returned from program', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node12345678"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node12345678'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e ' TEST_KEY=12345678\n TEST_key2=98765'", SYMLINK+="node$env{TEST_KEY}" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "substitution of sysfs value (%s{file})", + 'substitution of sysfs value (%s{file})', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk-ATA-sda"], - not_exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk-ATA-sda'], + not_exp_links=['modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["special-device-5"], - not_exp_links = ["not"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['special-device-5'], + not_exp_links=['not'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n" - """), - + ''', + ), Rules.new( - "program result substitution (newline removal)", + 'program result substitution (newline removal)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["newline_removed"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['newline_removed'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["test-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['test-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "program with lots of arguments", + 'program with lots of arguments', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program with subshell", + 'program with subshell', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["bar9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['bar9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program arguments combined with apostrophes", + 'program arguments combined with apostrophes', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo7"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo7'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 1", + 'program arguments combined with escaped double quotes, part 1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 2", + 'program arguments combined with escaped double quotes, part 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 3", + 'program arguments combined with escaped double quotes, part 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1", "foo3"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1', 'foo3'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf \"%%s %%s\" \"foo1 foo2\" \"foo3\"| grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "characters before the %c{N} substitution", + 'characters before the %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}" - """), - + ''', + ), Rules.new( - "substitute the second to last argument", + 'substitute the second to last argument', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo8"], - not_exp_links = ["my-foo3", "my-foo4", "my-foo5", "my-foo6", "my-foo7", "my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo8'], + not_exp_links=['my-foo3', 'my-foo4', 'my-foo5', 'my-foo6', 'my-foo7', 'my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}" - """), - + ''', + ), Rules.new( - "test substitution by variable name", + 'test substitution by variable name', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:$major-minor:$minor-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 2", + 'test substitution by variable name 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:$major-minor:%m-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 3", + 'test substitution by variable name 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["850:0:0:05"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['850:0:0:05'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" - """), - + ''', + ), Rules.new( - "test substitution by variable name 4", + 'test substitution by variable name 4', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["855"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['855'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number" - """), - + ''', + ), Rules.new( - "test substitution by variable name 5", + 'test substitution by variable name 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["8550:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['8550:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major%m%n$id" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS for device with no parent", + 'non matching SUBSYSTEMS for device with no parent', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS", + 'non matching SUBSYSTEMS', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "ATTRS match", + 'ATTRS match', Device( - "/devices/virtual/tty/console", - exp_links = ["foo", "TTY"], + '/devices/virtual/tty/console', + exp_links=['foo', 'TTY'], ), - rules = r""" + rules=r''' KERNEL=="console", SYMLINK+="TTY" ATTRS{dev}=="5:1", SYMLINK+="foo" - """), - + ''', + ), Rules.new( - "ATTR (empty file)", + 'ATTR (empty file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["empty", "not-something"], - not_exp_links = ["something", "not-empty"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['empty', 'not-something'], + not_exp_links=['something', 'not-empty'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something" - """), - + ''', + ), Rules.new( - "ATTR (non-existent file)", + 'ATTR (non-existent file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["non-existent", "wrong"], - not_exp_links = ["something", "empty", "not-empty", - "not-something", "something"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['non-existent', 'wrong'], + not_exp_links=['something', 'empty', 'not-empty', 'not-something', 'something'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something" KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent" KERNEL=="sda", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "program and bus type match", + 'program and bus type match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c" SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "sysfs parent hierarchy", + 'sysfs parent hierarchy', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' ATTRS{idProduct}=="007b", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name", + 'name test with ! in the name', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - exp_links = ["is/a/fake/blockdev0"], - not_exp_links = ["is/not/a/fake/blockdev0", "modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + exp_links=['is/a/fake/blockdev0'], + not_exp_links=['is/not/a/fake/blockdev0', 'modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k" SUBSYSTEM=="block", SYMLINK+="is/a/%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name, but no matching rule", + 'name test with ! in the name, but no matching rule', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - not_exp_links = ["modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + not_exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "KERNELS rule", + 'KERNELS rule', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "short-id", "not-scsi"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'short-id', 'not-scsi'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id" SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard all", + 'KERNELS wildcard all', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial", + 'KERNELS wildcard partial', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial 2", + 'KERNELS wildcard partial 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (first match)", + 'substitute attr with link target value (first match)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-sd"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-sd'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (currently selected device)", + 'substitute attr with link target value (currently selected device)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-ahci"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-ahci'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "ignore ATTRS attribute whitespace", + 'ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ignored"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ignored'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" - """), - + ''', + ), Rules.new( - "do not ignore ATTRS attribute whitespace", + 'do not ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["matched-with-space"], - not_exp_links = ["wrong-to-ignore"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['matched-with-space'], + not_exp_links=['wrong-to-ignore'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore" SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space" - """), - + ''', + ), Rules.new( - "permissions USER=bad GROUP=name", + 'permissions USER=bad GROUP=name', Device( - "/devices/virtual/tty/tty33", - exp_perms = "0:0:0600", + '/devices/virtual/tty/tty33', + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="tty33", OWNER="bad", GROUP="name" - """), - + ''', + ), Rules.new( - "permissions OWNER=1", + 'permissions OWNER=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP=1", + 'permissions GROUP=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':1:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1" - """), - + ''', + ), Rules.new( - "textual user id", + 'textual user id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "daemon::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='daemon::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon" - """), - + ''', + ), Rules.new( - "textual group id", + 'textual group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":daemon:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':daemon:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" - """), - + ''', + ), Rules.new( - "textual user/group id", + 'textual user/group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "root:audio:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='root:audio:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio" - """), - + ''', + ), Rules.new( - "permissions MODE=0777", + 'permissions MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "::0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='::0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER=1 GROUP=1 MODE=0777", + 'permissions OWNER=1 GROUP=1 MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER to 1", + 'permissions OWNER to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1::", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1::', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP to 1", + 'permissions GROUP to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms=':1:0660', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1" - """), - + ''', + ), Rules.new( - "permissions MODE to 0060", + 'permissions MODE to 0060', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "::0060", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='::0060', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" - """), - + ''', + ), Rules.new( - "permissions OWNER, GROUP, MODE", + 'permissions OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions only rule", + 'permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "multiple permissions only rule", + 'multiple permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "permissions only rule with override at SYMLINK+ rule", + 'permissions only rule with override at SYMLINK+ rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:2:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:2:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="2" - """), - + ''', + ), Rules.new( - "major/minor number test", + 'major/minor number test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_major_minor = "8:0", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_major_minor='8:0', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major number test", + 'big major number test', Device( - "/devices/virtual/misc/misc-fake1", - exp_links = ["node"], - exp_major_minor = "4095:1", + '/devices/virtual/misc/misc-fake1', + exp_links=['node'], + exp_major_minor='4095:1', ), - rules = r""" + rules=r''' KERNEL=="misc-fake1", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major and big minor number test", + 'big major and big minor number test', Device( - "/devices/virtual/misc/misc-fake89999", - exp_links = ["node"], - exp_major_minor = "4095:89999", + '/devices/virtual/misc/misc-fake89999', + exp_links=['node'], + exp_major_minor='4095:89999', ), - rules = r""" + rules=r''' KERNEL=="misc-fake89999", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "multiple symlinks with format char", + 'multiple symlinks with format char', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink1-0", "symlink2-ttyACM0", "symlink3-"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink1-0', 'symlink2-ttyACM0', 'symlink3-'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b" - """), - + ''', + ), Rules.new( - "multiple symlinks with a lot of s p a c e s", + 'multiple symlinks with a lot of s p a c e s', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["one", "two"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['one', 'two'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " - """), - + ''', + ), Rules.new( - "symlink with spaces in substituted variable", + 'symlink with spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with leading space in substituted variable", + 'symlink with leading space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with trailing space in substituted variable", + 'symlink with trailing space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with lots of space in substituted variable", + 'symlink with lots of space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with multiple spaces in substituted variable", + 'symlink with multiple spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with space and var with space", + 'symlink with space and var with space', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-one_two_three-end", - "another_symlink", "a", "b", "c"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-one_two_three-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK=" first name-$env{WITH_WS}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink with env which contain slash (see #19309)", + 'symlink with env which contain slash (see #19309)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-aaa_bbb_ccc-end", - "another_symlink", "a", "b", "c"], - not_exp_links = ["ame-aaa/bbb/ccc-end"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-aaa_bbb_ccc-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=['ame-aaa/bbb/ccc-end'], ), - rules = r""" + rules=r''' ENV{WITH_SLASH}="aaa/bbb/ccc" OPTIONS="string_escape=replace", ENV{REPLACED}="$env{WITH_SLASH}" SYMLINK=" first name-$env{REPLACED}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink creation (same directory)", + 'symlink creation (same directory)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" - """), - + ''', + ), Rules.new( - "multiple symlinks", + 'multiple symlinks', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first-0", "second-0", "third-0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first-0', 'second-0', 'third-0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n" - """), - + ''', + ), Rules.new( "symlink name '.'", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." - """), - + ''', + ), Rules.new( - "symlink node to itself", + 'symlink node to itself', Device( - "/devices/virtual/tty/tty0", + '/devices/virtual/tty/tty0', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' KERNEL=="tty0", SYMLINK+="tty0" - """), - + ''', + ), Rules.new( - "symlink %n substitution", + 'symlink %n substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" - """), - + ''', + ), Rules.new( - "symlink %k substitution", + 'symlink %k substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink-ttyACM0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink-ttyACM0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" - """), - + ''', + ), Rules.new( - "symlink %M:%m substitution", + 'symlink %M:%m substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["major-166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['major-166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" - """), - + ''', + ), Rules.new( - "symlink %b substitution", + 'symlink %b substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["symlink-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['symlink-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" - """), - + ''', + ), Rules.new( - "symlink %c substitution", + 'symlink %c substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "symlink %c{N} substitution", + 'symlink %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], - not_exp_links = ["symlink", "this"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], + not_exp_links=['symlink', 'this'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "symlink %c{N+} substitution", + 'symlink %c{N+} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink only rule with %c{N+}", + 'symlink only rule with %c{N+}', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink %s{filename} substitution", + 'symlink %s{filename} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of)", + 'program result substitution (numbered part of)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of+)", + 'program result substitution (numbered part of+)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2", "link3", "link4"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2', 'link3', 'link4'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "SUBSYSTEM match test", + 'SUBSYSTEM match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match", "should_not_match2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match', 'should_not_match2'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc" - """), - + ''', + ), Rules.new( - "DRIVERS match test", + 'DRIVERS match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match"] + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd" - """), - + ''', + ), Rules.new( - "devnode substitution test", + 'devnode substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" - """), - + ''', + ), Rules.new( - "parent node name substitution test", + 'parent node name substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sda-part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sda-part-1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n" - """), - + ''', + ), Rules.new( - "udev_root substitution", + 'udev_root substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["start-/dev-end"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['start-/dev-end'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" - """), - + ''', + ), Rules.new( # This is not supported any more - "last_rule option", + 'last_rule option', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["last", "very-last"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['last', 'very-last'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" - """), - + ''', + ), Rules.new( - "negation KERNEL!=", + 'negation KERNEL!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["match", "before"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['match', 'before'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match" - """), - + ''', + ), Rules.new( - "negation SUBSYSTEM!=", + 'negation SUBSYSTEM!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "not-anything"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'not-anything'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything" - """), - + ''', + ), Rules.new( - "negation PROGRAM!= exit code", + 'negation PROGRAM!= exit code', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "nonzero-program"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'nonzero-program'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong", "no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong', 'no'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test (assign)", + 'ENV{} test (assign)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign 2 times)", + 'ENV{} test (assign 2 times)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no", "bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no', 'bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-$env{ASSIGN}" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="bad" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign2)", + 'ENV{} test (assign2)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part"], - not_exp_links = ["disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part'], + not_exp_links=['disk'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk"], - not_exp_links = ["part"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk'], + not_exp_links=['part'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false" SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true" ENV{MAINDEVICE}=="true", SYMLINK+="disk" SUBSYSTEM=="block", SYMLINK+="before" ENV{PARTITION}=="true", SYMLINK+="part" - """), - + ''', + ), Rules.new( - "untrusted string sanitize", + 'untrusted string sanitize', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sane"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sane'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane" - """), - + ''', + ), Rules.new( "untrusted string sanitize (don't replace utf8)", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["uber"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['uber'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber" - """), - + ''', + ), Rules.new( - "untrusted string sanitize (replace invalid utf8)", + 'untrusted string sanitize (replace invalid utf8)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["replaced"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['replaced'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" - """), - + ''', + ), Rules.new( - "read sysfs value from parent device", + 'read sysfs value from parent device', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["serial-354172020305000"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['serial-354172020305000'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" - """), - + ''', + ), Rules.new( - "match against empty key string", + 'match against empty key string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["not-1-ok", "not-2-ok", "not-3-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['not-1-ok', 'not-2-ok', 'not-3-ok'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok" KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok" KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok" KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok" - """), - + ''', + ), Rules.new( - "check ACTION value", + 'check ACTION value', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["unknown-not-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['unknown-not-ok'], ), - rules = r""" + rules=r''' ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" ACTION=="add", KERNEL=="sda", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment", + 'final assignment', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment 2", + 'final assignment 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" SUBSYSTEM=="block", MODE:="640" KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "env substitution", + 'env substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node-add-me"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node-add-me'], ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me" - """), - + ''', + ), Rules.new( - "reset list to current value", + 'reset list to current value', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["three"], - not_exp_links = ["two", "one"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['three'], + not_exp_links=['two', 'one'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="one" KERNEL=="ttyACM[0-9]*", SYMLINK+="two" KERNEL=="ttyACM[0-9]*", SYMLINK="three" - """), - + ''', + ), Rules.new( - "test empty SYMLINK+ (empty override)", + 'test empty SYMLINK+ (empty override)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" KERNEL=="ttyACM[0-9]*", SYMLINK="" KERNEL=="ttyACM[0-9]*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches", + 'test multi matches', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="ttyACM*|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 2", + 'test multi matches 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], - not_exp_links = ["nomatch"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], + not_exp_links=['nomatch'], ), - rules = r""" + rules=r''' KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 3", + 'test multi matches 3', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 4", + 'test multi matches 4', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2", "wrong3"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2', 'wrong3'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right" KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3" - """), - + ''', + ), Rules.new( - "test multi matches 5", + 'test multi matches 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="|foo", SYMLINK+="found" TAGS=="|aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 6", + 'test multi matches 6', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="|foo", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 7", + 'test multi matches 7', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo||bar", SYMLINK+="found" TAGS=="aaa||bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 8", + 'test multi matches 8', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo||bar", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 9", + 'test multi matches 9', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found", "found2"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found', 'found2'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" KERNEL=="sda", TAGS!="hoge", SYMLINK+="found2" KERNEL=="sda", TAGS!="foo", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "test multi matches 10", + 'test multi matches 10', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo|", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 11", + 'test multi matches 11', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="c" TAGS=="foo||bar||c", SYMLINK+="found" TAGS=="aaa||bbb||ccc", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "TAG refuses invalid string", + 'TAG refuses invalid string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["valid", "found"], - not_exp_links = ["empty", "invalid_char", "path", "bad", "bad2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['valid', 'found'], + not_exp_links=['empty', 'invalid_char', 'path', 'bad', 'bad2'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid" TAGS=="", SYMLINK+="empty" TAGS=="invalid.char", SYMLINK+="invalid_char" @@ -1856,33 +1868,33 @@ def create_rules_file(self) -> None: TAGS=="valid|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" TAGS=="aaa|bbb", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "IMPORT parent test", + 'IMPORT parent test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["parent"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['parent'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["parentenv-parent_right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['parentenv-parent_right'], ), - delay = 500000, # Serialized! We need to sleep here after adding sda - rules = r""" + delay=500000, # Serialized! We need to sleep here after adding sda + rules=r''' KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-$env{PARENT_KEY}$env{WRONG_PARENT_KEY}" KERNEL=="sda", IMPORT{program}="/bin/echo -e 'PARENT_KEY=parent_right\nWRONG_PARENT_KEY=parent_wrong'" KERNEL=="sda", SYMLINK+="parent" - """), - + ''', + ), Rules.new( - "GOTO test", + 'GOTO test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], - not_exp_links = ["wrong", "wrong2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], + not_exp_links=['wrong', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="TEST" KERNEL=="sda1", SYMLINK+="wrong" KERNEL=="sda1", GOTO="BAD" @@ -1890,207 +1902,207 @@ def create_rules_file(self) -> None: KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" LABEL="end" - """), - + ''', + ), Rules.new( - "GOTO label does not exist", + 'GOTO label does not exist', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="does-not-exist" KERNEL=="sda1", SYMLINK+="right", LABEL="exists" - """), - + ''', + ), Rules.new( - "SYMLINK+ compare test", + 'SYMLINK+ compare test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right", "link"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right', 'link'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="sda1", SYMLINK+="link" KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "invalid key operation", + 'invalid key operation', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' KERNEL="sda1", SYMLINK+="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "operator chars in attribute", + 'operator chars in attribute', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "overlong comment line", + 'overlong comment line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 KERNEL=="sda1", SYMLINK+=="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "magic subsys/kernel lookup", + 'magic subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["00:16:41:e2:8d:ff"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['00:16:41:e2:8d:ff'], ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}" - """), - + ''', + ), Rules.new( - "TEST absolute path", + 'TEST absolute path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["there"], - not_exp_links = ["notthere"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['there'], + not_exp_links=['notthere'], ), - rules = r""" + rules=r''' TEST=="/etc/passwd", SYMLINK+="there" TEST!="/etc/passwd", SYMLINK+="notthere" - """), - + ''', + ), Rules.new( - "TEST subsys/kernel lookup", + 'TEST subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "TEST relative path", + 'TEST relative path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["relative"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['relative'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="size", SYMLINK+="relative" - """), - + ''', + ), Rules.new( - "TEST wildcard substitution (find queue/nr_requests)", + 'TEST wildcard substitution (find queue/nr_requests)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found-subdir"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found-subdir'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" - """), - + ''', + ), Rules.new( - "TEST MODE=0000", + 'TEST MODE=0000', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0000", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0000', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0000" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds OWNER, GROUP, MODE", + 'TEST PROGRAM feeds OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "1:1:0400", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='1:1:0400', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="666" KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds MODE with overflow", + 'TEST PROGRAM feeds MODE with overflow', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0440", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0440', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="440" KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "magic [subsys/sysname] attribute substitution", + 'magic [subsys/sysname] attribute substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["sda-8741C4G-end"], - exp_perms = "0:0:0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['sda-8741C4G-end'], + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" - """), - + ''', + ), Rules.new( - "builtin path_id", + 'builtin path_id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0'], ), - rules = r""" + rules=r''' KERNEL=="sda", IMPORT{builtin}="path_id" KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}" - """), - + ''', + ), Rules.new( - "add and match tag", + 'add and match tag', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green" TAGS=="green", SYMLINK+="found" TAGS=="blue", SYMLINK+="bad" - """), - + ''', + ), Rules.new( "don't crash with lots of tags", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], ), - rules = f""" + rules=f''' {rules_10k_tags} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations", + 'continuations', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = f""" + rules=f''' {rules_10k_tags_continuation} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad" KERNEL=="sda",\\ @@ -2104,16 +2116,16 @@ def create_rules_file(self) -> None: \\ TAG+="hoge4" TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations with empty line", + 'continuations with empty line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' # empty line finishes continuation KERNEL=="sda", TAG+="foo" \ @@ -2122,16 +2134,16 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "continuations with space only line", + 'continuations with space only line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = """ + rules=''' # space only line finishes continuation KERNEL=="sda", TAG+="foo" \\ \t @@ -2140,192 +2152,194 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "multiple devices", + 'multiple devices', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio", + 'multiple devices, same link name, positive prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - repeat = 100, - rules = r""" + repeat=100, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, negative prio", + 'multiple devices, same link name, negative prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL!="*7", OPTIONS+="link_priority=-10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio, sleep", + 'multiple devices, same link name, positive prio, sleep', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - delay = 10000, - rules = r""" + delay=10000, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( 'all_block_devs', - device_generator = lambda: \ - all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)), - repeat = 10, - rules = r""" + device_generator=lambda: all_block_devs( + lambda name: (['blockdev'], None) if name.endswith('/sda6') else (None, None) + ), + repeat=10, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev" KERNEL=="sda6", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "case insensitive match", + 'case insensitive match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['ok'], ), - - rules = r""" + rules=r''' KERNEL==i"SDA1", SUBSYSTEMS==i"SCSI", ATTRS{vendor}==i"a?a", SYMLINK+="ok" - """), + ''', + ), ] + def fork_and_run_udev(action: str, rules: Rules) -> None: kinder = [] for k, device in enumerate(rules.devices): @@ -2351,13 +2365,11 @@ def environment_issue(): if os.getuid() != 0: return 'Must be root to run properly' - c = subprocess.run(['systemd-detect-virt', '-r', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-r', '-q'], check=False) if c.returncode == 0: return 'Running in a chroot, skipping the test' - c = subprocess.run(['systemd-detect-virt', '-c', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-c', '-q'], check=False) if c.returncode == 0: return 'Running in a container, skipping the test' @@ -2375,27 +2387,24 @@ def udev_setup(): _tmpdir = tempfile.TemporaryDirectory() tmpdir = Path(_tmpdir.name) - UDEV_RUN = tmpdir / 'run' + UDEV_RUN = tmpdir / 'run' UDEV_RULES = UDEV_RUN / 'udev-test.rules' udev_tmpfs = tmpdir / 'tmpfs' - UDEV_DEV = udev_tmpfs / 'dev' - UDEV_SYS = udev_tmpfs / 'sys' + UDEV_DEV = udev_tmpfs / 'dev' + UDEV_SYS = udev_tmpfs / 'sys' - subprocess.run(['umount', udev_tmpfs], - stderr=subprocess.DEVNULL, - check=False) + subprocess.run(['umount', udev_tmpfs], stderr=subprocess.DEVNULL, check=False) udev_tmpfs.mkdir(exist_ok=True, parents=True) - subprocess.check_call(['mount', '-v', - '-t', 'tmpfs', - '-o', 'rw,mode=0755,nosuid,noexec', - 'tmpfs', udev_tmpfs]) + subprocess.check_call( + ['mount', '-v', '-t', 'tmpfs', '-o', 'rw,mode=0755,nosuid,noexec', 'tmpfs', udev_tmpfs] + ) UDEV_DEV.mkdir(exist_ok=True) # setting group and mode of udev_dev ensures the tests work # even if the parent directory has setgid bit enabled. - os.chmod(UDEV_DEV,0o755) + os.chmod(UDEV_DEV, 0o755) os.chown(UDEV_DEV, 0, 0) os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3)) @@ -2411,10 +2420,10 @@ def udev_setup(): os.chdir(tmpdir) - if subprocess.run([UDEV_BIN, 'check'], - check=False).returncode != 0: - pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test', - allow_module_level=True) + if subprocess.run([UDEV_BIN, 'check'], check=False).returncode != 0: + pytest.skip( + f'{UDEV_BIN} failed to set up the environment, skipping the test', allow_module_level=True + ) yield @@ -2423,7 +2432,7 @@ def udev_setup(): udev_tmpfs.rmdir() -@pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES)) +@pytest.mark.parametrize('rules', RULES, ids=(rule.desc for rule in RULES)) def test_udev(rules: Rules, udev_setup): assert udev_setup is None @@ -2441,6 +2450,7 @@ def test_udev(rules: Rules, udev_setup): for device in rules.devices: device.check_remove() + if __name__ == '__main__': issue = environment_issue() if issue: From b27bd1a39941e00ed1b6e24bbca9dc9d07faac46 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:03:17 +0900 Subject: [PATCH 1852/2155] TEST-69-SHUTDOWN: apply "ruff format" --- test/units/TEST-69-SHUTDOWN.py | 54 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/test/units/TEST-69-SHUTDOWN.py b/test/units/TEST-69-SHUTDOWN.py index 51569bf51ef34..a208e42f07847 100755 --- a/test/units/TEST-69-SHUTDOWN.py +++ b/test/units/TEST-69-SHUTDOWN.py @@ -10,56 +10,58 @@ def main(): # TODO: drop once https://bugs.debian.org/1075733 is fixed - with open("/usr/lib/os-release") as f: + with open('/usr/lib/os-release') as f: for line in f: - if line.startswith("ID="): - if "debian" in line or "ubuntu" in line: + if line.startswith('ID='): + if 'debian' in line or 'ubuntu' in line: sys.exit(77) - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') consoles = [] for _ in range(2): # Use script to allocate a separate pseudo tty to run the login shell in. console = pexpect.spawn( - "script", ["--quiet", "--return", "--flush", "--command", "login -f root", "/dev/null"], + 'script', + ['--quiet', '--return', '--flush', '--command', 'login -f root', '/dev/null'], logfile=sys.stdout, - env={"TERM": "dumb"}, - encoding="utf-8", + env={'TERM': 'dumb'}, + encoding='utf-8', timeout=60, ) - logger.info("waiting for login prompt") - console.expect(".*# ", 10) + logger.info('waiting for login prompt') + console.expect('.*# ', 10) consoles += [console] - consoles[1].sendline("tty") - consoles[1].expect(r"/dev/(pts/\d+)") + consoles[1].sendline('tty') + consoles[1].expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") - consoles[1].sendline("shutdown -r") + logger.info('schedule reboot') + consoles[1].sendline('shutdown -r') consoles[1].expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) - date = consoles[1].match.group("date") - logger.info("reboot scheduled for %s", date) + date = consoles[1].match.group('date') + logger.info('reboot scheduled for %s', date) - logger.info("verify broadcast message") - consoles[0].expect(f"Broadcast message from root@H on {pty}", 2) - consoles[0].expect(f"The system will reboot at {date}!", 2) + logger.info('verify broadcast message') + consoles[0].expect(f'Broadcast message from root@H on {pty}', 2) + consoles[0].expect(f'The system will reboot at {date}!', 2) - logger.info("check show output") - consoles[1].sendline("shutdown --show") + logger.info('check show output') + consoles[1].sendline('shutdown --show') consoles[1].expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") - consoles[1].sendline("shutdown -c") - consoles[0].expect("System shutdown has been cancelled", 2) + logger.info('cancel shutdown') + consoles[1].sendline('shutdown -c') + consoles[0].expect('System shutdown has been cancelled', 2) - consoles[0].sendline("> /testok") + consoles[0].sendline('> /testok') -if __name__ == "__main__": + +if __name__ == '__main__': main() # vim: sw=4 et From ce9b618e4675ca185ca68e89c164d3acd0a64864 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:03:44 +0900 Subject: [PATCH 1853/2155] analyze-dump-sort: apply "ruff format" --- tools/analyze-dump-sort.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/analyze-dump-sort.py b/tools/analyze-dump-sort.py index a464a14bd1229..da6094f9961e7 100755 --- a/tools/analyze-dump-sort.py +++ b/tools/analyze-dump-sort.py @@ -58,6 +58,7 @@ def sort_dump(sourcefile, destfile=None): destfile.flush() return destfile + def parse_args(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('one') @@ -65,6 +66,7 @@ def parse_args(): p.add_argument('--user', action='store_true') return p.parse_args() + if __name__ == '__main__': opts = parse_args() @@ -73,8 +75,7 @@ def parse_args(): two = sort_dump(open(opts.two)) else: user = ['--user'] if opts.user else [] - two = subprocess.run(['systemd-analyze', 'dump', *user], - capture_output=True, text=True, check=True) + two = subprocess.run(['systemd-analyze', 'dump', *user], capture_output=True, text=True, check=True) two = sort_dump(two.stdout.splitlines()) with subprocess.Popen(['diff', '-U10', one.name, two.name], stdout=subprocess.PIPE) as diff: subprocess.Popen(['less'], stdin=diff.stdout) From 0965c5d71974ca2e0838ac44fbde62eaa115d617 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:03:56 +0900 Subject: [PATCH 1854/2155] catalog-report: apply "ruff format" --- tools/catalog-report.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/catalog-report.py b/tools/catalog-report.py index 060b1aae869e1..f7e67c2fb4cb5 100755 --- a/tools/catalog-report.py +++ b/tools/catalog-report.py @@ -36,9 +36,13 @@ def log_entry(entry): if 'CODE_FILE' in entry: # some of our code was using 'CODE_FUNCTION' instead of 'CODE_FUNC' - print('{}:{} {}'.format(entry.get('CODE_FILE', '???'), - entry.get('CODE_LINE', '???'), - entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'))) + print( + '{}:{} {}'.format( + entry.get('CODE_FILE'), + entry.get('CODE_LINE', '???'), + entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'), + ) + ) print(' {}'.format(entry.get('MESSAGE', 'no message!'))) for k, v in entry.items(): if k.startswith('CODE_') or k in {'MESSAGE_ID', 'MESSAGE'}: @@ -46,12 +50,13 @@ def log_entry(entry): print(f' {k}={v}') print() + if __name__ == '__main__': j = journal.Reader() logged = set() pattern = re.compile('@[A-Z0-9_]+@') - mids = { v:k for k,v in id128.__dict__.items() if k.startswith('SD_MESSAGE') } + mids = {v: k for k, v in id128.__dict__.items() if k.startswith('SD_MESSAGE')} for i, x in enumerate(j): if i % 1000 == 0: From 53172028c8070c77607a6e2569e0c9aa985c5fcb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:04:20 +0900 Subject: [PATCH 1855/2155] check-efi-alignment: apply "ruff format" --- tools/check-efi-alignment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/check-efi-alignment.py b/tools/check-efi-alignment.py index abdeb22fdbdb3..b442a16f55621 100755 --- a/tools/check-efi-alignment.py +++ b/tools/check-efi-alignment.py @@ -15,22 +15,23 @@ def main(): pe = pefile.PE(sys.argv[1], fast_load=True) for section in pe.sections: - name = section.Name.rstrip(b"\x00").decode() + name = section.Name.rstrip(b'\x00').decode() file_addr = section.PointerToRawData virt_addr = section.VirtualAddress - print(f"{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}") + print(f'{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}') if file_addr % 512 != 0: - print(f"File address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'File address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 if virt_addr % 512 != 0: - print(f"Virt address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'Virt address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 + if __name__ == '__main__': if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} pe-image") + print(f'Usage: {sys.argv[0]} pe-image') sys.exit(1) sys.exit(main()) From a5d54f52d6d36711924c17cfe90e4d75ae30fcd7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:04:33 +0900 Subject: [PATCH 1856/2155] check-version-history: apply "ruff format" and "ruff check --fix" --- tools/check-version-history.py | 83 ++++++++++++++-------------------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/tools/check-version-history.py b/tools/check-version-history.py index db32b6920ab52..39cf127b98df2 100755 --- a/tools/check-version-history.py +++ b/tools/check-version-history.py @@ -20,17 +20,14 @@ def find_undocumented_functions(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - assert pagetree.getroot().tag == "refentry" + assert pagetree.getroot().tag == 'refentry' hist_section = pagetree.find("refsect1[title='History']") - for func in pagetree.findall(".//funcprototype/funcdef/function"): + for func in pagetree.findall('.//funcprototype/funcdef/function'): path = f"./refsynopsisdiv/funcsynopsis/funcprototype/funcdef/function[.='{func.text}']" assert pagetree.findall(path) == [func] - if ( - hist_section is None - or hist_section.find(f"para/function[.='{func.text}()']") is None - ): + if hist_section is None or hist_section.find(f"para/function[.='{func.text}()']") is None: if func.text not in ignorelist: undocumented.append((filename, func.text)) return undocumented @@ -39,22 +36,22 @@ def find_undocumented_functions(pages, ignorelist): def construct_path(element): tag = element.tag - if tag == "refentry": - return "." + if tag == 'refentry': + return '.' - predicate = "" - if tag == "varlistentry": - text = "".join(element.find("term").itertext()) + predicate = '' + if tag == 'varlistentry': + text = ''.join(element.find('term').itertext()) predicate = f'[term="{text}"]' - elif tag.startswith("refsect"): - text = "".join(element.find("title").itertext()) + elif tag.startswith('refsect'): + text = ''.join(element.find('title').itertext()) predicate = f'[title="{text}"]' - elif tag == "variablelist": + elif tag == 'variablelist': varlists = element.getparent().findall(tag) if len(varlists) > 1: - predicate = f"[{varlists.index(element)+1}]" + predicate = f'[{varlists.index(element) + 1}]' - return construct_path(element.getparent()) + "/" + tag + predicate + return construct_path(element.getparent()) + '/' + tag + predicate def find_undocumented_commands(pages, ignorelist): @@ -63,23 +60,21 @@ def find_undocumented_commands(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - if pagetree.getroot().tag != "refentry": + if pagetree.getroot().tag != 'refentry': continue - for varlistentry in pagetree.findall("*//variablelist/varlistentry"): + for varlistentry in pagetree.findall('*//variablelist/varlistentry'): path = construct_path(varlistentry) assert pagetree.findall(path) == [varlistentry] - listitem = varlistentry.find("listitem") + listitem = varlistentry.find('listitem') parent = listitem if listitem is not None else varlistentry rev = parent.getchildren()[-1] - if ( - rev.get("href") != "version-info.xml" and - not path.startswith(tuple(entry[1] for entry in ignorelist if entry[0] == filename)) - ): - undocumented.append((filename, path)) + ignored_files = tuple(entry[1] for entry in ignorelist if entry[0] == filename) + if rev.get('href') != 'version-info.xml' and not path.startswith(ignored_files): + undocumented.append((filename, path)) return undocumented @@ -90,54 +85,44 @@ def process_pages(pages): for page in pages: filename = os.path.basename(page) - if filename.startswith("org.freedesktop."): # dbus + if filename.startswith('org.freedesktop.'): # dbus continue - if ( - filename.startswith("sd_") - or filename.startswith("sd-") - or filename.startswith("udev_") - ): + if filename.startswith('sd_') or filename.startswith('sd-') or filename.startswith('udev_'): function_pages.append(page) continue command_pages.append(page) - undocumented_commands = find_undocumented_commands( - command_pages, command_ignorelist - ) - undocumented_functions = find_undocumented_functions( - function_pages, function_ignorelist - ) + undocumented_commands = find_undocumented_commands(command_pages, command_ignorelist) + undocumented_functions = find_undocumented_functions(function_pages, function_ignorelist) return undocumented_commands, undocumented_functions -if __name__ == "__main__": - with open(os.path.join(os.path.dirname(__file__), "command_ignorelist")) as f: +if __name__ == '__main__': + with open(os.path.join(os.path.dirname(__file__), 'command_ignorelist')) as f: command_ignorelist = [] - for l in f.read().splitlines(): - if l.startswith("#"): + for line in f.read().splitlines(): + if line.startswith('#'): continue - fname, path = l.split(" ", 1) - path = path.replace("\\n", "\n") + fname, path = line.split(' ', 1) + path = path.replace('\\n', '\n') command_ignorelist.append((fname, path)) - with open(os.path.join(os.path.dirname(__file__), "function_ignorelist")) as f: + with open(os.path.join(os.path.dirname(__file__), 'function_ignorelist')) as f: function_ignorelist = f.read().splitlines() undocumented_commands, undocumented_functions = process_pages(sys.argv[1:]) if undocumented_commands or undocumented_functions: for filename, func in undocumented_functions: - print( - f"Function {func}() in {filename} isn't documented in the History section." - ) + print(f"Function {func}() in {filename} isn't documented in the History section.") for filename, path in undocumented_commands: - print(filename, path, "is undocumented") + print(filename, path, 'is undocumented') if undocumented_commands: print( - "Hint: if you reorganized this part of the documentation, " - "please update tools/commands_ignorelist." + 'Hint: if you reorganized this part of the documentation, ' + 'please update tools/commands_ignorelist.' ) sys.exit(1) From 5aa172e2635e176eae063693fbe73bd32bb70811 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:06:56 +0900 Subject: [PATCH 1857/2155] dbus-exporter: apply "ruff format" and "ruff check --fix" --- tools/dbus_exporter.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/dbus_exporter.py b/tools/dbus_exporter.py index 854c7a848bbcb..ce6f8dbf78e8f 100755 --- a/tools/dbus_exporter.py +++ b/tools/dbus_exporter.py @@ -11,14 +11,15 @@ def extract_interfaces_xml(output_dir, executable): # as glibc looks at /proc/self/exe when resolving RPATH env = os.environ.copy() if not os.path.exists('/proc/self'): - env["LD_ORIGIN_PATH"] = executable.parent.as_posix() + env['LD_ORIGIN_PATH'] = executable.parent.as_posix() proc = run( args=[executable.absolute(), '--bus-introspect', 'list'], stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_names = (x.split()[1] for x in proc.stdout.splitlines()) @@ -28,19 +29,25 @@ def extract_interfaces_xml(output_dir, executable): stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_file_name = output_dir / (interface_name + '.xml') interface_file_name.write_text(proc.stdout) interface_file_name.chmod(0o644) + def main(): parser = ArgumentParser() - parser.add_argument('output', - type=Path) - parser.add_argument('executables', - nargs='+', - type=Path) + parser.add_argument( + 'output', + type=Path, + ) + parser.add_argument( + 'executables', + nargs='+', + type=Path, + ) args = parser.parse_args() @@ -50,5 +57,6 @@ def main(): for exe in args.executables: extract_interfaces_xml(args.output, exe) + if __name__ == '__main__': main() From 7e62c9be6fa379d6b7613ef8f9fe3f157d50d263 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:07:28 +0900 Subject: [PATCH 1858/2155] dump-auxv: apply "ruff format" --- tools/dump-auxv.py | 54 +++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/tools/dump-auxv.py b/tools/dump-auxv.py index 1abacda9c1fab..1eb93b0125137 100755 --- a/tools/dump-auxv.py +++ b/tools/dump-auxv.py @@ -85,24 +85,43 @@ 'AT_L3_CACHEGEOMETRY' : 47, 'AT_MINSIGSTKSZ' : 51, # Stack needed for signal delivery -} -AT_AUXV_NAMES = {v:k for k,v in AT_AUXV.items()} +} # fmt: skip +AT_AUXV_NAMES = {v: k for k, v in AT_AUXV.items()} + @click.command(help=__doc__) -@click.option('-b', '--big-endian', 'endian', - flag_value='>', - help='Input is big-endian') -@click.option('-l', '--little-endian', 'endian', - flag_value='<', - help='Input is little-endian') -@click.option('-3', '--32', 'field_width', - flag_value=32, - help='Input is 32-bit') -@click.option('-6', '--64', 'field_width', - flag_value=64, - help='Input is 64-bit') -@click.argument('file', - type=click.File(mode='rb')) +@click.option( + '-b', + '--big-endian', + 'endian', + flag_value='>', + help='Input is big-endian', +) +@click.option( + '-l', + '--little-endian', + 'endian', + flag_value='<', + help='Input is little-endian', +) +@click.option( + '-3', + '--32', + 'field_width', + flag_value=32, + help='Input is 32-bit', +) +@click.option( + '-6', + '--64', + 'field_width', + flag_value=64, + help='Input is 64-bit', +) +@click.argument( + 'file', + type=click.File(mode='rb'), +) def dump(endian, field_width, file): data = file.read() @@ -111,7 +130,7 @@ def dump(endian, field_width, file): if endian is None: endian = '@' - width = {32:'II', 64:'QQ'}[field_width] + width = {32: 'II', 64: 'QQ'}[field_width] format_str = f'{endian}{width}' print(f'# {format_str=}') @@ -137,5 +156,6 @@ def dump(endian, field_width, file): if not seen_null: print('# array not terminated with AT_NULL') + if __name__ == '__main__': dump() From 0df17e665b4efdb1f73a154d477a20cf806d61d6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:08:46 +0900 Subject: [PATCH 1859/2155] elf2efi: apply "ruff format" and "ruff check --fix" --- tools/elf2efi.py | 473 +++++++++++++++++++++++------------------------ 1 file changed, 236 insertions(+), 237 deletions(-) diff --git a/tools/elf2efi.py b/tools/elf2efi.py index ed705beb3169f..1442d1a723616 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -31,12 +31,12 @@ import time import typing from ctypes import ( + LittleEndianStructure, c_char, c_uint8, c_uint16, c_uint32, c_uint64, - LittleEndianStructure, sizeof, ) @@ -46,28 +46,28 @@ class PeCoffHeader(LittleEndianStructure): _fields_ = ( - ("Machine", c_uint16), - ("NumberOfSections", c_uint16), - ("TimeDateStamp", c_uint32), - ("PointerToSymbolTable", c_uint32), - ("NumberOfSymbols", c_uint32), - ("SizeOfOptionalHeader", c_uint16), - ("Characteristics", c_uint16), - ) + ('Machine', c_uint16), + ('NumberOfSections', c_uint16), + ('TimeDateStamp', c_uint32), + ('PointerToSymbolTable', c_uint32), + ('NumberOfSymbols', c_uint32), + ('SizeOfOptionalHeader', c_uint16), + ('Characteristics', c_uint16), + ) # fmt: skip class PeDataDirectory(LittleEndianStructure): _fields_ = ( - ("VirtualAddress", c_uint32), - ("Size", c_uint32), - ) + ('VirtualAddress', c_uint32), + ('Size', c_uint32), + ) # fmt: skip class PeRelocationBlock(LittleEndianStructure): _fields_ = ( - ("PageRVA", c_uint32), - ("BlockSize", c_uint32), - ) + ('PageRVA', c_uint32), + ('BlockSize', c_uint32), + ) # fmt: skip def __init__(self, PageRVA: int): super().__init__(PageRVA) @@ -76,64 +76,64 @@ def __init__(self, PageRVA: int): class PeRelocationEntry(LittleEndianStructure): _fields_ = ( - ("Offset", c_uint16, 12), - ("Type", c_uint16, 4), - ) + ('Offset', c_uint16, 12), + ('Type', c_uint16, 4), + ) # fmt: skip class PeOptionalHeaderStart(LittleEndianStructure): _fields_ = ( - ("Magic", c_uint16), - ("MajorLinkerVersion", c_uint8), - ("MinorLinkerVersion", c_uint8), - ("SizeOfCode", c_uint32), - ("SizeOfInitializedData", c_uint32), - ("SizeOfUninitializedData", c_uint32), - ("AddressOfEntryPoint", c_uint32), - ("BaseOfCode", c_uint32), - ) + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ) # fmt: skip class PeOptionalHeaderMiddle(LittleEndianStructure): _fields_ = ( - ("SectionAlignment", c_uint32), - ("FileAlignment", c_uint32), - ("MajorOperatingSystemVersion", c_uint16), - ("MinorOperatingSystemVersion", c_uint16), - ("MajorImageVersion", c_uint16), - ("MinorImageVersion", c_uint16), - ("MajorSubsystemVersion", c_uint16), - ("MinorSubsystemVersion", c_uint16), - ("Win32VersionValue", c_uint32), - ("SizeOfImage", c_uint32), - ("SizeOfHeaders", c_uint32), - ("CheckSum", c_uint32), - ("Subsystem", c_uint16), - ("DllCharacteristics", c_uint16), - ) + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ) # fmt: skip class PeOptionalHeaderEnd(LittleEndianStructure): _fields_ = ( - ("LoaderFlags", c_uint32), - ("NumberOfRvaAndSizes", c_uint32), - ("ExportTable", PeDataDirectory), - ("ImportTable", PeDataDirectory), - ("ResourceTable", PeDataDirectory), - ("ExceptionTable", PeDataDirectory), - ("CertificateTable", PeDataDirectory), - ("BaseRelocationTable", PeDataDirectory), - ("Debug", PeDataDirectory), - ("Architecture", PeDataDirectory), - ("GlobalPtr", PeDataDirectory), - ("TLSTable", PeDataDirectory), - ("LoadConfigTable", PeDataDirectory), - ("BoundImport", PeDataDirectory), - ("IAT", PeDataDirectory), - ("DelayImportDescriptor", PeDataDirectory), - ("CLRRuntimeHeader", PeDataDirectory), - ("Reserved", PeDataDirectory), - ) + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('ExportTable', PeDataDirectory), + ('ImportTable', PeDataDirectory), + ('ResourceTable', PeDataDirectory), + ('ExceptionTable', PeDataDirectory), + ('CertificateTable', PeDataDirectory), + ('BaseRelocationTable', PeDataDirectory), + ('Debug', PeDataDirectory), + ('Architecture', PeDataDirectory), + ('GlobalPtr', PeDataDirectory), + ('TLSTable', PeDataDirectory), + ('LoadConfigTable', PeDataDirectory), + ('BoundImport', PeDataDirectory), + ('IAT', PeDataDirectory), + ('DelayImportDescriptor', PeDataDirectory), + ('CLRRuntimeHeader', PeDataDirectory), + ('Reserved', PeDataDirectory), + ) # fmt: skip class PeOptionalHeader(LittleEndianStructure): @@ -141,47 +141,47 @@ class PeOptionalHeader(LittleEndianStructure): class PeOptionalHeader32(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("BaseOfData", c_uint32), - ("ImageBase", c_uint32), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint32), - ("SizeOfStackCommit", c_uint32), - ("SizeOfHeapReserve", c_uint32), - ("SizeOfHeapCommit", c_uint32), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint32), + ('SizeOfStackCommit', c_uint32), + ('SizeOfHeapReserve', c_uint32), + ('SizeOfHeapCommit', c_uint32), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeOptionalHeader32Plus(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("ImageBase", c_uint64), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint64), - ("SizeOfStackCommit", c_uint64), - ("SizeOfHeapReserve", c_uint64), - ("SizeOfHeapCommit", c_uint64), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('ImageBase', c_uint64), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint64), + ('SizeOfStackCommit', c_uint64), + ('SizeOfHeapReserve', c_uint64), + ('SizeOfHeapCommit', c_uint64), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeSection(LittleEndianStructure): _fields_ = ( - ("Name", c_char * 8), - ("VirtualSize", c_uint32), - ("VirtualAddress", c_uint32), - ("SizeOfRawData", c_uint32), - ("PointerToRawData", c_uint32), - ("PointerToRelocations", c_uint32), - ("PointerToLinenumbers", c_uint32), - ("NumberOfRelocations", c_uint16), - ("NumberOfLinenumbers", c_uint16), - ("Characteristics", c_uint32), - ) + ('Name', c_char * 8), + ('VirtualSize', c_uint32), + ('VirtualAddress', c_uint32), + ('SizeOfRawData', c_uint32), + ('PointerToRawData', c_uint32), + ('PointerToRelocations', c_uint32), + ('PointerToLinenumbers', c_uint32), + ('NumberOfRelocations', c_uint16), + ('NumberOfLinenumbers', c_uint16), + ('Characteristics', c_uint32), + ) # fmt: skip def __init__(self) -> None: super().__init__() @@ -195,30 +195,32 @@ def __init__(self) -> None: assert sizeof(PeOptionalHeader32) == 224 assert sizeof(PeOptionalHeader32Plus) == 240 +# fmt: off PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ +# fmt: on IGNORE_SECTIONS = [ - ".eh_frame", - ".eh_frame_hdr", - ".ARM.exidx", - ".relro_padding", - ".sframe", + '.eh_frame', + '.eh_frame_hdr', + '.ARM.exidx', + '.relro_padding', + '.sframe', ] IGNORE_SECTION_TYPES = [ - "SHT_DYNAMIC", - "SHT_DYNSYM", - "SHT_GNU_ATTRIBUTES", - "SHT_GNU_HASH", - "SHT_HASH", - "SHT_NOTE", - "SHT_REL", - "SHT_RELA", - "SHT_RELR", - "SHT_STRTAB", - "SHT_SYMTAB", + 'SHT_DYNAMIC', + 'SHT_DYNSYM', + 'SHT_GNU_ATTRIBUTES', + 'SHT_GNU_HASH', + 'SHT_HASH', + 'SHT_NOTE', + 'SHT_REL', + 'SHT_RELA', + 'SHT_RELR', + 'SHT_STRTAB', + 'SHT_SYMTAB', ] # EFI mandates 4KiB memory pages. @@ -227,7 +229,7 @@ def __init__(self) -> None: # Nobody cares about DOS headers, so put the PE header right after. PE_OFFSET = 64 -PE_MAGIC = b"PE\0\0" +PE_MAGIC = b'PE\0\0' def align_to(x: int, align: int) -> int: @@ -239,8 +241,7 @@ def align_down(x: int, align: int) -> int: def next_section_address(sections: list[PeSection]) -> int: - return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, - SECTION_ALIGNMENT) + return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT) class BadSectionError(ValueError): @@ -256,29 +257,30 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: relro = None for elf_seg in file.iter_segments(): - if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: - raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned" - f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})") - if elf_seg["p_type"] == "PT_GNU_RELRO": + if elf_seg['p_type'] == 'PT_LOAD' and elf_seg['p_align'] != SECTION_ALIGNMENT: + raise BadSectionError( + f'ELF segment {elf_seg["p_type"]} is not properly aligned ({elf_seg["p_align"]} != {SECTION_ALIGNMENT})' + ) + if elf_seg['p_type'] == 'PT_GNU_RELRO': relro = elf_seg for elf_s in file.iter_sections(): if ( - elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 - or elf_s["sh_type"] in IGNORE_SECTION_TYPES + elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 + or elf_s['sh_type'] in IGNORE_SECTION_TYPES or elf_s.name in IGNORE_SECTIONS - or elf_s["sh_size"] == 0 + or elf_s['sh_size'] == 0 ): continue - if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]: - raise BadSectionError(f"Unknown section {elf_s.name} with type {elf_s['sh_type']}") + if elf_s['sh_type'] not in ['SHT_PROGBITS', 'SHT_NOBITS']: + raise BadSectionError(f'Unknown section {elf_s.name} with type {elf_s["sh_type"]}') if elf_s.name == '.got': # FIXME: figure out why those sections are inserted - print("WARNING: Non-empty .got section", file=sys.stderr) + print('WARNING: Non-empty .got section', file=sys.stderr) - if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR: + if elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_EXECINSTR: rwx = PE_CHARACTERISTICS_RX - elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE: + elif elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_WRITE: rwx = PE_CHARACTERISTICS_RW else: rwx = PE_CHARACTERISTICS_R @@ -293,11 +295,11 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: if pe_s: # Insert padding to properly align the section. - pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data) + pad_len = elf_s['sh_addr'] - pe_s.VirtualAddress - len(pe_s.data) pe_s.data += bytearray(pad_len) + elf_s.data() else: pe_s = PeSection() - pe_s.VirtualAddress = elf_s["sh_addr"] + pe_s.VirtualAddress = elf_s['sh_addr'] pe_s.Characteristics = rwx pe_s.data = elf_s.data() @@ -306,8 +308,8 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: def convert_sections( - file: elffile.ELFFile, - opt: PeOptionalHeader, + file: elffile.ELFFile, + opt: PeOptionalHeader, ) -> list[PeSection]: last_vma = (0, 0) sections = [] @@ -324,24 +326,25 @@ def convert_sections( pe_s.VirtualSize = len(pe_s.data) pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT) pe_s.Name = { - PE_CHARACTERISTICS_RX: b".text", - PE_CHARACTERISTICS_RW: b".data", - PE_CHARACTERISTICS_R: b".rodata", + PE_CHARACTERISTICS_RX: b'.text', + PE_CHARACTERISTICS_RW: b'.data', + PE_CHARACTERISTICS_R: b'.rodata', }[pe_s.Characteristics] # This can happen if not building with '-z separate-code'. if pe_s.VirtualAddress < sum(last_vma): - raise BadSectionError(f"Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps" - f" previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}") + raise BadSectionError( + f'Section {pe_s.Name.decode()!r} @{pe_s.VirtualAddress:#x} overlaps previous section @{last_vma[0]:#x}+{last_vma[1]:#x}=@{sum(last_vma):#x}' + ) last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize) - if pe_s.Name == b".text": + if pe_s.Name == b'.text': opt.BaseOfCode = pe_s.VirtualAddress opt.SizeOfCode += pe_s.VirtualSize else: opt.SizeOfInitializedData += pe_s.VirtualSize - if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): + if pe_s.Name == b'.data' and isinstance(opt, PeOptionalHeader32): opt.BaseOfData = pe_s.VirtualAddress sections.append(pe_s) @@ -355,14 +358,14 @@ def copy_sections( input_names: str, sections: list[PeSection], ) -> None: - for name in input_names.split(","): + for name in input_names.split(','): elf_s = file.get_section_by_name(name) if not elf_s: continue if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0: - raise BadSectionError(f"ELF section {name} is not aligned") - if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: - raise BadSectionError(f"ELF section {name} is not read-only data") + raise BadSectionError(f'ELF section {name} is not aligned') + if elf_s['sh_flags'] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: # fmt: skip + raise BadSectionError(f'ELF section {name} is not read-only data') pe_s = PeSection() pe_s.Name = name.encode() @@ -381,18 +384,21 @@ def apply_elf_relative_relocation( sections: list[PeSection], addend_size: int, ) -> None: - [target] = [pe_s for pe_s in sections - if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] + [target] = [ + pe_s + for pe_s in sections + if pe_s.VirtualAddress <= reloc['r_offset'] < pe_s.VirtualAddress + len(pe_s.data) + ] - addend_offset = reloc["r_offset"] - target.VirtualAddress + addend_offset = reloc['r_offset'] - target.VirtualAddress if reloc.is_RELA(): - addend = reloc["r_addend"] + addend = reloc['r_addend'] else: addend = target.data[addend_offset : addend_offset + addend_size] - addend = int.from_bytes(addend, byteorder="little") + addend = int.from_bytes(addend, byteorder='little') - value = (image_base + addend).to_bytes(addend_size, byteorder="little") + value = (image_base + addend).to_bytes(addend_size, byteorder='little') target.data[addend_offset : addend_offset + addend_size] = value @@ -404,47 +410,44 @@ def convert_elf_reloc_table( pe_reloc_blocks: dict[int, PeRelocationBlock], ) -> None: NONE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], - "EM_LOONGARCH": 0, - "EM_RISCV": 0, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_NONE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_NONE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_NONE'], + 'EM_LOONGARCH': 0, + 'EM_RISCV': 0, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_NONE'], + }[file['e_machine']] # fmt: skip RELATIVE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], - "EM_LOONGARCH": 3, - "EM_RISCV": 3, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_RELATIVE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_RELATIVE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_RELATIVE'], + 'EM_LOONGARCH': 3, + 'EM_RISCV': 3, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_RELATIVE'], + }[file['e_machine']] # fmt: skip for reloc in elf_reloc_table.iter_relocations(): - if reloc["r_info_type"] == NONE_RELOC: + if reloc['r_info_type'] == NONE_RELOC: continue - if reloc["r_info_type"] == RELATIVE_RELOC: - apply_elf_relative_relocation(reloc, - elf_image_base, - sections, - file.elfclass // 8) + if reloc['r_info_type'] == RELATIVE_RELOC: + apply_elf_relative_relocation(reloc, elf_image_base, sections, file.elfclass // 8) # Now that the ELF relocation has been applied, we can create a PE relocation. - block_rva = reloc["r_offset"] & ~0xFFF + block_rva = reloc['r_offset'] & ~0xFFF if block_rva not in pe_reloc_blocks: pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) entry = PeRelocationEntry() - entry.Offset = reloc["r_offset"] & 0xFFF + entry.Offset = reloc['r_offset'] & 0xFFF # REL_BASED_HIGHLOW or REL_BASED_DIR64 entry.Type = 3 if file.elfclass == 32 else 10 pe_reloc_blocks[block_rva].entries.append(entry) continue - raise BadSectionError(f"Unsupported relocation {reloc}") + raise BadSectionError(f'Unsupported relocation {reloc}') def convert_elf_relocations( @@ -453,27 +456,29 @@ def convert_elf_relocations( sections: list[PeSection], minimum_sections: int, ) -> typing.Optional[PeSection]: - dynamic = file.get_section_by_name(".dynamic") + dynamic = file.get_section_by_name('.dynamic') if dynamic is None: - raise BadSectionError("ELF .dynamic section is missing") + raise BadSectionError('ELF .dynamic section is missing') - [flags_tag] = dynamic.iter_tags("DT_FLAGS_1") - if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]: - raise ValueError("ELF file is not a PIE") + [flags_tag] = dynamic.iter_tags('DT_FLAGS_1') + if not flags_tag['d_val'] & elf.enums.ENUM_DT_FLAGS_1['DF_1_PIE']: + raise ValueError('ELF file is not a PIE') # This checks that the ELF image base is 0. - symtab = file.get_section_by_name(".symtab") + symtab = file.get_section_by_name('.symtab') if symtab: - exe_start = symtab.get_symbol_by_name("__executable_start") - if exe_start and exe_start[0]["st_value"] != 0: - raise ValueError("Unexpected ELF image base") - - opt.SizeOfHeaders = align_to(PE_OFFSET - + len(PE_MAGIC) - + sizeof(PeCoffHeader) - + sizeof(opt) - + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), - FILE_ALIGNMENT) + exe_start = symtab.get_symbol_by_name('__executable_start') + if exe_start and exe_start[0]['st_value'] != 0: + raise ValueError('Unexpected ELF image base') + + opt.SizeOfHeaders = align_to( + PE_OFFSET + + len(PE_MAGIC) + + sizeof(PeCoffHeader) + + sizeof(opt) + + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), + FILE_ALIGNMENT, + ) # We use the basic VMA layout from the ELF image in the PE image. This could cause the first # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed @@ -482,23 +487,18 @@ def convert_elf_relocations( # the ELF portions of the image. segment_offset = 0 if sections[0].VirtualAddress < opt.SizeOfHeaders: - segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, - SECTION_ALIGNMENT) + segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT) - opt.AddressOfEntryPoint = file["e_entry"] + segment_offset + opt.AddressOfEntryPoint = file['e_entry'] + segment_offset opt.BaseOfCode += segment_offset if isinstance(opt, PeOptionalHeader32): opt.BaseOfData += segment_offset pe_reloc_blocks: dict[int, PeRelocationBlock] = {} for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): - if reloc_type not in ["REL", "RELA"]: - raise BadSectionError(f"Unsupported relocation type {reloc_type}") - convert_elf_reloc_table(file, - reloc_table, - opt.ImageBase + segment_offset, - sections, - pe_reloc_blocks) + if reloc_type not in ['REL', 'RELA']: + raise BadSectionError(f'Unsupported relocation type {reloc_type}') + convert_elf_reloc_table(file, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks) for pe_s in sections: pe_s.VirtualAddress += segment_offset @@ -524,7 +524,7 @@ def convert_elf_relocations( data += entry pe_reloc_s = PeSection() - pe_reloc_s.Name = b".reloc" + pe_reloc_s.Name = b'.reloc' pe_reloc_s.data = data pe_reloc_s.VirtualAddress = next_section_address(sections) pe_reloc_s.VirtualSize = len(data) @@ -543,9 +543,9 @@ def write_pe( opt: PeOptionalHeader, sections: list[PeSection], ) -> None: - file.write(b"MZ") + file.write(b'MZ') file.seek(0x3C, io.SEEK_SET) - file.write(PE_OFFSET.to_bytes(2, byteorder="little")) + file.write(PE_OFFSET.to_bytes(2, byteorder='little')) file.seek(PE_OFFSET, io.SEEK_SET) file.write(PE_MAGIC) file.write(coff) @@ -554,8 +554,9 @@ def write_pe( offset = opt.SizeOfHeaders for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): if pe_s.VirtualAddress < opt.SizeOfHeaders: - raise BadSectionError(f"Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps" - " PE headers ending at 0x{opt.SizeOfHeaders:x}") + raise BadSectionError( + f'Section {pe_s.Name} @{pe_s.VirtualAddress:#x} overlaps PE headers ending at {opt.SizeOfHeaders:#x}' + ) pe_s.PointerToRawData = offset file.write(pe_s) @@ -573,20 +574,20 @@ def write_pe( def elf2efi(args: argparse.Namespace) -> None: file = elffile.ELFFile(args.ELF) if not file.little_endian: - raise ValueError("ELF file is not little-endian") - if file["e_type"] not in ["ET_DYN", "ET_EXEC"]: - raise ValueError(f"Unsupported ELF type {file['e_type']}") + raise ValueError('ELF file is not little-endian') + if file['e_type'] not in ['ET_DYN', 'ET_EXEC']: + raise ValueError(f'Unsupported ELF type {file["e_type"]}') pe_arch = { - "EM_386": 0x014C, - "EM_AARCH64": 0xAA64, - "EM_ARM": 0x01C2, - "EM_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264, - "EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064, - "EM_X86_64": 0x8664, - }.get(file["e_machine"]) + 'EM_386': 0x014C, + 'EM_AARCH64': 0xAA64, + 'EM_ARM': 0x01C2, + 'EM_LOONGARCH': 0x6232 if file.elfclass == 32 else 0x6264, + 'EM_RISCV': 0x5032 if file.elfclass == 32 else 0x5064, + 'EM_X86_64': 0x8664, + }.get(file['e_machine']) # fmt: skip if pe_arch is None: - raise ValueError(f"Unsupported ELF architecture {file['e_machine']}") + raise ValueError(f'Unsupported ELF architecture {file["e_machine"]}') coff = PeCoffHeader() opt = PeOptionalHeader32() if file.elfclass == 32 else PeOptionalHeader32Plus() @@ -605,7 +606,7 @@ def elf2efi(args: argparse.Namespace) -> None: coff.Machine = pe_arch coff.NumberOfSections = len(sections) - coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH") or time.time()) + coff.TimeDateStamp = int(os.environ.get('SOURCE_DATE_EPOCH') or time.time()) coff.SizeOfOptionalHeader = sizeof(opt) # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) @@ -632,66 +633,64 @@ def elf2efi(args: argparse.Namespace) -> None: opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES if pe_reloc_s: - opt.BaseRelocationTable = PeDataDirectory( - pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize - ) + opt.BaseRelocationTable = PeDataDirectory(pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize) write_pe(args.PE, coff, opt, sections) def create_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") + parser = argparse.ArgumentParser(description='Convert ELF binaries to PE/EFI') parser.add_argument( - "--version-major", + '--version-major', type=int, default=0, - help="Major image version of EFI image", + help='Major image version of EFI image', ) parser.add_argument( - "--version-minor", + '--version-minor', type=int, default=0, - help="Minor image version of EFI image", + help='Minor image version of EFI image', ) parser.add_argument( - "--efi-major", + '--efi-major', type=int, default=0, - help="Minimum major EFI subsystem version", + help='Minimum major EFI subsystem version', ) parser.add_argument( - "--efi-minor", + '--efi-minor', type=int, default=0, - help="Minimum minor EFI subsystem version", + help='Minimum minor EFI subsystem version', ) parser.add_argument( - "--subsystem", + '--subsystem', type=int, default=10, - help="PE subsystem", + help='PE subsystem', ) parser.add_argument( - "ELF", - type=argparse.FileType("rb"), - help="Input ELF file", + 'ELF', + type=argparse.FileType('rb'), + help='Input ELF file', ) parser.add_argument( - "PE", - type=argparse.FileType("wb"), - help="Output PE/EFI file", + 'PE', + type=argparse.FileType('wb'), + help='Output PE/EFI file', ) parser.add_argument( - "--minimum-sections", + '--minimum-sections', type=int, default=0, - help="Minimum number of sections to leave space for", + help='Minimum number of sections to leave space for', ) parser.add_argument( - "--copy-sections", + '--copy-sections', type=str, - default="", - help="Copy these sections if found", + default='', + help='Copy these sections if found', ) return parser @@ -701,5 +700,5 @@ def main() -> None: elf2efi(parser.parse_args()) -if __name__ == "__main__": +if __name__ == '__main__': main() From 6c2bf5330b0966d219ac967b332fe557095f833c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:09:04 +0900 Subject: [PATCH 1860/2155] fetch-distro: apply "ruff format" --- tools/fetch-distro.py | 75 +++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/tools/fetch-distro.py b/tools/fetch-distro.py index 00b8eca4d1847..a94b80718687b 100755 --- a/tools/fetch-distro.py +++ b/tools/fetch-distro.py @@ -12,6 +12,7 @@ import subprocess from pathlib import Path + def parse_args(): p = argparse.ArgumentParser( description=__doc__, @@ -27,34 +28,35 @@ def parse_args(): default=True, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) p.add_argument('--profile') return p.parse_args() + def read_config(distro: str): cmd = ['mkosi', '--json', '-d', distro, '-f', 'summary'] if args.profile: cmd += ['--profile', args.profile] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') text = subprocess.check_output(cmd, text=True) data = json.loads(text) - images = {image["Image"]: image for image in data["Images"]} - return images["build"] + images = {image['Image']: image for image in data['Images']} + return images['build'] + def commit_file(distro: str, files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update {distro} commit reference to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update {distro} commit reference to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_distro(args, distro: str, config: dict): url = config['Environment']['GIT_URL'] branch = config['Environment']['GIT_BRANCH'] @@ -74,28 +76,30 @@ def checkout_distro(args, distro: str, config: dict): reference = ['--reference-if-able=.'] if distro == 'debian' else [] cmd = [ - 'git', 'clone', url, + 'git', + 'clone', + url, f'--branch={branch}', *sparse, dest.as_posix(), *reference, ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) # Sparse checkout if the package is in a subdirectory if subdir is not None: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', - '--no-cone', f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', '--no-cone', f'{subdir}'] + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'checkout', 'HEAD'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) args.fetch = False # no need to fetch if we just cloned + def update_distro(args, distro: str, config: dict): branch = config['Environment']['GIT_BRANCH'] subdir = config['Environment'].get('GIT_SUBDIR') @@ -103,32 +107,46 @@ def update_distro(args, distro: str, config: dict): pkg_subdir = config['Environment']['PKG_SUBDIR'] if args.fetch: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'fetch', 'origin', '-v', - f'{branch}:remotes/origin/{branch}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'fetch', + 'origin', + '-v', + f'{branch}:remotes/origin/{branch}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'switch', branch] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', - f'refs/remotes/origin/{branch}'] + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', f'refs/remotes/origin/{branch}'] if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'{pkg_subdir}: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() conf_dir = Path('mkosi/mkosi.pkgenv/mkosi.conf.d') @@ -142,8 +160,8 @@ def update_distro(args, distro: str, config: dict): file.write_text(new) tocommit = [file] - if distro == "fedora": - packit = Path(".packit.yml") + if distro == 'fedora': + packit = Path('.packit.yml') s = packit.read_text() assert old_commit in s new = s.replace(old_commit, new_commit) @@ -155,6 +173,7 @@ def update_distro(args, distro: str, config: dict): else: raise ValueError(f'{distro}: hash {new_commit} not found under {conf_dir}') + if __name__ == '__main__': args = parse_args() From 2ab056cb7e27cc48cb94641d3bd1337a050ec52c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:09:19 +0900 Subject: [PATCH 1861/2155] fetch-mkosi: apply "ruff format" and "ruff check --fix" This also makes CONFIG is closed when not necessary. --- tools/fetch-mkosi.py | 51 ++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/tools/fetch-mkosi.py b/tools/fetch-mkosi.py index 6e6440d65ef9c..481f607d40fcf 100755 --- a/tools/fetch-mkosi.py +++ b/tools/fetch-mkosi.py @@ -7,9 +7,9 @@ """ import argparse +import re import shlex import subprocess -import re from pathlib import Path URL = 'https://github.com/systemd/mkosi' @@ -17,6 +17,7 @@ CONFIG = Path('mkosi/mkosi.conf') WORKFLOWS = [Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml']] + def parse_args(): p = argparse.ArgumentParser( description=__doc__, @@ -26,59 +27,72 @@ def parse_args(): type=Path, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) return p.parse_args() + def read_config(): print(f'Reading {CONFIG}…') - matches = [m.group(1) - for line in open(CONFIG) - if (m := re.match('^MinimumVersion=commit:([a-z0-9]{40})$', - line.strip()))] + c = CONFIG.read_text() + matches = [ + m.group(1) for m in re.finditer('^\s*MinimumVersion=commit:([a-z0-9]{40})\s*$', c, re.MULTILINE) + ] assert len(matches) == 1 return matches[0] + def commit_file(files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update mkosi ref to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update mkosi ref to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_mkosi(args): if args.dir.exists(): print(f'{args.dir} already exists.') return cmd = [ - 'git', 'clone', URL, + 'git', + 'clone', + URL, f'--branch={BRANCH}', args.dir.as_posix(), ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def update_mkosi(args): old_commit = read_config() cmd = ['git', '-C', args.dir.as_posix(), 'rev-parse', f'refs/remotes/origin/{BRANCH}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'mkosi: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', args.dir.as_posix(), 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', args.dir.as_posix(), + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() for f in [CONFIG, *WORKFLOWS]: @@ -91,6 +105,7 @@ def update_mkosi(args): commit_file([CONFIG, *WORKFLOWS], new_commit, changes) + if __name__ == '__main__': args = parse_args() checkout_mkosi(args) From 30c87278d44feccf0508e84e73e3443c396b68d4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:09:45 +0900 Subject: [PATCH 1862/2155] find-unused-library-symbols: apply "ruff format" and "ruff check --fix" --- tools/find-unused-library-symbols.py | 75 ++++++++++++++-------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/tools/find-unused-library-symbols.py b/tools/find-unused-library-symbols.py index 47f96df2ee44f..66083c0ea4620 100755 --- a/tools/find-unused-library-symbols.py +++ b/tools/find-unused-library-symbols.py @@ -39,10 +39,10 @@ def get_exported_symbols(library_path): ['nm', '--dynamic', '--defined-only', '--extern-only', library_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Error: Failed to run nm on {library_path}: {e}", file=sys.stderr) + print(f'Error: Failed to run nm on {library_path}: {e}', file=sys.stderr) sys.exit(1) except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -79,10 +79,10 @@ def get_undefined_symbols(executable_path): ['nm', '--dynamic', '--undefined-only', executable_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run nm on {executable_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run nm on {executable_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -112,7 +112,7 @@ def verify_executable_links_library(executable_path, library_name): ['ldd', executable_path], capture_output=True, text=True, - check=True + check=True, ) except (subprocess.CalledProcessError, FileNotFoundError): # If ldd fails or doesn't exist, we'll skip the verification @@ -137,14 +137,16 @@ def get_library_internal_references(library_path, exported_symbols): ['objdump', '-R', library_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run objdump on {library_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run objdump on {library_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: - print("Warning: 'objdump' command not found. Internal references won't be detected.", - file=sys.stderr) + print( + "Warning: 'objdump' command not found. Internal references won't be detected.", + file=sys.stderr, + ) return set() internal_refs = set() @@ -181,7 +183,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): exported_symbols = get_exported_symbols(library_path) if not exported_symbols: - print(f"Warning: No exported symbols found in {library_path}", file=sys.stderr) + print(f'Warning: No exported symbols found in {library_path}', file=sys.stderr) return set(), set(), set() # Collect all symbols used by the executables @@ -194,8 +196,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): for exe_path in executable_paths: # Optionally verify linkage if verify_linkage and not verify_executable_links_library(exe_path, library_name): - print(f"Warning: {exe_path} does not appear to link against {library_name}", - file=sys.stderr) + print(f'Warning: {exe_path} does not appear to link against {library_name}', file=sys.stderr) undefined_symbols = get_undefined_symbols(exe_path) # Only count symbols that are actually exported by our library @@ -209,31 +210,31 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): def main(): parser = argparse.ArgumentParser( - description='Find unused exported symbols in a shared library' + description='Find unused exported symbols in a shared library', ) parser.add_argument( 'library', - help='Path to the shared library to analyze' + help='Path to the shared library to analyze', ) parser.add_argument( 'executables', nargs='+', - help='Paths to executables that link against the library' + help='Paths to executables that link against the library', ) parser.add_argument( '--no-verify-linkage', action='store_true', - help='Skip verification that executables actually link against the library' + help='Skip verification that executables actually link against the library', ) parser.add_argument( '--show-used', action='store_true', - help='Also show used symbols' + help='Also show used symbols', ) parser.add_argument( '--stats-only', action='store_true', - help='Only show statistics, not individual symbols' + help='Only show statistics, not individual symbols', ) args = parser.parse_args() @@ -241,7 +242,7 @@ def main(): # Verify library exists library_path = Path(args.library) if not library_path.exists(): - print(f"Error: Library not found: {library_path}", file=sys.stderr) + print(f'Error: Library not found: {library_path}', file=sys.stderr) sys.exit(1) # Verify executables exist @@ -249,47 +250,45 @@ def main(): for exe in args.executables: exe_path = Path(exe) if not exe_path.exists(): - print(f"Warning: Executable not found: {exe_path}", file=sys.stderr) + print(f'Warning: Executable not found: {exe_path}', file=sys.stderr) else: executable_paths.append(str(exe_path)) if not executable_paths: - print("Error: No valid executables provided", file=sys.stderr) + print('Error: No valid executables provided', file=sys.stderr) sys.exit(1) # Analyze symbols unused, exported, used = find_unused_symbols( - str(library_path), - executable_paths, - verify_linkage=not args.no_verify_linkage + str(library_path), executable_paths, verify_linkage=not args.no_verify_linkage ) # Print results - print(f"Analysis of {library_path.name}") - print("=" * 70) - print(f"Total exported symbols: {len(exported)}") - print(f" (excluding public API symbols starting with 'sd_')") - print(f"Used symbols: {len(used)}") - print(f"Unused symbols: {len(unused)}") - print(f"Usage rate: {len(used)/len(exported)*100:.1f}%" if exported else "N/A") + print(f'Analysis of {library_path.name}') + print('=' * 70) + print(f'Total exported symbols: {len(exported)}') + print(" (excluding public API symbols starting with 'sd_')") + print(f'Used symbols: {len(used)}') + print(f'Unused symbols: {len(unused)}') + print(f'Usage rate: {len(used) / len(exported) * 100:.1f}%' if exported else 'N/A') print() if not args.stats_only: if unused: - print("Unused symbols:") - print("-" * 70) + print('Unused symbols:') + print('-' * 70) for symbol in sorted(unused): - print(f" {symbol}") + print(f' {symbol}') print() else: - print("All exported symbols are used!") + print('All exported symbols are used!') print() if args.show_used and used: - print("Used symbols:") - print("-" * 70) + print('Used symbols:') + print('-' * 70) for symbol in sorted(used): - print(f" {symbol}") + print(f' {symbol}') print() # Exit with non-zero if there are unused symbols (useful for CI) From 86d3b07fbae1151311f3e83f3ef7af92a79e9468 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:10:00 +0900 Subject: [PATCH 1863/2155] gdb-sd_dump_hashmaps: apply "ruff format" and "ruff check --fix" --- tools/gdb-sd_dump_hashmaps.py | 56 ++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/tools/gdb-sd_dump_hashmaps.py b/tools/gdb-sd_dump_hashmaps.py index 596ee8d90c32b..d6fc2c4bb8e85 100755 --- a/tools/gdb-sd_dump_hashmaps.py +++ b/tools/gdb-sd_dump_hashmaps.py @@ -4,38 +4,39 @@ import gdb + class sd_dump_hashmaps(gdb.Command): "dump systemd's hashmaps" def __init__(self): - super().__init__("sd_dump_hashmaps", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) + super().__init__('sd_dump_hashmaps', gdb.COMMAND_DATA, gdb.COMPLETE_NONE) def invoke(self, arg, _from_tty): - d = gdb.parse_and_eval("hashmap_debug_list") - hashmap_type_info = gdb.parse_and_eval("hashmap_type_info") - uchar_t = gdb.lookup_type("unsigned char") - ulong_t = gdb.lookup_type("unsigned long") - debug_offset = gdb.parse_and_eval("(unsigned long)&((HashmapBase*)0)->debug") + d = gdb.parse_and_eval('hashmap_debug_list') + hashmap_type_info = gdb.parse_and_eval('hashmap_type_info') + uchar_t = gdb.lookup_type('unsigned char') + ulong_t = gdb.lookup_type('unsigned long') + debug_offset = gdb.parse_and_eval('(unsigned long)&((HashmapBase*)0)->debug') - print("type, hash, indirect, entries, max_entries, buckets, creator") + print('type, hash, indirect, entries, max_entries, buckets, creator') while d: - h = gdb.parse_and_eval(f"(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})") + h = gdb.parse_and_eval(f'(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})') - if h["has_indirect"]: - storage_ptr = h["indirect"]["storage"].cast(uchar_t.pointer()) - n_entries = h["indirect"]["n_entries"] - n_buckets = h["indirect"]["n_buckets"] + if h['has_indirect']: + storage_ptr = h['indirect']['storage'].cast(uchar_t.pointer()) + n_entries = h['indirect']['n_entries'] + n_buckets = h['indirect']['n_buckets'] else: - storage_ptr = h["direct"]["storage"].cast(uchar_t.pointer()) - n_entries = h["n_direct_entries"] - n_buckets = hashmap_type_info[h["type"]]["n_direct_buckets"] + storage_ptr = h['direct']['storage'].cast(uchar_t.pointer()) + n_entries = h['n_direct_entries'] + n_buckets = hashmap_type_info[h['type']]['n_direct_buckets'] - t = ["plain", "ordered", "set"][int(h["type"])] + t = ['plain', 'ordered', 'set'][int(h['type'])] - print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') + print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') # fmt: skip - if arg != "" and n_entries > 0: - dib_raw_addr = storage_ptr + hashmap_type_info[h["type"]]["entry_size"] * n_buckets + if arg != '' and n_entries > 0: + dib_raw_addr = storage_ptr + hashmap_type_info[h['type']]['entry_size'] * n_buckets histogram = {} for i in range(0, n_buckets): @@ -44,11 +45,11 @@ def invoke(self, arg, _from_tty): for dib in sorted(histogram): if dib != 255: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_entries):.0%} of entries") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_entries):.0%} of entries') # fmt: skip else: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_buckets):.0%} of slots") - s = sum(dib*count for (dib, count) in histogram.items() if dib != 255) / n_entries - print(f"mean DIB of entries: {s}") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_buckets):.0%} of slots') # fmt: skip + s = sum(dib * count for (dib, count) in histogram.items() if dib != 255) / n_entries + print(f'mean DIB of entries: {s}') blocks = [] current_len = 1 @@ -69,10 +70,11 @@ def invoke(self, arg, _from_tty): if len(blocks) > 1 and blocks[0][0] == blocks[0][1] and blocks[-1][0] == n_buckets - 1: blocks[0][1] += blocks[-1][1] blocks = blocks[0:-1] - print("max block: {}".format(max(blocks, key=lambda a: a[1]))) - print("sum block lens: {}".format(sum(b[1] for b in blocks))) - print("mean block len: {}".format(sum(b[1] for b in blocks) / len(blocks))) + print(f'max block: {max(blocks, key=lambda a: a[1])}') + print(f'sum block lens: {sum(b[1] for b in blocks)}') + print(f'mean block len: {sum(b[1] for b in blocks) / len(blocks)}') + + d = d['debug_list_next'] - d = d["debug_list_next"] sd_dump_hashmaps() From cc1373700ba7eea6412627c582849f569851486a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:10:14 +0900 Subject: [PATCH 1864/2155] generate-gperfs: apply "ruff format" --- tools/generate-gperfs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/generate-gperfs.py b/tools/generate-gperfs.py index fc349b43faed5..e9e520247eb20 100755 --- a/tools/generate-gperfs.py +++ b/tools/generate-gperfs.py @@ -13,23 +13,23 @@ sys.exit(f'Usage: {sys.argv[0]} name prefix file [includes...]') name, prefix, file, *includes = sys.argv[1:] - includes = [f"#include {i}" for i in includes] + includes = [f'#include {i}' for i in includes] # Older versions of python don't allow backslashes # in f-strings so use chr(10) for newlines and chr(92) # for backslashes instead as a workaround. - print(f"""\ + print(f'''\ %{{ _Pragma("GCC diagnostic ignored {chr(92)}"-Wimplicit-fallthrough{chr(92)}"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored {chr(92)}"-Wzero-as-null-pointer-constant{chr(92)}"") #endif {chr(10).join(includes)} -%}}""") - print(f"""\ +%}}''') + print(f'''\ struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""") +%%''') for line in open(file): - print("{0}, {1}{0}".format(line.rstrip(), prefix)) + print('{0}, {1}{0}'.format(line.rstrip(), prefix)) From e0ac581a8b8acf9e4bc8ff4efdef5ab1d5dcb36c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:10:48 +0900 Subject: [PATCH 1865/2155] list-discoverable-partitions: apply "ruff format" --- tools/list-discoverable-partitions.py | 71 +++++++++++++++++---------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/tools/list-discoverable-partitions.py b/tools/list-discoverable-partitions.py index a19bf1d6e2ee7..437be68d727f7 100755 --- a/tools/list-discoverable-partitions.py +++ b/tools/list-discoverable-partitions.py @@ -30,16 +30,15 @@ 'TILEGX': 'TILE-Gx', 'X86': 'x86', 'X86_64': 'amd64/x86_64', -} +} # fmt: skip TYPES = { - 'ROOT' : 'Root Partition', - 'ROOT_VERITY' : 'Root Verity Partition', - 'ROOT_VERITY_SIG' : 'Root Verity Signature Partition', - 'USR' : '`/usr/` Partition', - 'USR_VERITY' : '`/usr/` Verity Partition', - 'USR_VERITY_SIG' : '`/usr/` Verity Signature Partition', - + 'ROOT': 'Root Partition', + 'ROOT_VERITY': 'Root Verity Partition', + 'ROOT_VERITY_SIG': 'Root Verity Signature Partition', + 'USR': '`/usr/` Partition', + 'USR_VERITY': '`/usr/` Verity Partition', + 'USR_VERITY_SIG': '`/usr/` Verity Signature Partition', 'ESP': 'EFI System Partition', 'SRV': 'Server Data Partition', 'VAR': 'Variable Data Partition', @@ -49,7 +48,7 @@ 'USER_HOME': 'Per-user Home Partition', 'LINUX_GENERIC': 'Generic Linux Data Partition', 'XBOOTLDR': 'Extended Boot Loader Partition', -} +} # fmt: skip DESCRIPTIONS = { 'ROOT': ( @@ -57,56 +56,66 @@ 'On systems with matching architecture, the first partition with this type UUID on the disk ' 'containing the active EFI ESP is automatically mounted to the root directory `/`. ' 'If the partition is encrypted with LUKS or has dm-verity integrity data (see below), the ' - 'device mapper file will be named `/dev/mapper/root`.'), + 'device mapper file will be named `/dev/mapper/root`.', + ), 'USR': ( 'Any native, optionally in LUKS', - 'Similar semantics to root partition, but just the `/usr/` partition.'), + 'Similar semantics to root partition, but just the `/usr/` partition.', + ), 'ROOT_VERITY': ( 'A dm-verity superblock followed by hash data', 'Contains dm-verity integrity hash data for the matching root partition. If this feature is ' 'used the partition UUID of the root partition should be the first 128 bits of the root hash ' 'of the dm-verity hash data, and the partition UUID of this dm-verity partition should be the ' 'final 128 bits of it, so that the root partition and its Verity partition can be discovered ' - 'easily, simply by specifying the root hash.'), + 'easily, simply by specifying the root hash.', + ), 'USR_VERITY': ( 'A dm-verity superblock followed by hash data', - 'Similar semantics to root Verity partition, but just for the `/usr/` partition.'), + 'Similar semantics to root Verity partition, but just for the `/usr/` partition.', + ), 'ROOT_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.'), + 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.', + ), 'USR_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.'), - + 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.', + ), 'ESP': ( 'VFAT', 'The ESP used for the current boot is automatically mounted to `/efi/` (or `/boot/` as ' 'fallback), unless a different partition is mounted there (possibly via `/etc/fstab`, or ' 'because the Extended Boot Loader Partition — see below — exists) or the directory is ' 'non-empty on the root disk. This partition type is defined by the ' - '[UEFI Specification](http://www.uefi.org/specifications).'), + '[UEFI Specification](http://www.uefi.org/specifications).', + ), 'XBOOTLDR': ( 'Typically VFAT', 'The Extended Boot Loader Partition (XBOOTLDR) used for the current boot is automatically ' 'mounted to `/boot/`, unless a different partition is mounted there (possibly via ' '`/etc/fstab`) or the directory is non-empty on the root disk. This partition type ' 'is defined by the [Boot Loader ' - 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).'), + 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).', + ), 'SWAP': ( 'Swap, optionally in LUKS', 'All swap partitions on the disk containing the root partition are automatically enabled. ' 'If the partition is encrypted with LUKS, the device mapper file will be named ' - '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.'), + '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.', + ), 'HOME': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/home/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/home`.'), + 'mapper file will be named `/dev/mapper/home`.', + ), 'SRV': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/srv/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/srv`.'), + 'mapper file will be named `/dev/mapper/srv`.', + ), 'VAR': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -118,7 +127,8 @@ 'listed here) is inherently private to a specific installation and cannot possibly be ' 'shared between multiple OS installations on the same disk, and thus should be bound to ' 'a specific instance of the OS, identified by its machine ID. If the partition is ' - 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.'), + 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.', + ), 'TMP': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -127,19 +137,23 @@ 'is indeed `/var/tmp/`, not `/tmp/`. The latter is typically maintained in memory via ' '`tmpfs` and does not require a partition on disk. In some cases it might be ' 'desirable to make `/tmp/` persistent too, in which case it is recommended to make it ' - 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.'), + 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.', + ), 'USER_HOME': ( 'Any native, optionally in LUKS', 'A home partition of a user, managed by ' - '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).'), + '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).', + ), 'LINUX_GENERIC': ( 'Any native, optionally in LUKS', 'No automatic mounting takes place for other Linux data partitions. This partition type ' 'should be used for all partitions that carry Linux file systems. The installer needs ' 'to mount them explicitly via entries in `/etc/fstab`. Optionally, these partitions may ' - 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.'), + 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.', + ), } + def extract(file): for line in file: # print(line) @@ -148,7 +162,10 @@ def extract(file): continue name = line.split()[1] - if m2 := re.match(r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', m.group(1)): + if m2 := re.match( + r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', + m.group(1), + ): ptype, arch, suffix, u = m2.groups() u = uuid.UUID(u.replace(',', '')) assert arch in ARCHITECTURES, f'{arch} not in f{ARCHITECTURES}' @@ -165,6 +182,7 @@ def extract(file): else: raise ValueError(f'Failed to match: {m.group(1)}') + def generate(defines): prevtype = None @@ -188,6 +206,7 @@ def generate(defines): print(f'| _{tdesc}{adesc}_ | `{puuid}` `{name}` | {morea} | {moreb} |') + if __name__ == '__main__': known = extract(sys.stdin) generate(known) From 8b24db05d42cd62642a5d3f19cd88a2d96eca24c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Feb 2026 02:11:03 +0900 Subject: [PATCH 1866/2155] make-directive-index: apply "ruff format" --- tools/make-directive-index.py | 38 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tools/make-directive-index.py b/tools/make-directive-index.py index 5398b452eff18..ddf07534ef30c 100755 --- a/tools/make-directive-index.py +++ b/tools/make-directive-index.py @@ -13,6 +13,7 @@ referring to {pages} individual manual pages. ''' + def _extract_directives(directive_groups, formatting, page): t = xml_parse(page) section = t.find('./refmeta/manvolnum').text @@ -21,12 +22,13 @@ def _extract_directives(directive_groups, formatting, page): storopt = directive_groups['options'] for variablelist in t.iterfind('.//variablelist'): klass = variablelist.attrib.get('class') - searchpath = variablelist.attrib.get('xpath','./varlistentry/term/varname') + searchpath = variablelist.attrib.get('xpath', './varlistentry/term/varname') storvar = directive_groups[klass or 'miscellaneous'] # + sd_json_parse_fd() reads and parses a JSON value from the file referenced by + the file descriptor fd. By default the file descriptor is internally duplicated + and the caller's descriptor is left untouched (the current file offset will be shared with the original + file descriptor however); the JSON text is read starting at the descriptor's current file offset. This + behaviour may be modified via the SD_JSON_PARSE_SEEK0, + SD_JSON_PARSE_DONATE_FD and SD_JSON_PARSE_REOPEN_FD flags, see + below. The path argument is not used to open anything in this case; it is only + recorded as source information in the resulting JSON variant (see above) and may be passed as + NULL. + The flags argument is a bitmask of zero or more of the following flags: @@ -179,6 +203,44 @@ + + + SD_JSON_PARSE_SEEK0 + + Before reading, seek the input to its beginning (i.e. file offset 0). This flag has + no effect when parsing from a string. When used together with + SD_JSON_PARSE_REOPEN_FD in sd_json_parse_fd() it is + redundant, since a freshly reopened file descriptor starts at offset 0 anyway. + + + + + + SD_JSON_PARSE_DONATE_FD + + Only has an effect on sd_json_parse_fd(). If set, ownership of + the file descriptor passed in fd is transferred into the call: the descriptor + is consumed and closed before the function returns, including in the error path. If not set (the + default), the caller retains ownership of fd and the function operates on an + internally duplicated descriptor instead. This flag may not be combined with + SD_JSON_PARSE_REOPEN_FD. + + + + + + SD_JSON_PARSE_REOPEN_FD + + Only has an effect on sd_json_parse_fd(). If set, the file + descriptor passed in fd is reopened (read-only) before reading, instead of + being duplicated. This is primarily useful to obtain an independent file offset (positioned at the + beginning of the file) and a clean, read-only access mode, even if the original descriptor was opened + differently (for example with O_PATH). The caller retains ownership of the + original descriptor, which is left untouched. This flag may not be combined with + SD_JSON_PARSE_DONATE_FD. + + + If both SD_JSON_PARSE_MUST_BE_OBJECT and @@ -232,6 +294,11 @@ sd_json_parse_with_source_continue(), sd_json_parse_file(), and sd_json_parse_file_at() were added in version 257. + + sd_json_parse_fd() and the SD_JSON_PARSE_MUST_BE_OBJECT, + SD_JSON_PARSE_MUST_BE_ARRAY, SD_JSON_PARSE_SEEK0, + SD_JSON_PARSE_DONATE_FD and SD_JSON_PARSE_REOPEN_FD flags were + added in version 261. From 914c41a7747bc3b6ec039072bad2c59965c3779a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 17 May 2026 08:46:59 +0200 Subject: [PATCH 1920/2155] test: add unit test for new sd-json functionality --- src/libsystemd/sd-json/test-json.c | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index 3be4b09660b14..c9aa5d2f90169 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -13,6 +13,7 @@ #include "fd-util.h" #include "format-util.h" #include "fileio.h" +#include "io-util.h" #include "iovec-util.h" #include "json-internal.h" #include "json-util.h" @@ -518,6 +519,68 @@ TEST(source) { printf("--- pretty end ---\n"); } +TEST(parse_fd) { + static const char data[] = "{ \"foo\" : \"bar\", \"baz\" : 4711 }"; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK(fd = open_tmpfile_unlinkable(NULL, O_RDWR)); + ASSERT_OK(loop_write(fd, data, strlen(data))); + + /* By default the fd is internally duplicated, the caller's fd stays open and the JSON text is + * read starting at the current file offset. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, /* flags= */ 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); /* still open, we only got a duplicate */ + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(v, "baz")), UINT64_C(4711)); + v = sd_json_variant_unref(v); + + /* Without SD_JSON_PARSE_SEEK0 and with the offset left at EOF there is nothing to read. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END)); + ASSERT_ERROR(sd_json_parse_fd("tmpfile", fd, /* flags= */ 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL), ENODATA); + ASSERT_NULL(v); + ASSERT_OK(fd_validate(fd)); + + /* SD_JSON_PARSE_SEEK0 rewinds to the beginning first, so the stale offset no longer matters. */ + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_SEEK0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_REOPEN_FD reopens the fd internally (starting at offset 0), the caller's fd and + * its offset are left untouched. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_REOPEN_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_REOPEN_FD and SD_JSON_PARSE_DONATE_FD are mutually exclusive. */ + ASSERT_RETURN_EXPECTED_SE(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_REOPEN_FD|SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL) == -EINVAL); + ASSERT_OK(fd_validate(fd)); /* not consumed on the -EINVAL path */ + + /* SD_JSON_PARSE_DONATE_FD passes ownership into the call: the fd is consumed and closed even on + * success. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(fd_validate(fd), EBADF); + TAKE_FD(fd); /* already closed by the call, don't double-close */ + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_DONATE_FD also consumes the fd when parsing fails. */ + _cleanup_close_ int fd2 = -EBADF; + ASSERT_OK(fd2 = open_tmpfile_unlinkable(NULL, O_RDWR)); + ASSERT_OK(loop_write(fd2, "kookoo", strlen("kookoo"))); + ASSERT_OK_ERRNO(lseek(fd2, 0, SEEK_SET)); + ASSERT_ERROR(sd_json_parse_fd("tmpfile", fd2, SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(fd_validate(fd2), EBADF); + TAKE_FD(fd2); + ASSERT_NULL(v); +} + TEST(depth) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; From 92e0c920c7e4ad99a9a0b388038e38588e251559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:28:08 +0200 Subject: [PATCH 1921/2155] basic/path-util: fix executable_is_good MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by qarmin (Rafał Mikrut). --- src/basic/path-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index fedb347e4a461..b41029b157cf9 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -804,7 +804,7 @@ static int executable_is_good(const char *executable) { if (r < 0) return r; - return !PATH_IN_SET(d, "true" + return !PATH_IN_SET(d, "true", "/bin/true", "/usr/bin/true", "/dev/null"); From e305f552ec34be6df08abb1882707091b2b61479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:29:12 +0200 Subject: [PATCH 1922/2155] core/dbus-execute: propagate oom in property_get_cpu_affinity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function already returns errors, so I'm not sure why we ignored the error in the second call, potentially leaving variables unitialized. It seems easiest to propagate the error. Reported by qarmin (Rafał Mikrut). --- src/core/dbus-execute.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 906002570f1e8..4d86c07a41a43 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -123,19 +123,20 @@ static int property_get_cpu_affinity( _cleanup_(cpu_set_done) CPUSet s = {}; _cleanup_free_ uint8_t *array = NULL; size_t allocated; + int r; assert(bus); assert(reply); if (c->cpu_affinity_from_numa) { - int r; - r = numa_to_cpu_set(&c->numa_policy, &s); if (r < 0) return r; } - (void) cpu_set_to_dbus(c->cpu_affinity_from_numa ? &s : &c->cpu_set, &array, &allocated); + r = cpu_set_to_dbus(c->cpu_affinity_from_numa ? &s : &c->cpu_set, &array, &allocated); + if (r < 0) + return r; return sd_bus_message_append_array(reply, 'y', array, allocated); } From 5f299e53a8a936d799bb1f223f2abbdcf3973ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Mon, 18 May 2026 16:46:28 +0900 Subject: [PATCH 1923/2155] import: Handle small files When systemd-pull encountered a file shorter than the compression magic headers it looks for, then it would complete the download in the analysis state and fail. When we are still in the analysis state and the download is done, we know there is no compression and we should leave the analysis state and continue writing out to disk as usual. --- src/import/pull-job.c | 52 ++++++++++++++++++++++++--------- test/units/TEST-72-SYSUPDATE.sh | 24 +++++++++++++++ 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 2dc960c39292d..31d6d34af6bc7 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -299,6 +299,29 @@ static int pull_job_open_disk(PullJob *j) { return 0; } +static int pull_job_begin_running(PullJob *j) { + int r; + + assert(j); + assert(j->state == PULL_JOB_ANALYZING); + assert(j->compress); + + r = pull_job_open_disk(j); + if (r < 0) + return r; + + /* Now, take the payload we read so far, and decompress it */ + _cleanup_(iovec_done) struct iovec stub = TAKE_STRUCT(j->payload); + + j->state = PULL_JOB_RUNNING; + + r = pull_job_write_compressed(j, &stub); + if (r < 0) + return r; + + return 0; +} + static int pull_job_curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result, void *userdata) { PullJob *j = ASSERT_PTR(userdata); char *scheme = NULL; @@ -382,6 +405,20 @@ static int pull_job_curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request to %s finished with unexpected code %li.", j->url, status)); } + if (j->state == PULL_JOB_ANALYZING) { + /* When curl finished the download while we were still looking for a compression magic + * header the content isn't compressed but should be written out as is. */ + assert(result == CURLE_OK); + + r = decompressor_force_off(&j->compress); + if (r < 0) + return pull_job_finish(j, r); + + r = pull_job_begin_running(j); + if (r < 0) + return pull_job_finish(j, r); + } + if (j->state != PULL_JOB_RUNNING) return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Premature connection termination.")); @@ -483,20 +520,7 @@ static int pull_job_detect_compression(PullJob *j) { log_debug("Stream is compressed: %s", compression_to_string(compressor_type(j->compress))); - r = pull_job_open_disk(j); - if (r < 0) - return r; - - /* Now, take the payload we read so far, and decompress it */ - _cleanup_(iovec_done) struct iovec stub = TAKE_STRUCT(j->payload); - - j->state = PULL_JOB_RUNNING; - - r = pull_job_write_compressed(j, &stub); - if (r < 0) - return r; - - return 0; + return pull_job_begin_running(j); } static size_t pull_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 0e1c9afa837fb..21a3ea00a5904 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -552,4 +552,28 @@ EOF done done +# Make sure the processing of compressed streams still handles uncompressed streams shorter than +# COMPRESSION_MAGIC_BYTES_MAX correctly. +rm -rf "$CONFIGDIR" "$WORKDIR/blobs" +mkdir -p "$CONFIGDIR" "$WORKDIR/blobs" +printf 'xx' >"$WORKDIR/source/tiny-v1.bin" +(cd "$WORKDIR/source" && sha256sum tiny-v1.bin >SHA256SUMS) +cat >"$CONFIGDIR/01-tiny-url.transfer" < Date: Mon, 18 May 2026 12:30:36 +0200 Subject: [PATCH 1924/2155] core/manager: fix (theoretical) fd leak on invalid messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We'd leak fds on early return. We are sending a message to ourself, so it's unlikely to fail. But let's keep the logic correct. Reported by qarmin (Rafał Mikrut). --- src/core/manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/manager.c b/src/core/manager.c index accf9c8ff94f1..165914b2a53d0 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -5119,6 +5119,7 @@ static int manager_dispatch_pidref_transport_fd(sd_event_source *source, int fd, if (n != sizeof(child_pid)) { log_warning("Got pidref message of unexpected size %zi (expected %zu), ignoring.", n, sizeof(child_pid)); + cmsg_close_all(&msghdr); return 0; } @@ -5138,6 +5139,8 @@ static int manager_dispatch_pidref_transport_fd(sd_event_source *source, int fd, } } + /* From this point on, the fds are owned by our local variables. Call cmsg_close_all no more. */ + /* Verify and set parent pidref. */ if (!ucred || !pid_is_valid(ucred->pid)) { log_warning("Received pidref message without valid credentials. Ignoring."); From 4f688e61c95dc6848a624864c295c62b8c2ab3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:31:46 +0200 Subject: [PATCH 1925/2155] coredump: use a fixed string instead a scope-delimited compound literal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compound literal (const char[]){'.','.','.'} has block scope (C99 6.5.2.5p6). Once we leave the if and loop back, copy[1].iov_base formally points into a destroyed object. Works on GCC/Clang in practice, but is UB. Let's do the easy thing and use a string. Reported by qarmin (Rafał Mikrut). --- src/coredump/coredump-send.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coredump/coredump-send.c b/src/coredump/coredump-send.c index 0174be69a4056..4817d82611d4b 100644 --- a/src/coredump/coredump-send.c +++ b/src/coredump/coredump-send.c @@ -57,7 +57,7 @@ int coredump_send(CoredumpContext *context) { * what we want to send, and the second one contains * the trailing dots. */ copy[0] = *iovec; - copy[1] = IOVEC_MAKE(((const char[]){'.', '.', '.'}), 3); + copy[1] = IOVEC_MAKE_STRING("..."); mh.msg_iov = copy; mh.msg_iovlen = 2; From f0405bfdff0ca3787d2d83a30c3fffc65ff00f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:31:55 +0200 Subject: [PATCH 1926/2155] nsresourced: fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by qarmin (Rafał Mikrut). --- 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 11bb2e7d8fd36..a6cdd03c5e2c7 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -244,7 +244,7 @@ int userns_restrict_put_by_inode( if (n_try == 0) return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), - "Stillcan't create inode entry in BPF map after 10 tries."); + "Still cannot create inode entry in BPF map after 10 tries."); r = sym_bpf_map_lookup_elem(outer_map_fd, &ino, &innermap_id); if (r >= 0) { From 133909c8f65e49052e9e2bd413589095c1ab41fe Mon Sep 17 00:00:00 2001 From: ishwarbb Date: Mon, 23 Mar 2026 13:02:40 +0000 Subject: [PATCH 1927/2155] resolved: add configurable DNS cache size Add CacheSize= option to [Resolve] section of resolved.conf to allow configuring the maximum number of entries in the per-scope DNS cache. The default remains 4096 entries. Setting this to 0 disables caching (similar to Cache=no). CacheSize= is only read when Cache=yes or Cache=no-negative. When Cache=no, caching is fully disabled regardless of CacheSize=. Changes: - Add cache_size field to Manager struct - Parse CacheSize= from resolved.conf via gperf - Thread cache_size through dns_cache_put() and helper functions - Replace hard-coded CACHE_MAX with the configurable cache_size - When cache_size is 0 or Cache=no, flush cache and skip caching - Add man page documentation for the new option - Add unit tests for cache size enforcement Co-developed-by: Claude --- man/resolved.conf.xml | 26 +++++++++++ src/resolve/resolved-conf.c | 23 ++++++++++ src/resolve/resolved-conf.h | 1 + src/resolve/resolved-dns-cache.c | 17 ++++--- src/resolve/resolved-dns-cache.h | 6 +++ src/resolve/resolved-dns-scope.c | 1 + src/resolve/resolved-gperf.gperf | 4 ++ src/resolve/resolved-manager.c | 2 + src/resolve/resolved-manager.h | 1 + src/resolve/resolved.conf.in | 3 ++ src/resolve/test-dns-cache.c | 77 +++++++++++++++++++++++++++++++- 11 files changed, 153 insertions(+), 8 deletions(-) diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index f8899fe662c95..2c5358209f07f 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -301,6 +301,32 @@ + + DNSCacheSize= + MulticastDNSCacheSize= + LLMNRCacheSize= + Takes a non-negative integer. Configures the maximum number of DNS resource record + entries that may be stored in the per-scope cache for unicast DNS, Multicast DNS (mDNS), and + Link-Local Multicast Name Resolution (LLMNR) respectively. Each defaults to 4096. The maximum + allowed value is 16777216 (2^24). Setting any of these to 0 effectively disables caching for the + respective protocol. These settings are only effective when Cache= is set to + yes or no-negative. If Cache=no, caching + is fully disabled regardless of these values. + + Note that Multicast DNS relies heavily on caching for request suppression and efficient + operation. It is recommended to keep MulticastDNSCacheSize= at a reasonably high + value even when reducing DNSCacheSize=. + + Note that systemd-resolved automatically flushes all caches on system + memory pressure, thus in most cases manual cache size configuration should not be necessary. + + Note that caching is turned off by default for host-local DNS servers. + See CacheFromLocalhost= for details. + + + + + DNSStubListener= Takes a boolean argument or one of udp and diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 117bf7ccc3241..2a5c4eb6509fe 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -8,6 +8,7 @@ #include "ordered-set.h" #include "proc-cmdline.h" #include "resolved-conf.h" +#include "resolved-dns-cache.h" #include "resolved-dns-search-domain.h" #include "resolved-dns-server.h" #include "resolved-dns-stub.h" @@ -304,6 +305,28 @@ int manager_parse_config_file(Manager *m) { return 0; } +int config_parse_dns_cache_max( + 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) { + + Manager *m = ASSERT_PTR(userdata); + + assert(ltype >= 0 && ltype < _DNS_PROTOCOL_MAX); + + return config_parse_unsigned_bounded( + unit, filename, line, section, section_line, lvalue, rvalue, + 0, CACHE_MAX_UPPER_LIMIT, true, + &m->cache_max[ltype]); +} + int config_parse_record_types( const char *unit, const char *filename, diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 71899d36f680d..51be831086074 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -19,4 +19,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dns_servers); CONFIG_PARSER_PROTOTYPE(config_parse_search_domains); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_extra); +CONFIG_PARSER_PROTOTYPE(config_parse_dns_cache_max); CONFIG_PARSER_PROTOTYPE(config_parse_record_types); diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 6a7967842dbe4..3d5dc2772a83a 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -18,10 +18,6 @@ #include "string-util.h" #include "time-util.h" -/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to - * leave DNS caches unbounded, but that's crazy. */ -#define CACHE_MAX 4096 - /* We never keep any item longer than 2h in our cache unless StaleRetentionSec is greater than zero. */ #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) @@ -184,9 +180,12 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { if (add <= 0) return; + if (c->cache_max == 0) + return; + /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that + * the cache to grow beyond cache_max, but only when we shall + * add more RRs to the cache than cache_max at once. In that * case the cache will be emptied completely otherwise. */ for (;;) { @@ -196,7 +195,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { if (prioq_isempty(c->by_expiry)) break; - if (prioq_size(c->by_expiry) + add < CACHE_MAX) + if (prioq_size(c->by_expiry) + add < c->cache_max) break; i = prioq_peek(c->by_expiry); @@ -753,6 +752,10 @@ int dns_cache_put( assert(c); assert(owner_address); + /* Check cache mode here too, since the mDNS caller doesn't guard against Cache=no. */ + if (cache_mode == DNS_CACHE_MODE_NO || c->cache_max == 0) + return 0; + dns_cache_remove_previous(c, key, answer); /* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index be98a8a567665..54ae110c09ca9 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -3,11 +3,17 @@ #include "resolved-forward.h" +/* Never cache more than 4K entries by default. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define DEFAULT_CACHE_MAX 4096U +#define CACHE_MAX_UPPER_LIMIT (1U << 24) + typedef struct DnsCache { Hashmap *by_key; Prioq *by_expiry; unsigned n_hit; unsigned n_miss; + unsigned cache_max; } DnsCache; void dns_cache_flush(DnsCache *c); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 89b13b0f1d619..d48896494f90c 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -76,6 +76,7 @@ int dns_scope_new( .protocol = protocol, .family = family, .resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC, + .cache.cache_max = m->cache_max[protocol], /* Enforce ratelimiting for the multicast protocols */ .ratelimit = { MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST }, diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 8b8a66d0369bf..b5f31f91397a6 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -6,6 +6,7 @@ _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"") #endif #include #include "conf-parser.h" +#include "dns-packet.h" #include "resolved-conf.h" #include "resolved-dns-server.h" #include "resolved-manager.h" @@ -35,5 +36,8 @@ Resolve.ReadStaticRecords, config_parse_bool, 0, 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) +Resolve.DNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_DNS, 0 +Resolve.MulticastDNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_MDNS, 0 +Resolve.LLMNRCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_LLMNR, 0 Resolve.StaleRetentionSec, config_parse_sec, 0, offsetof(Manager, stale_retention_usec) Resolve.RefuseRecordTypes, config_parse_record_types, 0, offsetof(Manager, refuse_record_types) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index d7d707726587d..add4f64910507 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -641,6 +641,8 @@ static void manager_set_defaults(Manager *m) { m->read_static_records = true; m->resolve_unicast_single_label = false; m->cache_from_localhost = false; + for (DnsProtocol p = 0; p < _DNS_PROTOCOL_MAX; p++) + m->cache_max[p] = DEFAULT_CACHE_MAX; m->stale_retention_usec = 0; m->refuse_record_types = set_free(m->refuse_record_types); m->resolv_conf_stat = (struct stat) {}; diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index d72e9104d79d0..6fbd7d39fd4c9 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -25,6 +25,7 @@ typedef struct Manager { DnssecMode dnssec_mode; DnsOverTlsMode dns_over_tls_mode; DnsCacheMode enable_cache; + unsigned cache_max[_DNS_PROTOCOL_MAX]; bool cache_from_localhost; DnsStubListenerMode dns_stub_listener_mode; usec_t stale_retention_usec; diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 147d30845b129..c1f7b26c72169 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -36,6 +36,9 @@ #LLMNR={{DEFAULT_LLMNR_MODE_STR}} #Cache=yes #CacheFromLocalhost=no +#DNSCacheSize=4096 +#MulticastDNSCacheSize=4096 +#LLMNRCacheSize=4096 #DNSStubListener=yes #DNSStubListenerExtra= #ReadEtcHosts=yes diff --git a/src/resolve/test-dns-cache.c b/src/resolve/test-dns-cache.c index 705878422e081..6dc28f39fccb0 100644 --- a/src/resolve/test-dns-cache.c +++ b/src/resolve/test-dns-cache.c @@ -21,7 +21,9 @@ #include "tmpfile-util.h" static DnsCache new_cache(void) { - return (DnsCache) {}; + return (DnsCache) { + .cache_max = DEFAULT_CACHE_MAX, + }; } typedef struct PutArgs { @@ -511,6 +513,79 @@ TEST(dns_a_to_cname_success_escaped_name_returns_error) { ASSERT_TRUE(dns_cache_is_empty(&cache)); } +TEST(dns_cache_size_honored) { + _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache(); + _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args(); + + cache.cache_max = 4; + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "one.example.com"); + ASSERT_NOT_NULL(put_args.key); + put_args.rcode = DNS_RCODE_SUCCESS; + answer_add_a(&put_args, put_args.key, 0xc0a80101, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "two.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80102, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "three.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80103, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "four.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80104, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "five.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80105, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + /* Each dns_cache_put() call reserves space for both the answer RR and the key (cache_keys=2), + * so eviction triggers when prioq_size + 2 >= cache_max (i.e. at the 3rd entry with cache_max=4). + * After 5 inserts, only the last 2 entries remain. */ + ASSERT_EQ(dns_cache_size(&cache), 2u); +} + +TEST(dns_cache_size_zero_evicts_all) { + _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache(); + _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args(); + + cache.cache_max = 0; + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(put_args.key); + put_args.rcode = DNS_RCODE_SUCCESS; + answer_add_a(&put_args, put_args.key, 0xc0a8017f, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + ASSERT_TRUE(dns_cache_is_empty(&cache)); +} + /* ================================================================ * dns_cache_lookup() * ================================================================ */ From 3a333585fe3c825430910bd5953e6b3956bb9944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:33:08 +0200 Subject: [PATCH 1928/2155] repart: fix bogus errno in error message and returned value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by qarmin (Rafał Mikrut). --- src/repart/repart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index dca90f2509e37..133a8e02118dc 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -3442,8 +3442,8 @@ static int context_copy_from_one(Context *context, const char *src) { if (!np->copy_blocks_path) return log_oom(); - np->copy_blocks_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (np->copy_blocks_fd < 0) + np->copy_blocks_fd = r = RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); + if (r < 0) return log_error_errno(r, "Failed to duplicate file descriptor of %s: %m", src); np->copy_blocks_offset = start; From 901179e3c74209fcdd4d27523c8705d14517e85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 18 May 2026 12:33:24 +0200 Subject: [PATCH 1929/2155] sysupdate: fix noop assert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by qarmin (Rafał Mikrut). --- src/sysupdate/sysupdate-pattern.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysupdate/sysupdate-pattern.c b/src/sysupdate/sysupdate-pattern.c index 58aa524a5d1ba..e1cc3dc466cdb 100644 --- a/src/sysupdate/sysupdate-pattern.c +++ b/src/sysupdate/sysupdate-pattern.c @@ -420,7 +420,7 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) { } default: - assert_se("unexpected pattern element"); + assert_not_reached(); } p = n; From 06960c11b3155dc3a24bdb8261cf607d65714445 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 11:50:07 +0100 Subject: [PATCH 1930/2155] NEWS: add new items for v261~rc1 --- NEWS | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 402 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 4e2166feffbd7..c7a61dc8fb90c 100644 --- a/NEWS +++ b/NEWS @@ -21,10 +21,11 @@ CHANGES WITH 261 in spe: * 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. + 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. * Several configuration fields in the io.systemd.Unit varlink interface that were previously exposed as plain strings have been converted to @@ -52,31 +53,97 @@ CHANGES WITH 261 in spe: 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 - on the root directory (/) to 0555 + Changes in the system and service manager: - * 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. + * PID1 now supports the kernel's Live Update Orchestration (LUO) / + Kexec Handover (KHO) systems when present and enabled. System units' + FD Stores are now preserved through kexec, and units will get back + stashed (named) file descriptors after kexec, if the kernel supports + the FD type (at the time of writing only memfds are supported). + Units can also create their own LUO Sessions by talking to the kernel + directly, and store them in their FD Stores, and those will be also + preserved and passed down to the unit after kexec. Units must set + 'FileDescriptorStorePreserve=yes' in order to enable this feature. + + * User session managers now supports persisting user unit's FD Stores + by receiving FDs via the notify socket, and passing them down via + $SLISTEN_FDS when the user session is restarted, when the + 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' + options are set in the user@.service unit. Combined with the LUO + support, this lets user units persist state (e.g.: memfds) across + not only user session restarts, but also kexec reboots. * The manager exposes a new ReloadCount property on its D-Bus and Varlink interfaces (org.freedesktop.systemd1.Manager and io.systemd.Manager respectively). The counter increments after - each successfully completed daemon-reload. It is not preserved - across daemon-reexec. + each successfully completed daemon-reload, and it is reset on + daemon-reexec. + + * A new ConditionSecurity=measured-os condition has been added that + checks whether the system was booted with measured-boot semantics + (i.e. via systemd-stub or an equivalent verified-boot mechanism + that measured the OS to the TPM). + + * A new unit setting CPUSetPartition= has been added that allows + configuring the cpuset cgroup partition type (e.g. "root", + "isolated", "member") for a service. + + * Two new optional sd_notify() messages have been introduced that + allow services to be notified of I/O and CPU pressure events from + PSI (Pressure Stall Information). The system manager forwards + pressure events for the corresponding cgroup. - Changes in systemd-sysext/systemd-confext: + * A new RestrictFileSystemAccess= setting has been added that uses a + BPF LSM program to restrict execution to only binares that are + stored on a signed and verified dm-verity protected filesystem. + + * The io.systemd.Unit.StartTransient Varlink method has been extended + to accept SetCredentials, SetCredentialsEncrypted, Environment and + WorkingDirectory fields, on par with what is already possible via + the legacy D-Bus interface. + + * A new set of Varlink methods has been added to the + io.systemd.Manager interface to request system shutdown: + PowerOff(), Reboot(), SoftReboot(), Halt() and Kexec(). These + complement the existing D-Bus interfaces. + + * The io.systemd.Manager.ListUnitsByNames() Varlink method allows + querying multiple units in one call and supports a result limit. + + * A new DefaultMemoryZSwapWriteback= manager setting has been added + that provides a system-wide default for the existing + MemoryZSwapWriteback= per-unit setting. + + * A new io.systemd.Job Varlink interface exposes information about + pending and running manager jobs. + + Changes in systemd-tmpfiles, systemd-sysusers and similar early-boot + tools: + + * A new tmpfiles.d/root.conf has been added that sets permissions + on the root directory (/) to 0555. + + * systemd-tmpfiles gained a new --inline option to accept + tmpfiles.d directives on the command line. + + * New directive types 'k/K' have been added to systemd-tmpfiles for + setting file capabilities. + + * systemd-firstboot can now set the static hostname from a system + credential (firstboot.hostname). + + Changes in systemd-sysext and 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. + 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. + + * A kernel command line kill switch is now honored that disables + systemd-sysext and systemd-confext merging entirely. Changes in systemd-networkd and networkctl: @@ -84,6 +151,321 @@ CHANGES WITH 261 in spe: dump acquired DHCP leases. This may be useful for inspecting the DHCP options provided by the server. + * systemd-networkd implements the io.systemd.service.Reload() Varlink + method, and exposes new io.systemd.Network.Link.Describe(), + Reconfigure(), Renew() and ForceRenew() methods. 'networkctl' now + uses these Varlink methods in preference to the legacy D-Bus API + where possible. + + * A new IPv4SrcValidMark= setting has been added to .network files. + + * The VRF.Table= setting now accepts symbolic route table names (as + configured via RouteTable= in networkd.conf) in addition to + numeric table IDs. + + * New DHCPServerPoolSize= and DHCPServerPoolOffset= properties have + been added to the D-Bus interface, mirroring the existing + configuration file options. + + * The DHCPv4 server gained support for serving the SIP server option + (RFC 3361) to clients. + + * The Varlink Describe() output now reports interface bit rates. + + Changes in systemd-resolved: + + * New 'DNSCacheSize=', 'MulticastDNSCacheSize=' and 'LLMNRCacheSize=' + settings are now supported to allow overriding the default caches + sizes for the respective protocols. + + * Additional local resource records may now be defined via drop-in + configuration files, complementing the existing global definitions. + + * Insecure DNSSEC answers using unsupported signature or digest + algorithms are now correctly accepted as insecure, rather than + being rejected outright. + + * When StaleRetentionSec= is set, the resolver no longer flushes its + cache on server switch or re-probe, keeping potentially useful + stale entries available. + + * /etc/hosts entries are now re-read on reload (SIGHUP / D-Bus + Reload / Varlink Reload). + + Changes in systemd-udevd, hwdb and udev rules: + + * The DMI ID device (/sys/class/dmi/id) is now tagged so that + early-boot consumers can reliably order against it. + + * A new hwdb database describes basic IMDS endpoints for known + cloud providers (see also systemd-imdsd above). + + Changes in systemd-boot, systemd-stub, bootctl, ukify and BLS: + + * A new "boot secret" mechanism has been added: systemd-boot can + provision a per-system secret in an EFI variable that is locked + down so that the OS cannot read it back. This allows the boot + loader to attest its identity to the booted system without giving + the system the means to impersonate it on systems without a TPM2. + + * systemd-stub now auto-detects the active EFI serial console + device and appends an appropriate "console=" parameter to the + kernel command line, simplifying serial-console UKI deployments. + + * A new "extra" type-1 Boot Loader Specification stanza is parsed + and used to deliver additional initrds to a UKI without modifying + its contents. The generic "addon" handling has been generalized + so that all UKI sidecar artifacts (initrds, command-line + overlays, devicetree blobs, etc.) follow the same lookup rules. + + * systemd-boot will never auto-boot a non-default UKI profile, + preventing accidental boots into alternative profiles after a + single timeout expiry. + + * El Torito CDROM boot catalog partition UUIDs are now discovered + and exposed via the same mechanism as GPT/MBR partitions, + enabling unified ISO image dissection. + + * bootctl gained a new 'link' verb (with a matching Varlink API) + that installs a UKI on the ESP by symlinking it from + /usr/lib/modules/ instead of copying. A new + '--print-efi-architecture' option prints the EFI architecture + identifier of the running system, which is useful from packaging + scripts. + + Changes in systemd-repart: + + * A new EncryptKDF= setting controls the KDF used for LUKS2 + partitions (e.g. argon2id, argon2i, pbkdf2). + + * A new VolumeName= setting allows specifying the LUKS2 volume + name independently of the on-disk partition label. + + * A new BlockDeviceReplace= setting allows partitions to + atomically replace the contents of an existing block device. + + * A new --grain-size= command line option overrides the alignment + granularity used when placing partitions. + + * A new --el-torito= command line option causes a minimal El + Torito boot catalog to be written for EFI boot on hybrid ISO + images. + + * --shrink now uses mkfs.btrfs's native minimal-filesystem support + when available. + + * A new persistent activation flag for LUKS2 partitions causes the + allow-discards option to be persisted in the LUKS2 header. + + Changes in systemd-sysupdate: + + * Partial-and-pending UpdateSet states are now correctly recognized + in additional code paths, and partial versions may be returned + as the next candidate as well as targeted by vacuuming. + + * systemd-sysupdate now emits READY=1 via sd_notify() after the + install step completes, allowing for tighter integration with + orchestration tooling. + + * systemd-sysupdate is now installed in /usr/bin/ alongside the + other user-facing tools, as it is no longer considered experimental. + + Changes in systemd-nspawn, systemd-vmspawn, systemd-machined: + + * systemd-nspawn now supports persisting the payload's system manager + FD Store by receiving FDs via the notify socket, and passing them + down via $SLISTEN_FDS when the container is restarted, when the + 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' + options are set in the unit inside which systemd-nspawn is running. + Combined with the LUO support in PID1, this lets containers persist + state (e.g.: memfds) across not only container restarts, but also + kexec reboots. + + * systemd-nspawn gained new --forward-journal= and + --forward-journal-NAME= options to forward journal entries from + the payload to specified journal sockets. + + * systemd-vmspawn gained a new --bind-volume= option that binds host + paths into the VM. + + * systemd-vmspawn gained a new --cxl= option that configures CXL + memory devices and adds support for memory hotplug. + + * systemd-vmspawn gained a new --console-transport= option that + controls how the VM console is presented (PTY, native, headless, + etc.); a PTY is now provided for the native console mode, and + headless console operation is supported. + + * systemd-vmspawn gained a new --efi-nvram-template= option that + selects the EFI variable store template. + + * systemd-vmspawn gained a new --firmware-features= option that + enables or disables individual firmware features (with a + "~feature" prefix for negation). + + * systemd-vmspawn now supports direct kernel boot without UEFI + firmware. + + * systemd-vmspawn gained support for new disk types 'nvme', + 'virtio-scsi' and 'scsi-cd' (for ISO/CD-ROM images). + + * systemd-vmspawn now exposes a QMP-to-Varlink bridge that makes + the running QEMU instance reachable to other tools at runtime. + + * The io.systemd.MachineInstance Varlink interface gained + AddStorage(), RemoveStorage() and ReplaceStorage() methods for + runtime storage manipulation, implemented by systemd-vmspawn. + + * systemd-vmspawn now pre-allocates PCIe root ports to allow PCIe + device hotplug, with multifunction packing where supported. + + * systemd-vmspawn now uses the QEMU built-in vdagent (clipboard, + resolution sync) instead of spicevmc. + + * systemd-vmspawn now searches XDG_DATA_DIRS for QEMU firmware + descriptors. + + * systemd-vmspawn gained a new --print-profiles command that falls + back to a non-JSON representation when the output is not JSON. + + * systemd-vmspawn's --grow-image now detects and rejects qcow2 + images, where the operation is not supported. + + * systemd-vmspawn now propagates the host TERM environment variable + into the VM. + + * A new 'storagectl' command line tool and an accompanying + io.systemd.StorageProvider Varlink interface have been added, + alongside the new generic providers systemd-storage-fs@.service and + systemd-storage-block@.service. These allow exposing storage + resources (filesystems, block devices) in a unified manner for use + as managed user storage. + + * systemd-machined Machine.List/Register output now includes a + 'controlAddress' field describing the manager's bus address, + where known. + + * Querying metadata of registered machines is now gated behind + dedicated polkit actions + (org.freedesktop.machine1.inspect-machines and inspect-images). + + * machinectl gained 'bind-volume' / 'unbind-volume' verbs to + manage runtime bind mounts of host paths into running machines, + and new verbs to control the lifecycle of VMs (pause, resume, + power-off, etc.) via the io.systemd.MachineInstance Varlink + interface. + + Changes in systemd-coredump and coredumpctl: + + * 'coredumpctl info' has gained JSON output (--json=). + + * The crashing thread's TID and name are now captured and + recorded alongside the existing PID/comm metadata. + + Changes in systemd-logind: + + * A new io.systemd.Shutdown Varlink interface has been introduced + to request system shutdown. The peer connection identifier of + the requester is logged. + + Changes in systemd-creds, systemd-cryptsetup and + systemd-cryptenroll: + + * systemd-creds only locks against the public-key TPM2 PCR when + booting on UEFI firmware that supports TPMs, avoiding spurious + errors on systems without TPM. + + * libcryptsetup is now loaded via dlopen() in the cryptsetup + binaries, eliminating the hard runtime dependency for systems that do + not actually use it. + + Changes in libsystemd: + + * A new public 'sd-dlopen' header-only API has been added that + provides macros (SD_ELF_NOTE_DLOPEN()) for annotating dlopen'd + dependencies via the UAPI.12 ELF metadata specification + (https://uapi-group.org/specifications/specs/elf_dlopen_metadata/). + This header is licensed under MIT-0 to facilitate embedding it + directly in other projects. + + * A new 'sd_json_parse_fd' API is now available to facilitate parsing + FDs out of Varlink connections. + + * sd-varlink gained a protocol upgrade mechanism, exposed via the + new sd_varlink_call_and_upgrade() and + sd_varlink_reply_and_upgrade() API. Internally the upgrade fd + handling and MSG_PEEK semantics for upgradable sockets have + been reworked, and the upgrade API always returns two file + descriptors. + + * The 'ret' argument of sd_varlink_idl_parse() is now optional. + + * sd-varlink's per-UID connection limit has been scaled down to + 128. + + * Enumeration types have been introduced throughout the + well-known Varlink interfaces: ManagedOOMMode in + io.systemd.oom; class and whom in io.systemd.Machine; + configuration, scheduling and mount settings in + io.systemd.Unit; configuration settings in io.systemd.Manager. + + * varlinkctl gained a new 'serve' verb that wraps an arbitrary + command as a Varlink server, and a new '--upgrade' option + (along with '--exec') to consume the protocol upgrade API. + + * A new JsonStream transport-layer module has been added for + consumers building higher-level JSON-over-stream protocols on + top of sd-json. + + * sd-path now exposes an XDG 'projects' user directory. + + * sd-device gained a number of helpers, including + sd_device_get_sysattr_safe_string(), sd_device_get_sysattr_u8(), + and sd_device_get_sysattr_u16(). + + Other changes: + + * A new systemd-imdsd service has been introduced that makes cloud + Instance Metadata Service (IMDS) data accessible locally. It is + accompanied by a 'systemd-imds' client tool, a generator that hooks + IMDS retrieval into cloud guests, a hwdb database describing basic + IMDS endpoints for known clouds (including AWS, Azure, Google + Cloud, Oracle Cloud, Tencent Cloud and Alibaba ECS), and TPM + measurements of the retrieved data so that IMDS-provided values can + be used as attestation inputs. Networking to cloud IMDS services + may also be locked down for recognized clouds; the new meson option + "-Dimds-network=" can change the default mode to "locked" at build + time. This is recommended for secure installations, but typically + conflicts with traditional IMDS clients such as cloud-init, which + require direct IMDS access. + + * The systemd-report framework introduced in v260 has been + substantially extended. Basic system metrics + (PhysicalMemoryBytes, CPUsOnline) are now provided by a new + systemd-report-basic@.service that is enabled by default via its + report-basic.socket activation unit. Per-cgroup metrics (CPU time, + etc.) and per-service metrics are exposed through dedicated Varlink + services. systemd-report gained the ability to upload collected + reports via a "varlink socket directory" of HTTP destinations, and + to inject custom HTTP headers when doing so. + + * 'systemctl kexec' gained a new --kernel-cmdline= argument that + overrides the kernel command line for kexec invocations. + + * 'systemctl kexec' now prefers invoking the 'kexec_file_load' system + call directly, and uses the 'kexec' binary only as a fallback if + that is not available, so that on most systems the dependency on + 'kexec-tools' is no longer necessary. + + * fstab-generator now supports swap on network block devices. + + * libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk + and libcryptsetup are now consistently loaded via dlopen() + throughout the code base, further reducing the set of mandatory + dependencies from all binaries. + + * The unused dependency on libgpg-error has been dropped. + CHANGES WITH 260: Feature Removals and Incompatible Changes: From b6e160232c970d275400ce0c45050fe0d0860711 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:16:44 +0100 Subject: [PATCH 1931/2155] Update hwdb ninja -C build update-hwdb --- hwdb.d/20-OUI.hwdb | 1723 +++- hwdb.d/20-acpi-vendor.hwdb | 3 + hwdb.d/20-acpi-vendor.hwdb.patch | 96 +- hwdb.d/20-pci-vendor-model.hwdb | 2763 +++++-- hwdb.d/acpi_id_registry.csv | 303 +- hwdb.d/ma-large.txt | 12334 +++++++++++++++++------------ hwdb.d/ma-medium.txt | 741 +- hwdb.d/ma-small.txt | 861 +- hwdb.d/pci.ids | 1536 ++-- hwdb.d/pnp_id_registry.csv | 5114 ++++++------ 10 files changed, 16362 insertions(+), 9112 deletions(-) diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index ace393450eda4..d379b04bca170 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -2658,7 +2658,7 @@ OUI:000373* ID_OUI_FROM_DATABASE=Aselsan A.S OUI:000374* - ID_OUI_FROM_DATABASE=Control Microsystems + ID_OUI_FROM_DATABASE=Schneider Electric OUI:000375* ID_OUI_FROM_DATABASE=NetMedia, Inc. @@ -3945,7 +3945,7 @@ OUI:000520* ID_OUI_FROM_DATABASE=Smartronix, Inc. OUI:000521* - ID_OUI_FROM_DATABASE=Control Microsystems + ID_OUI_FROM_DATABASE=Schneider Electric OUI:000522* ID_OUI_FROM_DATABASE=LEA*D Corporation, Inc. @@ -5883,7 +5883,7 @@ OUI:0007A6* ID_OUI_FROM_DATABASE=Leviton Manufacturing Co., Inc. OUI:0007A7* - ID_OUI_FROM_DATABASE=A-Z Inc. + ID_OUI_FROM_DATABASE=Glory Technical Solutions Co., Ltd. OUI:0007A8* ID_OUI_FROM_DATABASE=Haier Group Technologies Ltd @@ -31487,6 +31487,9 @@ OUI:003C10* OUI:003C84* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:003CB7* + ID_OUI_FROM_DATABASE=AzureWave Technology Inc. + OUI:003CC5* ID_OUI_FROM_DATABASE=WONWOO Engineering Co., Ltd @@ -37991,6 +37994,9 @@ OUI:00CBB4* OUI:00CBBD* ID_OUI_FROM_DATABASE=Cambridge Broadband Networks Group +OUI:00CC05* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:00CC34* ID_OUI_FROM_DATABASE=Juniper Networks @@ -40070,6 +40076,9 @@ OUI:041B94* OUI:041BBA* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:041C6C* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:041CDB* ID_OUI_FROM_DATABASE=Siba Service @@ -40646,6 +40655,9 @@ OUI:047E4A* OUI:047F0E* ID_OUI_FROM_DATABASE=Barrot Technology Co.,LTD +OUI:04801A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:0480A7* ID_OUI_FROM_DATABASE=ShenZhen TianGang Micro Technology CO.LTD @@ -41019,7 +41031,7 @@ OUI:04C3E63* ID_OUI_FROM_DATABASE=Extech Electronics Co., LTD. OUI:04C3E64* - ID_OUI_FROM_DATABASE=Innovusion Inc. + ID_OUI_FROM_DATABASE=Seyond OUI:04C3E65* ID_OUI_FROM_DATABASE=Invasys @@ -42128,6 +42140,9 @@ OUI:0823B2* OUI:0823C6* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:08240B* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:082522* ID_OUI_FROM_DATABASE=ADVANSEE @@ -42551,6 +42566,12 @@ OUI:086332E* OUI:086361* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:08638A* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + +OUI:086480* + ID_OUI_FROM_DATABASE=Black Sesame Technologies Co., Ltd + OUI:086518* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -42636,7 +42657,7 @@ OUI:087572* ID_OUI_FROM_DATABASE=Obelux Oy OUI:087618* - ID_OUI_FROM_DATABASE=ViE Technologies Sdn. Bhd. + ID_OUI_FROM_DATABASE=ViTrox Technologies Sdn. Bhd OUI:087671* ID_OUI_FROM_DATABASE=Juniper Networks @@ -42659,6 +42680,9 @@ OUI:087999* OUI:087A4C* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:087B0F* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:087B12* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -42680,6 +42704,9 @@ OUI:087CBE* OUI:087D21* ID_OUI_FROM_DATABASE=Altasec technology corporation +OUI:087D60* + ID_OUI_FROM_DATABASE=SAMJIN Co.ltd + OUI:087E64* ID_OUI_FROM_DATABASE=Vantiva USA LLC @@ -42884,6 +42911,9 @@ OUI:08ACA5* OUI:08ACC4* ID_OUI_FROM_DATABASE=FMTech +OUI:08AD0A* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:08AED6* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -43127,6 +43157,9 @@ OUI:08DA33E* OUI:08DD03* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:08DD82* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:08DDEB* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -43406,6 +43439,9 @@ OUI:0C01DB* OUI:0C0227* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:0C025B* + ID_OUI_FROM_DATABASE=Microchip Technology Inc. + OUI:0C02BD* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -43433,6 +43469,21 @@ OUI:0C0CEA* OUI:0C0E76* ID_OUI_FROM_DATABASE=D-Link International +OUI:0C0EC10* + ID_OUI_FROM_DATABASE=Spintronica LLC + +OUI:0C0EC11* + ID_OUI_FROM_DATABASE=DELTACAST.TV + +OUI:0C0EC13* + ID_OUI_FROM_DATABASE=Lupa Tecnologia e Sistemas Ltda + +OUI:0C0EC16* + ID_OUI_FROM_DATABASE=COGITO TECH COMPANY LIMITED + +OUI:0C0EC1B* + ID_OUI_FROM_DATABASE=tecget GmbH + OUI:0C0ECB* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -43532,6 +43583,9 @@ OUI:0C238D* OUI:0C2576* ID_OUI_FROM_DATABASE=LONGCHEER TELECOMMUNICATION LIMITED +OUI:0C2643* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:0C2724* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -43613,6 +43667,9 @@ OUI:0C37DC* OUI:0C383E* ID_OUI_FROM_DATABASE=Fanvil Technology Co., Ltd. +OUI:0C393D* + ID_OUI_FROM_DATABASE=eero inc. + OUI:0C3956* ID_OUI_FROM_DATABASE=Observator instruments @@ -43772,6 +43829,9 @@ OUI:0C53B7* OUI:0C5415* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:0C5427* + ID_OUI_FROM_DATABASE=Dongguan Huayin Electronic Technology Co., Ltd. + OUI:0C54A5* ID_OUI_FROM_DATABASE=PEGATRON CORPORATION @@ -44972,6 +45032,9 @@ OUI:0CFE5DE* OUI:0CFE7B* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:0CFEE5* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:100000* ID_OUI_FROM_DATABASE=Private @@ -45785,6 +45848,9 @@ OUI:1078CE* OUI:1078D2* ID_OUI_FROM_DATABASE=Elitegroup Computer Systems Co.,Ltd. +OUI:107A2A* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:107A86* ID_OUI_FROM_DATABASE=U&U ENGINEERING INC. @@ -45944,6 +46010,9 @@ OUI:1098C3* OUI:109AB9* ID_OUI_FROM_DATABASE=Tosibox Oy +OUI:109ABA* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:109ADD* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -45959,6 +46028,9 @@ OUI:109D9C* OUI:109E3A* ID_OUI_FROM_DATABASE=Zhejiang Tmall Technology Co., Ltd. +OUI:109E6B* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:109F41* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -46043,6 +46115,9 @@ OUI:10AEA5* OUI:10AF78* ID_OUI_FROM_DATABASE=Shenzhen ATUE Technology Co., Ltd +OUI:10B06E* + ID_OUI_FROM_DATABASE=Shenzhen Phaten Tech. LTD + OUI:10B1DF* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -46439,6 +46514,9 @@ OUI:10F068* OUI:10F163* ID_OUI_FROM_DATABASE=TNK CO.,LTD +OUI:10F1C7* + ID_OUI_FROM_DATABASE=Tachyon Networks Inc + OUI:10F1F2* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) @@ -46856,6 +46934,9 @@ OUI:14373B* OUI:14375E* ID_OUI_FROM_DATABASE=Symbotic LLC +OUI:1438FA* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:14392F* ID_OUI_FROM_DATABASE=LEAR @@ -46883,6 +46964,9 @@ OUI:143E60* OUI:143EBF* ID_OUI_FROM_DATABASE=zte corporation +OUI:143EC2* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:143F27* ID_OUI_FROM_DATABASE=Noccela Oy @@ -47591,6 +47675,9 @@ OUI:14C67D* OUI:14C697* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:14C7C4* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:14C88B* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -47648,6 +47735,9 @@ OUI:14D424* OUI:14D4FE* ID_OUI_FROM_DATABASE=Commscope +OUI:14D537* + ID_OUI_FROM_DATABASE=All Inspire Health Inc. + OUI:14D5C6* ID_OUI_FROM_DATABASE=slash dev slash agents, inc @@ -48632,6 +48722,9 @@ OUI:1889DF* OUI:188A6A* ID_OUI_FROM_DATABASE=AVPro Global Hldgs +OUI:188AF1* + ID_OUI_FROM_DATABASE=LEDVANCE, LLC + OUI:188B0E* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -49407,7 +49500,7 @@ OUI:18FDCB2* ID_OUI_FROM_DATABASE=Cabtronix AG OUI:18FDCB3* - ID_OUI_FROM_DATABASE=Staclar, Inc. + ID_OUI_FROM_DATABASE=Blahaj Studio OUI:18FDCB4* ID_OUI_FROM_DATABASE=Gosuncn Technology Group Co.,LTD. @@ -49643,6 +49736,9 @@ OUI:1C232C* OUI:1C234F* ID_OUI_FROM_DATABASE=EDMI Europe Ltd +OUI:1C23A2* + ID_OUI_FROM_DATABASE=FRITZ! Technology GmbH + OUI:1C24CD* ID_OUI_FROM_DATABASE=ASKEY COMPUTER CORP @@ -49673,6 +49769,9 @@ OUI:1C2AB0* OUI:1C2CE0* ID_OUI_FROM_DATABASE=Shanghai Mountain View Silicon +OUI:1C2D60* + ID_OUI_FROM_DATABASE=Extreme Networks Headquarters + OUI:1C2E1B* ID_OUI_FROM_DATABASE=Suzhou Tremenet Communication Technology Co., Ltd. @@ -49778,6 +49877,9 @@ OUI:1C4190* OUI:1C427D* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:1C42C2* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:1C4363* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -49946,6 +50048,9 @@ OUI:1C5A3E* OUI:1C5A6B* ID_OUI_FROM_DATABASE=Philips Electronics Nederland BV +OUI:1C5BA2* + ID_OUI_FROM_DATABASE=HP GLOBALES MEXICO + OUI:1C5C55* ID_OUI_FROM_DATABASE=PRIMA Cinema, Inc @@ -50768,6 +50873,9 @@ OUI:1CB9C4* OUI:1CBA8C* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:1CBAB8* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:1CBBA8* ID_OUI_FROM_DATABASE=OJSC Ufimskiy Zavod Promsvyaz @@ -50954,6 +51062,9 @@ OUI:1CD1D7* OUI:1CD1E0* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:1CD21E* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:1CD3AF* ID_OUI_FROM_DATABASE=LG Innotek @@ -51386,6 +51497,9 @@ OUI:201F3B* OUI:201F54* ID_OUI_FROM_DATABASE=Raisecom Technology CO., LTD +OUI:201F55* + ID_OUI_FROM_DATABASE=DJI Osmo Technology Co., Ltd. + OUI:202027* ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited @@ -51671,6 +51785,9 @@ OUI:204E7F* OUI:204EF6* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:20500D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:20500F* ID_OUI_FROM_DATABASE=Fiber Groep B.V. @@ -52007,6 +52124,9 @@ OUI:209339* OUI:20934D* ID_OUI_FROM_DATABASE=FUJIAN STAR-NET COMMUNICATION CO.,LTD +OUI:209395* + ID_OUI_FROM_DATABASE=nVent + OUI:20968A* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. @@ -52067,6 +52187,9 @@ OUI:20A2E4* OUI:20A2E7* ID_OUI_FROM_DATABASE=Lee-Dickens Ltd +OUI:20A366* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:20A5CB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -52124,6 +52247,9 @@ OUI:20AC9C* OUI:20AD56* ID_OUI_FROM_DATABASE=AUMOVIO Systems, Inc. +OUI:20AEB6* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:20AF1B* ID_OUI_FROM_DATABASE=SteelSeries ApS @@ -52134,7 +52260,7 @@ OUI:20B0F7* ID_OUI_FROM_DATABASE=Enclustra GmbH OUI:20B37F0* - ID_OUI_FROM_DATABASE=Chelsio Communications Inc + ID_OUI_FROM_DATABASE=B810 SPA OUI:20B37F1* ID_OUI_FROM_DATABASE=TDK-Lambda UK @@ -52167,7 +52293,7 @@ OUI:20B37FA* ID_OUI_FROM_DATABASE=ShenZhen C&D Electronics CO.Ltd. OUI:20B37FB* - ID_OUI_FROM_DATABASE=B810 SPA + ID_OUI_FROM_DATABASE=Shenzhen Hengbang Xinchuang Technology Co.,Ltd OUI:20B37FC* ID_OUI_FROM_DATABASE=EGSTON Power Electronics GmbH @@ -52175,6 +52301,9 @@ OUI:20B37FC* OUI:20B37FD* ID_OUI_FROM_DATABASE=Xunmu Information Technology (Shanghai) Co., Ltd. +OUI:20B37FE* + ID_OUI_FROM_DATABASE=Kawasaki Thermal Engineering Co.,Ltd. + OUI:20B399* ID_OUI_FROM_DATABASE=Enterasys @@ -52418,6 +52547,9 @@ OUI:20E407* OUI:20E46F* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:20E525* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:20E52A* ID_OUI_FROM_DATABASE=NETGEAR @@ -52736,6 +52868,9 @@ OUI:241A8C* OUI:241AE6* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:241AF7* + ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD + OUI:241B13* ID_OUI_FROM_DATABASE=Shanghai Nutshell Electronic Co., Ltd. @@ -52868,6 +53003,9 @@ OUI:243408* OUI:2435CC* ID_OUI_FROM_DATABASE=Zhongshan Scinan Internet of Things Co.,Ltd. +OUI:243672* + ID_OUI_FROM_DATABASE=AMPAK Technology Inc. + OUI:2436DA* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -53006,6 +53144,9 @@ OUI:244E7BD* OUI:244E7BE* ID_OUI_FROM_DATABASE=WithWin Technology ShenZhen CO.,LTD +OUI:244ECD* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:244F1D* ID_OUI_FROM_DATABASE=iRule LLC @@ -53153,6 +53294,9 @@ OUI:2462C6* OUI:2462CE* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:246404* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:246477* ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd @@ -53168,6 +53312,9 @@ OUI:246511* OUI:2465E1* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:246800* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:246830* ID_OUI_FROM_DATABASE=Shenzhen Shokzhear Co., Ltd @@ -53672,6 +53819,9 @@ OUI:24BA13* OUI:24BA30* ID_OUI_FROM_DATABASE=Technical Consumer Products, Inc. +OUI:24BA79* + ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd + OUI:24BBC1* ID_OUI_FROM_DATABASE=Absolute Analysis @@ -54059,6 +54209,9 @@ OUI:280245* OUI:2802D8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:28047A* + ID_OUI_FROM_DATABASE=WNC Corporation + OUI:2804C6* ID_OUI_FROM_DATABASE=Wanan Hongsheng Electronic Co.Ltd @@ -54360,7 +54513,7 @@ OUI:2836136* ID_OUI_FROM_DATABASE=ESI Ventures, LLC OUI:2836137* - ID_OUI_FROM_DATABASE=shenzhen technology limited + ID_OUI_FROM_DATABASE=SHENZHEN OFEIXIN TECHNOLOGY LIMITED OUI:2836138* ID_OUI_FROM_DATABASE=Fuzhou Lesi Intelligent Technology Co., Ltd @@ -54641,6 +54794,9 @@ OUI:286847* OUI:2868D2* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:286926* + ID_OUI_FROM_DATABASE=OPTOKON, a.s. + OUI:286AB8* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -54689,6 +54845,9 @@ OUI:287184* OUI:2872C5* ID_OUI_FROM_DATABASE=Smartmatic Corp +OUI:2872C6* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:2872F0* ID_OUI_FROM_DATABASE=ATHENA @@ -54788,6 +54947,9 @@ OUI:28875F* OUI:288761* ID_OUI_FROM_DATABASE=LG Innotek +OUI:2887AF* + ID_OUI_FROM_DATABASE=Advantech Technology (CHINA) Co., Ltd. + OUI:2887BA* ID_OUI_FROM_DATABASE=TP-Link Systems Inc @@ -54809,6 +54971,9 @@ OUI:288EEC* OUI:288FF6* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:289104* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:289176* ID_OUI_FROM_DATABASE=Indyme Solutions, LLC @@ -55625,6 +55790,9 @@ OUI:2C0369* OUI:2C0547* ID_OUI_FROM_DATABASE=Shenzhen Phaten Tech. LTD +OUI:2C0613* + ID_OUI_FROM_DATABASE=China Mobile Group Device Co.,Ltd. + OUI:2C0623* ID_OUI_FROM_DATABASE=Win Leader Inc. @@ -56132,6 +56300,9 @@ OUI:2C4881* OUI:2C4A11* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:2C4B14* + ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD + OUI:2C4C15* ID_OUI_FROM_DATABASE=Juniper Networks @@ -57110,6 +57281,9 @@ OUI:2CE38E* OUI:2CE412* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS +OUI:2CE64D* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:2CE6CC* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -57257,9 +57431,15 @@ OUI:30074D* OUI:30075C* ID_OUI_FROM_DATABASE=43403 +OUI:3007A3* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + OUI:30084D* ID_OUI_FROM_DATABASE=Trumpf Hüttinger +OUI:300916* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:3009C0* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -57407,6 +57587,9 @@ OUI:30144A* OUI:301518* ID_OUI_FROM_DATABASE=Ubiquitous Communication Co. ltd. +OUI:301577* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:30168D* ID_OUI_FROM_DATABASE=ProLon @@ -57807,7 +57990,7 @@ OUI:3049509* ID_OUI_FROM_DATABASE=Shanghai gatang technology CO.,LTD OUI:304950A* - ID_OUI_FROM_DATABASE=Ledworks SRL + ID_OUI_FROM_DATABASE=Illucere Srl OUI:304950B* ID_OUI_FROM_DATABASE=HANGZHOU EV-TECH CO.,LTD @@ -57878,6 +58061,9 @@ OUI:30525A* OUI:3052CB* ID_OUI_FROM_DATABASE=Liteon Technology Corporation +OUI:30535B* + ID_OUI_FROM_DATABASE=Shenzhen Comnect Technology Co.,LTD + OUI:3053C1* ID_OUI_FROM_DATABASE=CRESYN @@ -57980,6 +58166,9 @@ OUI:306A85* OUI:306CBE* ID_OUI_FROM_DATABASE=Skymotion Technology (HK) Limited +OUI:306D34* + ID_OUI_FROM_DATABASE=Wu Qi Technologies,Inc. + OUI:306DF9* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -58118,6 +58307,9 @@ OUI:3089A6* OUI:3089D3* ID_OUI_FROM_DATABASE=HONGKONG UCLOUDLINK NETWORK TECHNOLOGY LIMITED +OUI:3089EC* + ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd + OUI:308AF7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -58385,6 +58577,9 @@ OUI:30C7AE* OUI:30C82A* ID_OUI_FROM_DATABASE=WI-BIZ srl +OUI:30C8A2* + ID_OUI_FROM_DATABASE=SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + OUI:30C91B* ID_OUI_FROM_DATABASE=Zhen Shi Information Technology(Shanghai)Co.,Ltd. @@ -58844,6 +59039,9 @@ OUI:34105D* OUI:3410BE* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:3410D0* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:3410F4* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -58934,6 +59132,9 @@ OUI:3420E3* OUI:342109* ID_OUI_FROM_DATABASE=Jensen Scandinavia AS +OUI:3422CF* + ID_OUI_FROM_DATABASE=AUMOVIO Systems, Inc. + OUI:342387* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. @@ -58952,6 +59153,9 @@ OUI:3425B4* OUI:3425BE* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:342601* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:342606* ID_OUI_FROM_DATABASE=CarePredict, Inc. @@ -58964,6 +59168,9 @@ OUI:342792* OUI:342840* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:342844* + ID_OUI_FROM_DATABASE=Kyung In Electronics + OUI:342865* ID_OUI_FROM_DATABASE=Juniper Networks @@ -59231,6 +59438,9 @@ OUI:344DF7* OUI:344E2F* ID_OUI_FROM_DATABASE=LEAR +OUI:344EE2* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:344F3F* ID_OUI_FROM_DATABASE=IO-Power Technology Co., Ltd. @@ -59549,6 +59759,9 @@ OUI:34885D* OUI:348A12* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:348A3B* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:348A7B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -59864,6 +60077,9 @@ OUI:34C103* OUI:34C1E9* ID_OUI_FROM_DATABASE=Ulak Communications Inc. +OUI:34C232* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:34C3AC* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -60146,6 +60362,9 @@ OUI:34DD04* OUI:34DD7E* ID_OUI_FROM_DATABASE=Umeox Innovations Co.,Ltd +OUI:34DDCC* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:34DE1A* ID_OUI_FROM_DATABASE=Intel Corporate @@ -60299,6 +60518,9 @@ OUI:34F015* OUI:34F043* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:34F084* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:34F0CA* ID_OUI_FROM_DATABASE=Shenzhen Linghangyuan Digital Technology Co.,Ltd. @@ -60335,6 +60557,9 @@ OUI:34F716* OUI:34F86E* ID_OUI_FROM_DATABASE=Parker Hannifin Corporation +OUI:34F8DD* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:34F8E7* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -60539,6 +60764,9 @@ OUI:381428* OUI:38144E* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:3814A1* + ID_OUI_FROM_DATABASE=LG Innotek + OUI:38165A* ID_OUI_FROM_DATABASE=zte corporation @@ -60722,6 +60950,9 @@ OUI:382B78* OUI:382C4A* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. +OUI:382CDB* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:382CE5* ID_OUI_FROM_DATABASE=Tuya Smart Inc. @@ -60758,6 +60989,9 @@ OUI:38384B* OUI:3838A6* ID_OUI_FROM_DATABASE=Arista Networks +OUI:383904* + ID_OUI_FROM_DATABASE=ittim + OUI:38396C* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -60902,6 +61136,9 @@ OUI:384C90* OUI:384DD2* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:384E56* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:384F49* ID_OUI_FROM_DATABASE=Juniper Networks @@ -61160,6 +61397,9 @@ OUI:388602* OUI:3886F7* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:38879C* + ID_OUI_FROM_DATABASE=Ei Electronics + OUI:3887D5* ID_OUI_FROM_DATABASE=Intel Corporate @@ -61527,7 +61767,7 @@ OUI:38B3F7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. OUI:38B4D3* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:38B54D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -61859,6 +62099,9 @@ OUI:38F3AB* OUI:38F3FB* ID_OUI_FROM_DATABASE=Asperiq +OUI:38F406* + ID_OUI_FROM_DATABASE=Jinan USR IOT Technology Limited + OUI:38F45E* ID_OUI_FROM_DATABASE=H1-Radio co.,ltd @@ -62033,6 +62276,9 @@ OUI:38FF13* OUI:38FF36* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:38FF59* + ID_OUI_FROM_DATABASE=Dell Inc. + OUI:3C01EF* ID_OUI_FROM_DATABASE=Sony Corporation @@ -62150,6 +62396,9 @@ OUI:3C13CC* OUI:3C1512* ID_OUI_FROM_DATABASE=Shenzhen Huanhu Technology Co.,Ltd +OUI:3C155A* + ID_OUI_FROM_DATABASE=Nokia + OUI:3C15C2* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -62300,6 +62549,9 @@ OUI:3C286D* OUI:3C28A6* ID_OUI_FROM_DATABASE=ALE International +OUI:3C2983* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:3C2AB3* ID_OUI_FROM_DATABASE=Telesystem communications Pte Ltd @@ -62366,6 +62618,9 @@ OUI:3C3464* OUI:3C3556* ID_OUI_FROM_DATABASE=Cognitec Systems GmbH +OUI:3C3558* + ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + OUI:3C3576* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -62384,6 +62639,9 @@ OUI:3C3712* OUI:3C3786* ID_OUI_FROM_DATABASE=NETGEAR +OUI:3C381F* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:3C3824* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -62774,6 +63032,9 @@ OUI:3C6FF7* OUI:3C7059* ID_OUI_FROM_DATABASE=MakerBot Industries +OUI:3C714B* + ID_OUI_FROM_DATABASE=HUMAX NETWORKS + OUI:3C71BF* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -62891,6 +63152,9 @@ OUI:3C8AB0* OUI:3C8AE5* ID_OUI_FROM_DATABASE=Tensun Information Technology(Hangzhou) Co.,LTD +OUI:3C8B6E* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:3C8B7F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -62999,6 +63263,9 @@ OUI:3C9FC3* OUI:3C9FCD* ID_OUI_FROM_DATABASE=Shenzhen Neoway Technology Co.,Ltd. +OUI:3CA00E* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + OUI:3CA067* ID_OUI_FROM_DATABASE=Liteon Technology Corporation @@ -63011,6 +63278,9 @@ OUI:3CA10D* OUI:3CA161* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:3CA239* + ID_OUI_FROM_DATABASE=DGSQ Co.,Ltd + OUI:3CA2C3* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -63197,6 +63467,9 @@ OUI:3CC683* OUI:3CC786* ID_OUI_FROM_DATABASE=DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. +OUI:3CC801* + ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited + OUI:3CC99E* ID_OUI_FROM_DATABASE=Huiyang Technology Co., Ltd @@ -63596,6 +63869,9 @@ OUI:400E67* OUI:400E85* ID_OUI_FROM_DATABASE=SAMSUNG ELECTRO-MECHANICS(THAILAND) +OUI:400EB9* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:400EF3* ID_OUI_FROM_DATABASE=zte corporation @@ -63656,6 +63932,9 @@ OUI:4011C3* OUI:4011DC* ID_OUI_FROM_DATABASE=Sonance +OUI:401277* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:4012E4* ID_OUI_FROM_DATABASE=Compass-EOS @@ -64466,6 +64745,9 @@ OUI:40A5EF* OUI:40A63D* ID_OUI_FROM_DATABASE=SignalFire Telemetry +OUI:40A654* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:40A677* ID_OUI_FROM_DATABASE=Juniper Networks @@ -64553,6 +64835,9 @@ OUI:40B4CD* OUI:40B4F0* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:40B570* + ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. + OUI:40B5C1* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -64814,6 +65099,9 @@ OUI:40ECF8* OUI:40ED00* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:40ED7B* + ID_OUI_FROM_DATABASE=Zscaler + OUI:40ED980* ID_OUI_FROM_DATABASE=Tsinghua Tongfang Co., LTD @@ -65198,6 +65486,9 @@ OUI:441D64* OUI:441DB1* ID_OUI_FROM_DATABASE=APTIV SERVICES US, LLC +OUI:441DE5* + ID_OUI_FROM_DATABASE=XCENA Inc. + OUI:441E91* ID_OUI_FROM_DATABASE=ARVIDA Intelligent Electronics Technology Co.,Ltd. @@ -65267,6 +65558,9 @@ OUI:44303F* OUI:443192* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:44321D* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:44322A* ID_OUI_FROM_DATABASE=Avaya Inc @@ -65378,6 +65672,9 @@ OUI:44422F* OUI:444450* ID_OUI_FROM_DATABASE=OttoQ +OUI:444520* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:44456F* ID_OUI_FROM_DATABASE=SHENZHEN ONEGA TECHNOLOGY CO.,LTD @@ -65411,6 +65708,9 @@ OUI:444963* OUI:444988* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:4449C0* + ID_OUI_FROM_DATABASE=NVIDIA Corporation + OUI:444A37* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -65684,6 +65984,9 @@ OUI:447831* OUI:44783E* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:447B45* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:447BBB* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd @@ -65822,6 +66125,9 @@ OUI:44962B* OUI:44975A* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD +OUI:44995B* + ID_OUI_FROM_DATABASE=GX India Pvt Ltd + OUI:449A52* ID_OUI_FROM_DATABASE=zte corporation @@ -66038,6 +66344,9 @@ OUI:44BA46* OUI:44BB3B* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:44BD8D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:44BDC8* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -66104,9 +66413,15 @@ OUI:44CB8B* OUI:44CBAD* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:44CC6E* + ID_OUI_FROM_DATABASE=Rockwell Automation + OUI:44CD0E* ID_OUI_FROM_DATABASE=FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. +OUI:44CE1D* + ID_OUI_FROM_DATABASE=Nokia + OUI:44CE3A* ID_OUI_FROM_DATABASE=Jiangsu Huacun Electronic Technology Co., Ltd. @@ -66335,6 +66650,9 @@ OUI:44EE14* OUI:44EE30* ID_OUI_FROM_DATABASE=Budelmann Elektronik GmbH +OUI:44EF26* + ID_OUI_FROM_DATABASE=Qingdao Intelligent&Precise Electronics Co.,Ltd. + OUI:44EFBF* ID_OUI_FROM_DATABASE=China Dragon Technology Limited @@ -66494,6 +66812,12 @@ OUI:4808EBD* OUI:4808EBE* ID_OUI_FROM_DATABASE=uniline energy systems pvt ltd +OUI:480951* + ID_OUI_FROM_DATABASE=Guangzhou Trustmo Information System Co.,LTD + +OUI:480A28* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:480BB20* ID_OUI_FROM_DATABASE=Ridango AS @@ -66566,6 +66890,9 @@ OUI:48128F* OUI:48137E* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:481389* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:4813F3* ID_OUI_FROM_DATABASE=BBK EDUCATIONAL ELECTRONICS CORP.,LTD. @@ -67265,6 +67592,9 @@ OUI:489507* OUI:4896D9* ID_OUI_FROM_DATABASE=zte corporation +OUI:4898AB* + ID_OUI_FROM_DATABASE=Wistron InfoComm(Chongqing)Co.,Ltd. + OUI:4898CA* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -67793,6 +68123,9 @@ OUI:48EA62* OUI:48EA63* ID_OUI_FROM_DATABASE=Zhejiang Uniview Technologies Co., Ltd. +OUI:48EAA9* + ID_OUI_FROM_DATABASE=ShenZhen C&D Electronics CO.Ltd. + OUI:48EB30* ID_OUI_FROM_DATABASE=ETERNA TECHNOLOGY, INC. @@ -68222,6 +68555,9 @@ OUI:4C496C* OUI:4C49E3* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:4C4AB4* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:4C4B1F* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -68312,6 +68648,9 @@ OUI:4C53FD* OUI:4C5427* ID_OUI_FROM_DATABASE=Linepro Sp. z o.o. +OUI:4C548B* + ID_OUI_FROM_DATABASE=Cerebras System Inc. + OUI:4C5499* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -68471,9 +68810,57 @@ OUI:4C6BE8* OUI:4C6C13* ID_OUI_FROM_DATABASE=IoT Company Solucoes Tecnologicas Ltda +OUI:4C6CA1* + ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Crop. + OUI:4C6D58* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:4C6E440* + ID_OUI_FROM_DATABASE=Quasonix + +OUI:4C6E441* + ID_OUI_FROM_DATABASE=Shenzhen Xmitech Electronic Co.,Ltd + +OUI:4C6E442* + ID_OUI_FROM_DATABASE=Accutrol LLC + +OUI:4C6E443* + ID_OUI_FROM_DATABASE=Qingting Intelligent Technology(Suzhou)Co.,Ltd. + +OUI:4C6E444* + ID_OUI_FROM_DATABASE=Private + +OUI:4C6E445* + ID_OUI_FROM_DATABASE=Shenzhen Langji Guangnian Technology Co., Ltd. + +OUI:4C6E446* + ID_OUI_FROM_DATABASE=Panache DigiLife Limited + +OUI:4C6E447* + ID_OUI_FROM_DATABASE=Luxshare Electronic Technology (KunShan) Ltd + +OUI:4C6E448* + ID_OUI_FROM_DATABASE=Shenzhen iTayga Technology Co.,Ltd + +OUI:4C6E449* + ID_OUI_FROM_DATABASE=Chengdu Ruibitechuang Technology Co.,Ltd + +OUI:4C6E44A* + ID_OUI_FROM_DATABASE=Swistec GmbH + +OUI:4C6E44B* + ID_OUI_FROM_DATABASE=NovaFly LLC + +OUI:4C6E44C* + ID_OUI_FROM_DATABASE=Windar Photonics A/S + +OUI:4C6E44D* + ID_OUI_FROM_DATABASE=1Home Solutions GmbH + +OUI:4C6E44E* + ID_OUI_FROM_DATABASE=Shenzhen Jooan Technology Co., Ltd + OUI:4C6E6E* ID_OUI_FROM_DATABASE=Comnect Technology CO.,LTD @@ -68825,6 +69212,9 @@ OUI:4C9FF1* OUI:4CA003* ID_OUI_FROM_DATABASE=VITEC +OUI:4CA03D* + ID_OUI_FROM_DATABASE=Bouffalo Lab (Nanjing) Co., Ltd. + OUI:4CA0D4* ID_OUI_FROM_DATABASE=Telink Semiconductor (Shanghai) Co., Ltd. @@ -69161,6 +69551,9 @@ OUI:4CD637* OUI:4CD717* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:4CD73A* + ID_OUI_FROM_DATABASE=ShenZhen XinZhongXin Technology Co., Ltd + OUI:4CD74A* ID_OUI_FROM_DATABASE=Vantiva USA LLC @@ -69353,6 +69746,9 @@ OUI:4CEBD6* OUI:4CEC0F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:4CECEE* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:4CECEF* ID_OUI_FROM_DATABASE=Soraa, Inc. @@ -69650,6 +70046,9 @@ OUI:5021EC* OUI:502267* ID_OUI_FROM_DATABASE=PixeLINK +OUI:5022C9* + ID_OUI_FROM_DATABASE=Bel Power Solutions, Inc. + OUI:50236D* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd @@ -70043,6 +70442,9 @@ OUI:505DAC* OUI:505E24* ID_OUI_FROM_DATABASE=zte corporation +OUI:505E3A* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:505E5C* ID_OUI_FROM_DATABASE=SUNITEC TECHNOLOGY CO.,LIMITED @@ -70277,6 +70679,9 @@ OUI:508492* OUI:508569* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:50857C* + ID_OUI_FROM_DATABASE=eero inc. + OUI:50874D* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -70742,6 +71147,9 @@ OUI:50D753* OUI:50DA00* ID_OUI_FROM_DATABASE=Hangzhou H3C Technologies Co., Limited +OUI:50DA9E* + ID_OUI_FROM_DATABASE=SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + OUI:50DAD6* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -70808,6 +71216,9 @@ OUI:50DE19D* OUI:50DE19E* ID_OUI_FROM_DATABASE=DTEN Inc. +OUI:50DE92* + ID_OUI_FROM_DATABASE=shenzhen worldelite electronics co., LTD + OUI:50DF95* ID_OUI_FROM_DATABASE=Lytx @@ -70838,6 +71249,9 @@ OUI:50E24E* OUI:50E452* ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Corp. +OUI:50E467* + ID_OUI_FROM_DATABASE=Ring LLC + OUI:50E478* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -71255,6 +71669,9 @@ OUI:541473* OUI:5414A7* ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. +OUI:5414E9* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:5414F3* ID_OUI_FROM_DATABASE=Intel Corporate @@ -71678,6 +72095,12 @@ OUI:54725E* OUI:54726E* ID_OUI_FROM_DATABASE=Daimler Truck AG +OUI:54735A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + +OUI:547370* + ID_OUI_FROM_DATABASE=The LEGO Group + OUI:547398* ID_OUI_FROM_DATABASE=Toyo Electronics Corporation @@ -71780,6 +72203,9 @@ OUI:5486BC* OUI:54880E* ID_OUI_FROM_DATABASE=SAMSUNG ELECTRO-MECHANICS(THAILAND) +OUI:5488D5* + ID_OUI_FROM_DATABASE=zte corporation + OUI:5488DE* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -72131,6 +72557,9 @@ OUI:54BAD6* OUI:54BAD9* ID_OUI_FROM_DATABASE=Intelbras +OUI:54BB8F* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:54BD79* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -72500,6 +72929,9 @@ OUI:580AD4* OUI:580D0D* ID_OUI_FROM_DATABASE=GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI +OUI:580FA5* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:581031* ID_OUI_FROM_DATABASE=Hon Hai Precision IND.CO.,LTD @@ -72530,6 +72962,9 @@ OUI:58170C* OUI:581862* ID_OUI_FROM_DATABASE=Sony Corporation +OUI:5818B4* + ID_OUI_FROM_DATABASE=Chengdu Quanjing Intelligent Technology Co.,Ltd + OUI:5819F8* ID_OUI_FROM_DATABASE=Commscope @@ -73244,6 +73679,9 @@ OUI:589153* OUI:5891CF* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:589204* + ID_OUI_FROM_DATABASE=zte corporation + OUI:58920D* ID_OUI_FROM_DATABASE=Kinetic Avionics Limited @@ -73847,6 +74285,9 @@ OUI:58F39C* OUI:58F496* ID_OUI_FROM_DATABASE=Source Chain +OUI:58F658* + ID_OUI_FROM_DATABASE=Edifier International + OUI:58F67B* ID_OUI_FROM_DATABASE=Xia Men UnionCore Technology LTD. @@ -74060,6 +74501,9 @@ OUI:5C167D* OUI:5C16C7* ID_OUI_FROM_DATABASE=Arista Networks +OUI:5C1715* + ID_OUI_FROM_DATABASE=ODrive Robotics + OUI:5C1720* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -74318,6 +74762,9 @@ OUI:5C5181* OUI:5C5188* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:5C51DF* + ID_OUI_FROM_DATABASE=eero inc. + OUI:5C521E* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd @@ -74366,6 +74813,9 @@ OUI:5C58E6* OUI:5C5948* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:5C5A35* + ID_OUI_FROM_DATABASE=eero inc. + OUI:5C5A4C0* ID_OUI_FROM_DATABASE=Jinchuan Group Co.,Ltd @@ -74486,6 +74936,9 @@ OUI:5C5F67* OUI:5C60BA* ID_OUI_FROM_DATABASE=HP Inc. +OUI:5C6117* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:5C6152* ID_OUI_FROM_DATABASE=NXP Semiconductor (Tianjin) LTD. @@ -74498,6 +74951,9 @@ OUI:5C625A* OUI:5C628B* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:5C63B0* + ID_OUI_FROM_DATABASE=Fortinet, Inc. + OUI:5C63BF* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -74684,6 +75140,9 @@ OUI:5C843C* OUI:5C8486* ID_OUI_FROM_DATABASE=Brightsource Industries Israel LTD +OUI:5C8505* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:5C857E0* ID_OUI_FROM_DATABASE=28 Gorilla @@ -74949,7 +75408,7 @@ OUI:5CA86A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD OUI:5CA931* - ID_OUI_FROM_DATABASE=38436 + ID_OUI_FROM_DATABASE=Ubee Interactive Co., Limited OUI:5CA933* ID_OUI_FROM_DATABASE=Luma Home @@ -74999,6 +75458,9 @@ OUI:5CB26D* OUI:5CB29E* ID_OUI_FROM_DATABASE=ASCO Power Technologies +OUI:5CB2DF* + ID_OUI_FROM_DATABASE=Shenzhen Powerleader Storage Technology Co., Ltd. + OUI:5CB395* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -75023,6 +75485,9 @@ OUI:5CB559* OUI:5CB6CC* ID_OUI_FROM_DATABASE=NovaComm Technologies Inc. +OUI:5CB8B7* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:5CB8CB* ID_OUI_FROM_DATABASE=Allis Communications @@ -75431,6 +75896,9 @@ OUI:5CF838E* OUI:5CF8A1* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:5CF92B* + ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + OUI:5CF938* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -75575,6 +76043,9 @@ OUI:601521* OUI:60152B* ID_OUI_FROM_DATABASE=Palo Alto Networks +OUI:60156F* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:6015920* ID_OUI_FROM_DATABASE=S Labs sp. z o.o. @@ -75785,6 +76256,9 @@ OUI:602D74* OUI:602E20* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:602ED5* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:6030B3* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -76913,6 +77387,9 @@ OUI:60F673* OUI:60F677* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:60F723* + ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd + OUI:60F81D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -77138,6 +77615,9 @@ OUI:6420E0* OUI:642184* ID_OUI_FROM_DATABASE=Nippon Denki Kagaku Co.,LTD +OUI:6421FD* + ID_OUI_FROM_DATABASE=Guang zhou Xradio Technology Co., Ltd + OUI:642216* ID_OUI_FROM_DATABASE=Shandong Taixin Electronic co.,Ltd @@ -77687,6 +78167,9 @@ OUI:647791* OUI:647924* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:647999* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:6479A7* ID_OUI_FROM_DATABASE=Phison Electronics Corp. @@ -78101,6 +78584,9 @@ OUI:64D2C4* OUI:64D315* ID_OUI_FROM_DATABASE=HMD Global Oy +OUI:64D363* + ID_OUI_FROM_DATABASE=Seyond + OUI:64D4BD* ID_OUI_FROM_DATABASE=ALPSALPINE CO,.LTD @@ -78410,6 +78896,9 @@ OUI:68070A* OUI:680715* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:68080D* + ID_OUI_FROM_DATABASE=Shenzhen Yingsheng Technology Co., LTD + OUI:680927* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -78450,7 +78939,7 @@ OUI:681590* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS OUI:6815D3* - ID_OUI_FROM_DATABASE=Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. + ID_OUI_FROM_DATABASE=R&G PLUS Sp. z o.o. OUI:681605* ID_OUI_FROM_DATABASE=Systems And Electronic Development FZCO @@ -78464,6 +78953,9 @@ OUI:6818D9* OUI:68193F* ID_OUI_FROM_DATABASE=Digital Airways +OUI:681977* + ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd + OUI:6819AC* ID_OUI_FROM_DATABASE=Guangzhou Xianyou Intelligent Technogoly CO., LTD @@ -78512,6 +79004,9 @@ OUI:68215F* OUI:68228E* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:68229F* + ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + OUI:6822E5* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -78875,6 +79370,9 @@ OUI:686E23* OUI:686E48* ID_OUI_FROM_DATABASE=Prophet Electronic Technology Corp.,Ltd +OUI:68709E* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:687161* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -79118,6 +79616,9 @@ OUI:6891D0E* OUI:689234* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:689268* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:689320* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -79676,6 +80177,9 @@ OUI:68FCB6* OUI:68FCCA* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:68FDE8* + ID_OUI_FROM_DATABASE=Ruckus Wireless + OUI:68FE71* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -79806,7 +80310,7 @@ OUI:6C15243* ID_OUI_FROM_DATABASE=Forcite Helmet Systems Pty Ltd OUI:6C15244* - ID_OUI_FROM_DATABASE=Magicyo Technology CO., LTD. + ID_OUI_FROM_DATABASE=Magicyo Technology CO.,Ltd OUI:6C15245* ID_OUI_FROM_DATABASE=Shenzhen Electron Technology Co., LTD. @@ -80309,6 +80813,9 @@ OUI:6C55B1* OUI:6C55E8* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:6C55F6* + ID_OUI_FROM_DATABASE=eero inc. + OUI:6C5640* ID_OUI_FROM_DATABASE=BLU Products Inc @@ -80441,6 +80948,9 @@ OUI:6C6567* OUI:6C67EF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:6C688A* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:6C68A4* ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. @@ -80660,6 +81170,9 @@ OUI:6C9308D* OUI:6C9308E* ID_OUI_FROM_DATABASE=ANDDORO LLC +OUI:6C9313* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:6C9354* ID_OUI_FROM_DATABASE=Yaojin Technology (Shenzhen) Co., LTD. @@ -80741,6 +81254,9 @@ OUI:6CA4D1* OUI:6CA604* ID_OUI_FROM_DATABASE=Commscope +OUI:6CA613* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:6CA682* ID_OUI_FROM_DATABASE=EDAM information & communications @@ -81020,6 +81536,9 @@ OUI:6CDDEF* OUI:6CDEA9* ID_OUI_FROM_DATABASE=Cisco Meraki +OUI:6CDFD9* + ID_OUI_FROM_DATABASE=Concept2, Inc. + OUI:6CDFFB0* ID_OUI_FROM_DATABASE=Shenzhen HDCVT Technology @@ -81408,7 +81927,7 @@ OUI:7022FE* ID_OUI_FROM_DATABASE=Apple, Inc. OUI:702393* - ID_OUI_FROM_DATABASE=fos4X GmbH + ID_OUI_FROM_DATABASE=Polytech A/S OUI:702526* ID_OUI_FROM_DATABASE=Nokia @@ -81425,6 +81944,9 @@ OUI:702661* OUI:702804* ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. +OUI:70287D* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:70288B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -81449,6 +81971,9 @@ OUI:702C09* OUI:702C1F* ID_OUI_FROM_DATABASE=Wisol +OUI:702D81* + ID_OUI_FROM_DATABASE=Infinix mobility limited + OUI:702D84* ID_OUI_FROM_DATABASE=i4C Innovations @@ -81842,6 +82367,9 @@ OUI:7061EE* OUI:7062B8* ID_OUI_FROM_DATABASE=D-Link International +OUI:7062CB* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:706417* ID_OUI_FROM_DATABASE=ORBIS TECNOLOGIA ELECTRICA S.A. @@ -81980,6 +82508,9 @@ OUI:7072CF* OUI:7072FE* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:70733A* + ID_OUI_FROM_DATABASE=Jiangxi Remote lntelligence Technology Co.,Ltd + OUI:707362* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -89337,7 +89868,7 @@ OUI:70B3D591D* ID_OUI_FROM_DATABASE=Cubitech OUI:70B3D591E* - ID_OUI_FROM_DATABASE=Creotech Instruments S.A. + ID_OUI_FROM_DATABASE=Creotech Quantum SA OUI:70B3D591F* ID_OUI_FROM_DATABASE=JSC InformInvestGroup @@ -94080,7 +94611,7 @@ OUI:70B3D5F4B* ID_OUI_FROM_DATABASE=Chengdu Lingya Technology Co., Ltd. OUI:70B3D5F4C* - ID_OUI_FROM_DATABASE=PolyTech A/S + ID_OUI_FROM_DATABASE=Polytech A/S OUI:70B3D5F4D* ID_OUI_FROM_DATABASE=Honeywell @@ -94682,6 +95213,9 @@ OUI:70BF3E* OUI:70BF92* ID_OUI_FROM_DATABASE=GN Audio A/S +OUI:70C288* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:70C59C* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -95072,6 +95606,9 @@ OUI:7412B3* OUI:7412BB* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:741348* + ID_OUI_FROM_DATABASE=Blink by Amazon + OUI:74136A* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -95483,6 +96020,9 @@ OUI:743C18* OUI:743C24* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:743CDE* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:743E2B* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -95670,7 +96210,7 @@ OUI:745D22* ID_OUI_FROM_DATABASE=LCFC(Hefei) Electronics Technology co., ltd OUI:745D43* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:745D68* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -95948,6 +96488,9 @@ OUI:748F3C* OUI:748F4D* ID_OUI_FROM_DATABASE=duagon Germany GmbH +OUI:748FBF* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:748FC2* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -96086,6 +96629,9 @@ OUI:74A78E* OUI:74A7EA* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:74A981* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:74AB93* ID_OUI_FROM_DATABASE=Blink by Amazon @@ -96302,6 +96848,9 @@ OUI:74D713* OUI:74D7CA* ID_OUI_FROM_DATABASE=Panasonic Automotive Systems Co.,Ltd +OUI:74D809* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:74D83E* ID_OUI_FROM_DATABASE=Intel Corporate @@ -96317,6 +96866,9 @@ OUI:74D9EB* OUI:74DA38* ID_OUI_FROM_DATABASE=Edimax Technology Co. Ltd. +OUI:74DA78* + ID_OUI_FROM_DATABASE=HP Inc. + OUI:74DA88* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -96440,6 +96992,9 @@ OUI:74E5F9* OUI:74E60F* ID_OUI_FROM_DATABASE=TECNO MOBILE LIMITED +OUI:74E665* + ID_OUI_FROM_DATABASE=Dynabook Technology (Hangzhou) Inc. + OUI:74E6B8* ID_OUI_FROM_DATABASE=LG Electronics @@ -96527,6 +97082,9 @@ OUI:74F661* OUI:74F67A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:74F714* + ID_OUI_FROM_DATABASE=Lushare Precision Industry Co.,LTD + OUI:74F726* ID_OUI_FROM_DATABASE=Neuron Robotics @@ -96692,6 +97250,9 @@ OUI:780AC7* OUI:780B8C* ID_OUI_FROM_DATABASE=Private +OUI:780C48* + ID_OUI_FROM_DATABASE=Hong Kong Yihao Electronic Technology Co., Limited + OUI:780C71* ID_OUI_FROM_DATABASE=Inseego Wireless, Inc @@ -96959,6 +97520,9 @@ OUI:78312B* OUI:7831C1* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:7831C4* + ID_OUI_FROM_DATABASE=Panascais ehf. + OUI:78321B* ID_OUI_FROM_DATABASE=D-Link International @@ -96974,6 +97538,9 @@ OUI:783409* OUI:783486* ID_OUI_FROM_DATABASE=Nokia +OUI:7834B4* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7834FD* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -97160,6 +97727,9 @@ OUI:78521A* OUI:785237* ID_OUI_FROM_DATABASE=zte corporation +OUI:785249* + ID_OUI_FROM_DATABASE=Loxone Electronics GmbH + OUI:78524A* ID_OUI_FROM_DATABASE=Optonic GmbH @@ -97322,6 +97892,9 @@ OUI:7864C0* OUI:7864E6* ID_OUI_FROM_DATABASE=Green Motive Technology Limited +OUI:7864F0* + ID_OUI_FROM_DATABASE=Beijing Soynetic Co., Ltd + OUI:78653B* ID_OUI_FROM_DATABASE=Shaoxing Ourten Electronics Co., Ltd. @@ -97364,6 +97937,9 @@ OUI:786A1F* OUI:786A89* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:786BA5* + ID_OUI_FROM_DATABASE=Changchun Jetty Automotive Technology Co., LTD + OUI:786C1C* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -97445,6 +98021,9 @@ OUI:787689* OUI:7876D9* ID_OUI_FROM_DATABASE=EXARA Group +OUI:787826* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7878350* ID_OUI_FROM_DATABASE=ATsens @@ -97574,6 +98153,9 @@ OUI:788A20* OUI:788A86* ID_OUI_FROM_DATABASE=China Dragon Technology Limited +OUI:788AFB* + ID_OUI_FROM_DATABASE=HANSHOW TECHNOLOGY CO.,LTD. + OUI:788B2A* ID_OUI_FROM_DATABASE=Zhen Shi Information Technology (Shanghai) Co., Ltd. @@ -97634,6 +98216,9 @@ OUI:7895EB* OUI:78960D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:78966E* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:789682* ID_OUI_FROM_DATABASE=zte corporation @@ -97835,6 +98420,9 @@ OUI:78BAD0* OUI:78BAF9* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:78BB5C* + ID_OUI_FROM_DATABASE=Nokia Solutions and Networks India Private Limited + OUI:78BB88* ID_OUI_FROM_DATABASE=Maxio Technology (Hangzhou) Ltd. @@ -98231,6 +98819,9 @@ OUI:78DEE4* OUI:78DF72* ID_OUI_FROM_DATABASE=Shanghai Imilab Technology Co.Ltd +OUI:78E0C5* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:78E103* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -98330,6 +98921,9 @@ OUI:78EC22* OUI:78EC74* ID_OUI_FROM_DATABASE=Kyland-USA +OUI:78ECB5* + ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD + OUI:78ED25* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -98480,6 +99074,9 @@ OUI:7C0A50* OUI:7C0BC6* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:7C0C5F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7C0C92* ID_OUI_FROM_DATABASE=Suzhou Mobydata Smart System Co.,Ltd. @@ -98519,6 +99116,9 @@ OUI:7C152D* OUI:7C160D* ID_OUI_FROM_DATABASE=Saia-Burgess Controls AG +OUI:7C162A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:7C1689* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -98855,9 +99455,15 @@ OUI:7C4E09* OUI:7C4F7D* ID_OUI_FROM_DATABASE=Sawwave +OUI:7C4FAD* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7C4FB5* ID_OUI_FROM_DATABASE=Arcadyan Corporation +OUI:7C4FCD* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:7C5049* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -99218,6 +99824,9 @@ OUI:7C8334E* OUI:7C8437* ID_OUI_FROM_DATABASE=China Post Communications Equipment Co., Ltd. +OUI:7C852F* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:7C8530* ID_OUI_FROM_DATABASE=Nokia @@ -99266,6 +99875,9 @@ OUI:7C8EE4* OUI:7C8FDE* ID_OUI_FROM_DATABASE=DWnet Technologies(Suzhou) Corporation +OUI:7C90E9* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:7C9122* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -99356,6 +99968,9 @@ OUI:7CA62A* OUI:7CA7B0* ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD +OUI:7CA85D* + ID_OUI_FROM_DATABASE=RONGCHEENG GOER TECHNOLOGY CO.,LTD. + OUI:7CA8EC* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise @@ -99764,6 +100379,9 @@ OUI:7CD4A8* OUI:7CD566* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:7CD62C* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:7CD661* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -99854,6 +100472,9 @@ OUI:7CE712* OUI:7CE87F* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS +OUI:7CE8B1* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7CE913* ID_OUI_FROM_DATABASE=Fantasia Trading LLC @@ -100196,6 +100817,51 @@ OUI:801970* OUI:8019FE* ID_OUI_FROM_DATABASE=JianLing Technology CO., LTD +OUI:801D0D0* + ID_OUI_FROM_DATABASE=KbDevice,Inc. + +OUI:801D0D1* + ID_OUI_FROM_DATABASE=Hörmann Warnsysteme GmbH + +OUI:801D0D2* + ID_OUI_FROM_DATABASE=SZ Spinning Power Top Boundary Technology Co.Ltd. + +OUI:801D0D3* + ID_OUI_FROM_DATABASE=Lecoo Technology Co.,Ltd. + +OUI:801D0D4* + ID_OUI_FROM_DATABASE=GTL Tecnologia e Sistemas Ltda + +OUI:801D0D5* + ID_OUI_FROM_DATABASE=LONGI METER CO.,LTD. + +OUI:801D0D6* + ID_OUI_FROM_DATABASE=Drowsy Digital Inc + +OUI:801D0D7* + ID_OUI_FROM_DATABASE=HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD + +OUI:801D0D8* + ID_OUI_FROM_DATABASE=CRESTCHIC (UK) LIMITED + +OUI:801D0D9* + ID_OUI_FROM_DATABASE=WARNER ELECTRONICS (I) PVT. LTD. + +OUI:801D0DA* + ID_OUI_FROM_DATABASE=Luxshare Electronic Technology (KunShan) Ltd + +OUI:801D0DB* + ID_OUI_FROM_DATABASE=Syrma SGS Technology + +OUI:801D0DC* + ID_OUI_FROM_DATABASE=LOGICOM SA + +OUI:801D0DD* + ID_OUI_FROM_DATABASE=802 Secure + +OUI:801D0DE* + ID_OUI_FROM_DATABASE=Shanghai ReveISpark Technologies Co.,Ltd. + OUI:801D39* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -100346,6 +101012,9 @@ OUI:803AF4* OUI:803B2A* ID_OUI_FROM_DATABASE=ABB Xiamen Low Voltage Equipment Co.,Ltd. +OUI:803B70* + ID_OUI_FROM_DATABASE=Private + OUI:803B9A* ID_OUI_FROM_DATABASE=ghe-ces electronic ag @@ -100388,6 +101057,9 @@ OUI:80433F* OUI:8044FD* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. +OUI:80456B* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:8045DD* ID_OUI_FROM_DATABASE=Intel Corporate @@ -100490,6 +101162,9 @@ OUI:8059FD* OUI:805A04* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) +OUI:805A70* + ID_OUI_FROM_DATABASE=Fortinet, Inc. + OUI:805B65* ID_OUI_FROM_DATABASE=LG Innotek @@ -100595,6 +101270,9 @@ OUI:806D71* OUI:806D97* ID_OUI_FROM_DATABASE=Private +OUI:806DDE* + ID_OUI_FROM_DATABASE=Beken Corporation + OUI:806F1C* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -100904,6 +101582,9 @@ OUI:809733* OUI:80999B* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:8099CF* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:8099E7* ID_OUI_FROM_DATABASE=Sony Corporation @@ -100940,6 +101621,9 @@ OUI:80A1D7* OUI:80A235* ID_OUI_FROM_DATABASE=Edgecore Networks Corporation +OUI:80A2FC* + ID_OUI_FROM_DATABASE=AzureWave Technology Inc. + OUI:80A5790* ID_OUI_FROM_DATABASE=Benano Inc. @@ -100988,6 +101672,9 @@ OUI:80A579E* OUI:80A589* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:80A63C* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:80A796* ID_OUI_FROM_DATABASE=Neuralink Corp. @@ -101357,6 +102044,9 @@ OUI:80E869* OUI:80E86F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:80E8A4* + ID_OUI_FROM_DATABASE=zte corporation + OUI:80E94A* ID_OUI_FROM_DATABASE=LEAPS s.r.o. @@ -101468,6 +102158,9 @@ OUI:8400D2* OUI:840112* ID_OUI_FROM_DATABASE=Kaon Group Co., Ltd. +OUI:84016E* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:8401A7* ID_OUI_FROM_DATABASE=Greyware Automation Products, Inc @@ -101609,6 +102302,9 @@ OUI:84183A* OUI:841888* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:841985* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:841A24* ID_OUI_FROM_DATABASE=UNIONMAN TECHNOLOGY CO.,LTD @@ -101987,6 +102683,9 @@ OUI:84652B* OUI:846569* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd +OUI:84679A* + ID_OUI_FROM_DATABASE=Arm Ltd + OUI:84683E* ID_OUI_FROM_DATABASE=Intel Corporate @@ -102326,6 +103025,9 @@ OUI:8497B8* OUI:849866* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:8498A7* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:849A40* ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. @@ -102821,6 +103523,9 @@ OUI:84EAD2* OUI:84EAED* ID_OUI_FROM_DATABASE=Roku, Inc +OUI:84EB0C* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:84EB18* ID_OUI_FROM_DATABASE=Texas Instruments @@ -104111,6 +104816,9 @@ OUI:88C242* OUI:88C255* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:88C344* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:88C36E* ID_OUI_FROM_DATABASE=Beijing Ereneben lnformation Technology Limited @@ -104273,6 +104981,9 @@ OUI:88DA33* OUI:88DA36* ID_OUI_FROM_DATABASE=Calix Inc. +OUI:88DB08* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:88DC96* ID_OUI_FROM_DATABASE=EnGenius Technologies, Inc. @@ -104366,6 +105077,9 @@ OUI:88F155* OUI:88F2BD* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. +OUI:88F3D5* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:88F488* ID_OUI_FROM_DATABASE=cellon communications technology(shenzhen)Co.,Ltd. @@ -104450,6 +105164,9 @@ OUI:8C0734* OUI:8C078C* ID_OUI_FROM_DATABASE=FLOW DATA INC +OUI:8C083C* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:8C0879* ID_OUI_FROM_DATABASE=Texas Instruments @@ -104513,6 +105230,9 @@ OUI:8C12C2* OUI:8C13E2* ID_OUI_FROM_DATABASE=NETLINK ICT +OUI:8C142A* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:8C147D0* ID_OUI_FROM_DATABASE=Nio @@ -104576,6 +105296,9 @@ OUI:8C1759* OUI:8C17B6* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:8C1801* + ID_OUI_FROM_DATABASE=zte corporation + OUI:8C1850* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. @@ -104717,6 +105440,9 @@ OUI:8C1F64003* OUI:8C1F64006* ID_OUI_FROM_DATABASE=DUNASYS INGENIERIE +OUI:8C1F64007* + ID_OUI_FROM_DATABASE=INTOWN CO., LTD. + OUI:8C1F64009* ID_OUI_FROM_DATABASE=Converging Systems Inc. @@ -104777,6 +105503,9 @@ OUI:8C1F64024* OUI:8C1F64025* ID_OUI_FROM_DATABASE=SMITEC S.p.A. +OUI:8C1F64027* + ID_OUI_FROM_DATABASE=Zhejiang Tengen Electric Co.,Ltd. + OUI:8C1F64028* ID_OUI_FROM_DATABASE=eyrise B.V. @@ -104807,6 +105536,9 @@ OUI:8C1F64035* OUI:8C1F64036* ID_OUI_FROM_DATABASE=Photon Counting Systems LLC +OUI:8C1F64037* + ID_OUI_FROM_DATABASE=ENLESS WIRELESS + OUI:8C1F6403A* ID_OUI_FROM_DATABASE=ORION COMPUTERS @@ -104819,6 +105551,9 @@ OUI:8C1F6403C* OUI:8C1F6403D* ID_OUI_FROM_DATABASE=HORIZON.INC +OUI:8C1F6403E* + ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd + OUI:8C1F64042* ID_OUI_FROM_DATABASE=HEITEC AG @@ -105068,6 +105803,9 @@ OUI:8C1F640AC* OUI:8C1F640AD* ID_OUI_FROM_DATABASE=E2 Nova Corporation +OUI:8C1F640AE* + ID_OUI_FROM_DATABASE=Gogo BA + OUI:8C1F640AF* ID_OUI_FROM_DATABASE=FORSEE POWER @@ -105311,6 +106049,9 @@ OUI:8C1F6412D* OUI:8C1F6412E* ID_OUI_FROM_DATABASE=inomatic GmbH +OUI:8C1F6412F* + ID_OUI_FROM_DATABASE=MB connect line GmbH + OUI:8C1F64133* ID_OUI_FROM_DATABASE=Vtron Pty Ltd @@ -105461,6 +106202,9 @@ OUI:8C1F6417F* OUI:8C1F64180* ID_OUI_FROM_DATABASE=Structural Integrity Services +OUI:8C1F64182* + ID_OUI_FROM_DATABASE=Private + OUI:8C1F64183* ID_OUI_FROM_DATABASE=NICE Total Cash Management Co., Ltd. @@ -105593,6 +106337,9 @@ OUI:8C1F641CA* OUI:8C1F641CB* ID_OUI_FROM_DATABASE=SASYS e.K. +OUI:8C1F641CC* + ID_OUI_FROM_DATABASE=TEX COMPUTER SRL + OUI:8C1F641CE* ID_OUI_FROM_DATABASE=Eiden Co.,Ltd. @@ -105710,6 +106457,9 @@ OUI:8C1F64203* OUI:8C1F64204* ID_OUI_FROM_DATABASE=castcore +OUI:8C1F64205* + ID_OUI_FROM_DATABASE=Anuvu AB + OUI:8C1F64206* ID_OUI_FROM_DATABASE=KRYFS TECHNOLOGIES PRIVATE LIMITED @@ -105959,6 +106709,9 @@ OUI:8C1F6428C* OUI:8C1F6428D* ID_OUI_FROM_DATABASE=AVA Monitoring AB +OUI:8C1F6428F* + ID_OUI_FROM_DATABASE=MDA SatConn UK + OUI:8C1F64290* ID_OUI_FROM_DATABASE=UBIQ TECHNOLOGIES INTERNATIONAL LTD @@ -106115,6 +106868,9 @@ OUI:8C1F642D6* OUI:8C1F642D8* ID_OUI_FROM_DATABASE=CONTROL SYSTEMS Srl +OUI:8C1F642DA* + ID_OUI_FROM_DATABASE=AT-Automation Technology GmbH + OUI:8C1F642DC* ID_OUI_FROM_DATABASE=TimeMachines Inc. @@ -106166,6 +106922,9 @@ OUI:8C1F642F4* OUI:8C1F642F5* ID_OUI_FROM_DATABASE=Florida R&D Associates LLC +OUI:8C1F642F6* + ID_OUI_FROM_DATABASE=Aether Energy Alliance LLC + OUI:8C1F642F7* ID_OUI_FROM_DATABASE=Sealink Technology B.V @@ -106205,6 +106964,9 @@ OUI:8C1F64305* OUI:8C1F64306* ID_OUI_FROM_DATABASE=Corigine,Inc. +OUI:8C1F64308* + ID_OUI_FROM_DATABASE=Eon Instrumentation + OUI:8C1F64309* ID_OUI_FROM_DATABASE=MECT SRL @@ -106223,6 +106985,9 @@ OUI:8C1F6430E* OUI:8C1F6430F* ID_OUI_FROM_DATABASE=EAST PHOTONICS +OUI:8C1F64310* + ID_OUI_FROM_DATABASE=CrossBar, Inc. + OUI:8C1F64313* ID_OUI_FROM_DATABASE=SB-GROUP LTD @@ -106253,6 +107018,9 @@ OUI:8C1F6431D* OUI:8C1F6431F* ID_OUI_FROM_DATABASE=STV Electronic GmbH +OUI:8C1F64320* + ID_OUI_FROM_DATABASE=VITREA Smart Home Technologies Ltd. + OUI:8C1F64324* ID_OUI_FROM_DATABASE=Kinetic Technologies @@ -106298,6 +107066,9 @@ OUI:8C1F6433C* OUI:8C1F6433D* ID_OUI_FROM_DATABASE=ARROW (CHINA) ELECTRONICS TRADING CO., LTD. +OUI:8C1F6433F* + ID_OUI_FROM_DATABASE=Lotec Teknoloji Limited Sirketi + OUI:8C1F64340* ID_OUI_FROM_DATABASE=BRS Sistemas Eletrônicos @@ -106346,6 +107117,9 @@ OUI:8C1F64357* OUI:8C1F64358* ID_OUI_FROM_DATABASE=Denso Manufacturing Tennessee +OUI:8C1F6435A* + ID_OUI_FROM_DATABASE=Korea Electric Vehicle Infra Technology + OUI:8C1F6435C* ID_OUI_FROM_DATABASE=Opgal Optronic Industries ltd @@ -106355,6 +107129,9 @@ OUI:8C1F6435D* OUI:8C1F6435E* ID_OUI_FROM_DATABASE=Test21 Taiwan Corp +OUI:8C1F64360* + ID_OUI_FROM_DATABASE=SPIE Dürr Austria GmbH + OUI:8C1F64362* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -106385,6 +107162,9 @@ OUI:8C1F6436A* OUI:8C1F6436B* ID_OUI_FROM_DATABASE=ViewSonic Corp +OUI:8C1F6436C* + ID_OUI_FROM_DATABASE=Scramble Tools LLC + OUI:8C1F6436E* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS @@ -106436,6 +107216,9 @@ OUI:8C1F6437F* OUI:8C1F64380* ID_OUI_FROM_DATABASE=YSLAB +OUI:8C1F64381* + ID_OUI_FROM_DATABASE=TimeMachines Inc. + OUI:8C1F64382* ID_OUI_FROM_DATABASE=Shenzhen ROLSTONE Technology Co., Ltd @@ -106517,6 +107300,9 @@ OUI:8C1F643A3* OUI:8C1F643A4* ID_OUI_FROM_DATABASE=QLM Technology Ltd +OUI:8C1F643A5* + ID_OUI_FROM_DATABASE=TECHMOVERS SYSTEMS INDIA LIMITED + OUI:8C1F643AC* ID_OUI_FROM_DATABASE=Benison Tech @@ -106670,6 +107456,9 @@ OUI:8C1F643F3* OUI:8C1F643F4* ID_OUI_FROM_DATABASE=ACTELSER S.L. +OUI:8C1F643F5* + ID_OUI_FROM_DATABASE=Wuxi Eutron Electronics Technology Co.,Ltd + OUI:8C1F643F6* ID_OUI_FROM_DATABASE=ANTARA TECHNOLOGIES @@ -107174,6 +107963,9 @@ OUI:8C1F644FA* OUI:8C1F644FB* ID_OUI_FROM_DATABASE=MESA TECHNOLOGIES LLC +OUI:8C1F644FC* + ID_OUI_FROM_DATABASE=QUBIX SPA + OUI:8C1F64500* ID_OUI_FROM_DATABASE=Nepean Networks Pty Ltd @@ -107249,8 +108041,8 @@ 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:8C1F6451C* + ID_OUI_FROM_DATABASE=JS Tech Co., Ltd. OUI:8C1F64521* ID_OUI_FROM_DATABASE=MP-SENSOR GmbH @@ -107435,6 +108227,9 @@ OUI:8C1F6457A* OUI:8C1F6457B* ID_OUI_FROM_DATABASE=Potter Electric Signal Co. LLC +OUI:8C1F6457C* + ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH + OUI:8C1F6457D* ID_OUI_FROM_DATABASE=ISDI Ltd @@ -107459,6 +108254,9 @@ OUI:8C1F64585* OUI:8C1F64589* ID_OUI_FROM_DATABASE=HVRND +OUI:8C1F6458A* + ID_OUI_FROM_DATABASE=Wacebo Europe Srl + OUI:8C1F6458B* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -107528,6 +108326,9 @@ OUI:8C1F645AA* OUI:8C1F645AC* ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd +OUI:8C1F645AD* + ID_OUI_FROM_DATABASE=TAKAHATA PRECISION Co., LTD. + OUI:8C1F645AE* ID_OUI_FROM_DATABASE=Suzhou Motorcomm Electronic Technology Co., Ltd @@ -107669,6 +108470,9 @@ OUI:8C1F645F1* OUI:8C1F645F2* ID_OUI_FROM_DATABASE=CMC Applied Technology institute +OUI:8C1F645F4* + ID_OUI_FROM_DATABASE=Jiangsu Yi Rong Mstar Technology Ltd. + OUI:8C1F645F5* ID_OUI_FROM_DATABASE=HongSeok Ltd. @@ -107702,6 +108506,9 @@ OUI:8C1F64600* OUI:8C1F64601* ID_OUI_FROM_DATABASE=Camius +OUI:8C1F64602* + ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. + OUI:8C1F64603* ID_OUI_FROM_DATABASE=Fuku Energy Technology Co., Ltd. @@ -107738,6 +108545,9 @@ OUI:8C1F64610* OUI:8C1F64611* ID_OUI_FROM_DATABASE=Siemens Industry Software Inc. +OUI:8C1F64614* + ID_OUI_FROM_DATABASE=Instawork + OUI:8C1F64616* ID_OUI_FROM_DATABASE=DEUTA Werke GmbH @@ -107795,6 +108605,9 @@ OUI:8C1F6462F* OUI:8C1F64631* ID_OUI_FROM_DATABASE=HAIYANG OLIX CO.,LTD. +OUI:8C1F64633* + ID_OUI_FROM_DATABASE=Gyros Protein Technologies AB + OUI:8C1F64634* ID_OUI_FROM_DATABASE=AML @@ -107819,6 +108632,9 @@ OUI:8C1F6463E* OUI:8C1F6463F* ID_OUI_FROM_DATABASE=PREO INDUSTRIES FAR EAST LTD +OUI:8C1F64640* + ID_OUI_FROM_DATABASE=Transports Publics Genevois + OUI:8C1F64641* ID_OUI_FROM_DATABASE=biosilver .co.,ltd @@ -108182,6 +108998,9 @@ OUI:8C1F646ED* OUI:8C1F646EE* ID_OUI_FROM_DATABASE=Cortical Labs Pte Ltd +OUI:8C1F646F0* + ID_OUI_FROM_DATABASE=Right Time Sports LLC + OUI:8C1F646F4* ID_OUI_FROM_DATABASE=Elsist Srl @@ -108353,6 +109172,9 @@ OUI:8C1F64749* OUI:8C1F6474B* ID_OUI_FROM_DATABASE=AR Modular RF +OUI:8C1F6474D* + ID_OUI_FROM_DATABASE=IDUN Technologies AG + OUI:8C1F6474E* ID_OUI_FROM_DATABASE=OpenPark Technologies Kft @@ -108560,6 +109382,9 @@ OUI:8C1F647B0* OUI:8C1F647B1* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F647B2* + ID_OUI_FROM_DATABASE=Neosem Inc. + OUI:8C1F647B5* ID_OUI_FROM_DATABASE=Guan Show Technologe Co., Ltd. @@ -108674,6 +109499,9 @@ OUI:8C1F647E7* OUI:8C1F647E8* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F647EA* + ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS + OUI:8C1F647EC* ID_OUI_FROM_DATABASE=Methods2Business B.V. @@ -108794,6 +109622,9 @@ OUI:8C1F64820* OUI:8C1F64822* ID_OUI_FROM_DATABASE=IP Devices +OUI:8C1F64823* + ID_OUI_FROM_DATABASE=Lumiplan Duhamel + OUI:8C1F64824* ID_OUI_FROM_DATABASE=LOGICUBE INC @@ -108866,6 +109697,9 @@ OUI:8C1F64849* OUI:8C1F6484A* ID_OUI_FROM_DATABASE=Bitmapper Integration Technologies Private Limited +OUI:8C1F6484B* + ID_OUI_FROM_DATABASE=Unlimited Bandwidth LLC + OUI:8C1F6484C* ID_OUI_FROM_DATABASE=AvMap srlu @@ -109004,6 +109838,9 @@ OUI:8C1F64891* OUI:8C1F64892* ID_OUI_FROM_DATABASE=MDI Industrial +OUI:8C1F64893* + ID_OUI_FROM_DATABASE=Portrait Displays, Inc. + OUI:8C1F64895* ID_OUI_FROM_DATABASE=Dacom West GmbH @@ -109013,6 +109850,9 @@ OUI:8C1F64898* OUI:8C1F64899* ID_OUI_FROM_DATABASE=American Edge IP +OUI:8C1F6489A* + ID_OUI_FROM_DATABASE=National Control Devices, LLC + OUI:8C1F6489E* ID_OUI_FROM_DATABASE=Cinetix Srl @@ -109055,6 +109895,9 @@ OUI:8C1F648B2* OUI:8C1F648B3* ID_OUI_FROM_DATABASE=Hubbell Power Systems +OUI:8C1F648B4* + ID_OUI_FROM_DATABASE=MYIR Electronics Limited + OUI:8C1F648B5* ID_OUI_FROM_DATABASE=Ashton Bentley Collaboration Spaces @@ -109070,6 +109913,9 @@ OUI:8C1F648B8* OUI:8C1F648B9* ID_OUI_FROM_DATABASE=Zynex Monitoring Solutions +OUI:8C1F648BB* + ID_OUI_FROM_DATABASE=swiss electronic creation GmbH + OUI:8C1F648BC* ID_OUI_FROM_DATABASE=Peter Huber Kaeltemaschinenbau SE @@ -109175,9 +110021,15 @@ OUI:8C1F648ED* OUI:8C1F648EE* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F648EF* + ID_OUI_FROM_DATABASE=Osec + OUI:8C1F648F0* ID_OUI_FROM_DATABASE=IGL +OUI:8C1F648F3* + ID_OUI_FROM_DATABASE=EverBot Technology CO., LTD + OUI:8C1F648F4* ID_OUI_FROM_DATABASE=Loadrite (Auckland) Limited @@ -109295,6 +110147,9 @@ OUI:8C1F6492A* OUI:8C1F6492D* ID_OUI_FROM_DATABASE=IVOR Intelligent Electrical Appliance Co., Ltd +OUI:8C1F64930* + ID_OUI_FROM_DATABASE=Wuhan HYAIEV (华异) Technology Co., Ltd + OUI:8C1F64931* ID_OUI_FROM_DATABASE=Noptel Oy @@ -109355,6 +110210,9 @@ OUI:8C1F6494F* OUI:8C1F64953* ID_OUI_FROM_DATABASE=VAF Instruments BV +OUI:8C1F64955* + ID_OUI_FROM_DATABASE=Talleres de Escoriaza SAU + OUI:8C1F64956* ID_OUI_FROM_DATABASE=Paulmann Licht GmbH @@ -109496,6 +110354,9 @@ OUI:8C1F6499C* OUI:8C1F6499E* ID_OUI_FROM_DATABASE=EIDOS s.r.l. +OUI:8C1F649A0* + ID_OUI_FROM_DATABASE=NEOX Networks + OUI:8C1F649A1* ID_OUI_FROM_DATABASE=Pacific Software Development Co., Ltd. @@ -109520,6 +110381,9 @@ OUI:8C1F649A7* OUI:8C1F649A9* ID_OUI_FROM_DATABASE=TIAMA +OUI:8C1F649AA* + ID_OUI_FROM_DATABASE=xTools Inc. + OUI:8C1F649AB* ID_OUI_FROM_DATABASE=DAVE SRL @@ -109643,6 +110507,9 @@ OUI:8C1F649EA* OUI:8C1F649EC* ID_OUI_FROM_DATABASE=Specialized Communications Corp. +OUI:8C1F649EE* + ID_OUI_FROM_DATABASE=Möbus Engineering GmbH + OUI:8C1F649F0* ID_OUI_FROM_DATABASE=ePlant, Inc. @@ -109748,6 +110615,9 @@ OUI:8C1F64A1F* OUI:8C1F64A20* ID_OUI_FROM_DATABASE=Intenseye Inc. +OUI:8C1F64A25* + ID_OUI_FROM_DATABASE=Potter Electric Signal Company + OUI:8C1F64A26* ID_OUI_FROM_DATABASE=Automatic Pty Ltd @@ -109811,6 +110681,9 @@ OUI:8C1F64A3E* OUI:8C1F64A3F* ID_OUI_FROM_DATABASE=ViewSonic Corp +OUI:8C1F64A41* + ID_OUI_FROM_DATABASE=Guan Show Technologe Co., Ltd. + OUI:8C1F64A42* ID_OUI_FROM_DATABASE=Rodgers Instruments US LLC @@ -109883,6 +110756,9 @@ OUI:8C1F64A67* OUI:8C1F64A6A* ID_OUI_FROM_DATABASE=Sphere Com Services Pvt Ltd +OUI:8C1F64A6C* + ID_OUI_FROM_DATABASE=Arctic Instruments Oy + OUI:8C1F64A6D* ID_OUI_FROM_DATABASE=CyberneX Co., Ltd @@ -109982,6 +110858,9 @@ OUI:8C1F64A9D* OUI:8C1F64A9E* ID_OUI_FROM_DATABASE=Optimum Instruments Inc. +OUI:8C1F64A9F* + ID_OUI_FROM_DATABASE=DADHWAL AI PRIVATE LIMITED + OUI:8C1F64AA0* ID_OUI_FROM_DATABASE=Flextronics International Kft @@ -110033,6 +110912,9 @@ OUI:8C1F64AB7* OUI:8C1F64AB8* ID_OUI_FROM_DATABASE=Private +OUI:8C1F64ABA* + ID_OUI_FROM_DATABASE=FLUGCOM GmbH + OUI:8C1F64ABE* ID_OUI_FROM_DATABASE=TAIYO DENON Corporation @@ -110078,6 +110960,9 @@ OUI:8C1F64ACF* OUI:8C1F64AD0* ID_OUI_FROM_DATABASE=Elektrotechnik & Elektronik Oltmann GmbH +OUI:8C1F64AD1* + ID_OUI_FROM_DATABASE=Hatteland Technology AS + OUI:8C1F64AD2* ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd @@ -110099,6 +110984,9 @@ OUI:8C1F64AD8* OUI:8C1F64AD9* ID_OUI_FROM_DATABASE=Vision Systems Safety Tech +OUI:8C1F64ADA* + ID_OUI_FROM_DATABASE=Ability Intelligent Corp. + OUI:8C1F64ADB* ID_OUI_FROM_DATABASE=Hebei Weiji Electric Co.,Ltd @@ -110273,6 +111161,9 @@ OUI:8C1F64B31* OUI:8C1F64B32* ID_OUI_FROM_DATABASE=Plug Power +OUI:8C1F64B33* + ID_OUI_FROM_DATABASE=Skylark Lasers + OUI:8C1F64B35* ID_OUI_FROM_DATABASE=RADA Electronics Industries Ltd. @@ -110555,6 +111446,9 @@ OUI:8C1F64BB9* OUI:8C1F64BBA* ID_OUI_FROM_DATABASE=elysia GmbH +OUI:8C1F64BBB* + ID_OUI_FROM_DATABASE=SiLC Technologies + OUI:8C1F64BBC* ID_OUI_FROM_DATABASE=Liberator Pty Ltd @@ -110618,6 +111512,9 @@ OUI:8C1F64BD2* OUI:8C1F64BD3* ID_OUI_FROM_DATABASE=IO Master Technology +OUI:8C1F64BD4* + ID_OUI_FROM_DATABASE=AVEA Group, Inc. + OUI:8C1F64BD5* ID_OUI_FROM_DATABASE=Pro-Custom Group @@ -110651,6 +111548,9 @@ OUI:8C1F64BE8* OUI:8C1F64BEA* ID_OUI_FROM_DATABASE=Microchip Technologies Inc +OUI:8C1F64BEB* + ID_OUI_FROM_DATABASE=aelettronica group srl + OUI:8C1F64BED* ID_OUI_FROM_DATABASE=Genius Sports SS LLC @@ -110792,6 +111692,9 @@ OUI:8C1F64C2B* OUI:8C1F64C2D* ID_OUI_FROM_DATABASE=iENSO Inc. +OUI:8C1F64C2E* + ID_OUI_FROM_DATABASE=ICS SOLUTIONS INC. + OUI:8C1F64C2F* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -110900,6 +111803,9 @@ OUI:8C1F64C62* OUI:8C1F64C64* ID_OUI_FROM_DATABASE=Ajeco Oy +OUI:8C1F64C65* + ID_OUI_FROM_DATABASE=Headwave + OUI:8C1F64C67* ID_OUI_FROM_DATABASE=Oriux @@ -110933,6 +111839,9 @@ OUI:8C1F64C74* OUI:8C1F64C75* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F64C77* + ID_OUI_FROM_DATABASE=VNET Corp. + OUI:8C1F64C78* ID_OUI_FROM_DATABASE=POLON-ALFA S.A. @@ -110954,6 +111863,9 @@ OUI:8C1F64C80* OUI:8C1F64C81* ID_OUI_FROM_DATABASE=Taolink Technologies Corporation +OUI:8C1F64C82* + ID_OUI_FROM_DATABASE=SekureTrak Inc. dba TraknProtect + OUI:8C1F64C83* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -110975,6 +111887,9 @@ OUI:8C1F64C91* OUI:8C1F64C92* ID_OUI_FROM_DATABASE=EQ Earthquake Ltd. +OUI:8C1F64C95* + ID_OUI_FROM_DATABASE=LEMIER + OUI:8C1F64C96* ID_OUI_FROM_DATABASE=Smart Data (Shenzhen) Intelligent System Co., Ltd. @@ -111308,6 +112223,9 @@ OUI:8C1F64D30* OUI:8C1F64D34* ID_OUI_FROM_DATABASE=KRONOTECH SRL +OUI:8C1F64D35* + ID_OUI_FROM_DATABASE=Xi'an Biangu Information Technology Co., Ltd. + OUI:8C1F64D38* ID_OUI_FROM_DATABASE=CUU LONG TECHNOLOGY AND TRADING COMPANY LIMITED @@ -111410,6 +112328,9 @@ OUI:8C1F64D69* OUI:8C1F64D6C* ID_OUI_FROM_DATABASE=Packetalk LLC +OUI:8C1F64D6D* + ID_OUI_FROM_DATABASE=Viettel High Tech + OUI:8C1F64D6F* ID_OUI_FROM_DATABASE=ARKTRON ELECTRONICS @@ -111446,6 +112367,12 @@ OUI:8C1F64D80* OUI:8C1F64D81* ID_OUI_FROM_DATABASE=Mitsubishi Electric India Pvt. Ltd. +OUI:8C1F64D82* + ID_OUI_FROM_DATABASE=Shanghai Jarue Microsystem.CO.,Ltd. + +OUI:8C1F64D87* + ID_OUI_FROM_DATABASE=EXPERIO TECH PRIVATE LIMITED + OUI:8C1F64D88* ID_OUI_FROM_DATABASE=University of Geneva - Department of Particle Physics @@ -111647,6 +112574,12 @@ OUI:8C1F64DEB* OUI:8C1F64DED* ID_OUI_FROM_DATABASE=PhotonPath +OUI:8C1F64DF0* + ID_OUI_FROM_DATABASE=Cyberkar Systems inc. + +OUI:8C1F64DF2* + ID_OUI_FROM_DATABASE=LOMAR SRL + OUI:8C1F64DF5* ID_OUI_FROM_DATABASE=Concept Pro Surveillance @@ -111701,6 +112634,9 @@ OUI:8C1F64E0B* OUI:8C1F64E0C* ID_OUI_FROM_DATABASE=TELESTRIDER SA +OUI:8C1F64E0D* + ID_OUI_FROM_DATABASE=MyDefence A/S + OUI:8C1F64E0E* ID_OUI_FROM_DATABASE=Nokeval Oy @@ -111755,6 +112691,9 @@ OUI:8C1F64E2A* OUI:8C1F64E2B* ID_OUI_FROM_DATABASE=Glotech Exim Private Limited +OUI:8C1F64E2C* + ID_OUI_FROM_DATABASE=Kairos Water, Inc + OUI:8C1F64E2D* ID_OUI_FROM_DATABASE=RADA Electronics Industries Ltd. @@ -112049,6 +112988,12 @@ OUI:8C1F64EC1* OUI:8C1F64EC2* ID_OUI_FROM_DATABASE=HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD +OUI:8C1F64EC3* + ID_OUI_FROM_DATABASE=Scenario Automation + +OUI:8C1F64EC5* + ID_OUI_FROM_DATABASE=Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. + OUI:8C1F64ECC* ID_OUI_FROM_DATABASE=Baldwin Jimek AB @@ -112136,6 +113081,9 @@ OUI:8C1F64EFB* OUI:8C1F64EFD* ID_OUI_FROM_DATABASE=Novatera(Shenzhen)Technologies Co.,Ltd. +OUI:8C1F64EFF* + ID_OUI_FROM_DATABASE=Automata GmbH & Co. KG + OUI:8C1F64F03* ID_OUI_FROM_DATABASE=Faust ApS @@ -112373,6 +113321,9 @@ OUI:8C1F64F79* OUI:8C1F64F7A* ID_OUI_FROM_DATABASE=SiEngine Technology Co., Ltd. +OUI:8C1F64F7B* + ID_OUI_FROM_DATABASE=XPS ELETRONICA LTDA + OUI:8C1F64F7C* ID_OUI_FROM_DATABASE=General Dynamics IT @@ -112403,6 +113354,9 @@ OUI:8C1F64F88* OUI:8C1F64F8C* ID_OUI_FROM_DATABASE=BK LAB +OUI:8C1F64F8E* + ID_OUI_FROM_DATABASE=Chengdu Aplux Inteligence Technology Ltd. + OUI:8C1F64F90* ID_OUI_FROM_DATABASE=Enfabrica @@ -112475,6 +113429,9 @@ OUI:8C1F64FB0* OUI:8C1F64FB1* ID_OUI_FROM_DATABASE=ABB +OUI:8C1F64FB3* + ID_OUI_FROM_DATABASE=Camius + OUI:8C1F64FB4* ID_OUI_FROM_DATABASE=Thales Nederland BV @@ -112517,6 +113474,9 @@ OUI:8C1F64FC5* OUI:8C1F64FC6* ID_OUI_FROM_DATABASE=Invertek Drives Ltd +OUI:8C1F64FC8* + ID_OUI_FROM_DATABASE=Metaphase Technologies + OUI:8C1F64FCA* ID_OUI_FROM_DATABASE=Elektrotechnik & Elektronik Oltmann GmbH @@ -112574,6 +113534,9 @@ OUI:8C1F64FE3* OUI:8C1F64FE5* ID_OUI_FROM_DATABASE=Truenorth +OUI:8C1F64FE6* + ID_OUI_FROM_DATABASE=Cognicom, Inc. + OUI:8C1F64FE9* ID_OUI_FROM_DATABASE=ALZAJEL MODERN TELECOMMUNICATION @@ -112826,6 +113789,9 @@ OUI:8C497A* OUI:8C49B6* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:8C49CF* + ID_OUI_FROM_DATABASE=Private + OUI:8C4AEE* ID_OUI_FROM_DATABASE=GIGA TMS INC @@ -113180,6 +114146,9 @@ OUI:8C64D4* OUI:8C65A3* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:8C65EC* + ID_OUI_FROM_DATABASE=TUBITAK MAM + OUI:8C6794* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -113309,6 +114278,9 @@ OUI:8C8126* OUI:8C8172* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:8C8283* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:8C82A8* ID_OUI_FROM_DATABASE=Insigma Technology Co.,Ltd @@ -113486,6 +114458,9 @@ OUI:8C9806* OUI:8C986B* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:8C9885* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:8C99E6* ID_OUI_FROM_DATABASE=TCT mobile ltd @@ -113831,6 +114806,9 @@ OUI:8CD0B2* OUI:8CD17B* ID_OUI_FROM_DATABASE=CG Mobile +OUI:8CD1A6* + ID_OUI_FROM_DATABASE=eero inc. + OUI:8CD2E9* ID_OUI_FROM_DATABASE=YOKOTE SEIKO CO., LTD. @@ -113957,6 +114935,9 @@ OUI:8CEC7B* OUI:8CEDE1* ID_OUI_FROM_DATABASE=Ubiquiti Inc +OUI:8CEE17* + ID_OUI_FROM_DATABASE=GYGES LABS PTE.LTD + OUI:8CEEC6* ID_OUI_FROM_DATABASE=Precepscion Pty. Ltd. @@ -114080,6 +115061,9 @@ OUI:9003B7* OUI:900628* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:9006DB* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:9006F2* ID_OUI_FROM_DATABASE=Texas Instruments @@ -114101,6 +115085,9 @@ OUI:900A39* OUI:900A3A* ID_OUI_FROM_DATABASE=PSG Plastic Service GmbH +OUI:900A48* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:900A62* ID_OUI_FROM_DATABASE=Inventus Power Eletronica do Brasil LTDA @@ -114338,6 +115325,9 @@ OUI:902E1C* OUI:902E87* ID_OUI_FROM_DATABASE=LabJack +OUI:9030D6* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:90314B* ID_OUI_FROM_DATABASE=AltoBeam Inc. @@ -114365,6 +115355,9 @@ OUI:9035A2* OUI:9035EA* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:9036B2* + ID_OUI_FROM_DATABASE=TRATON AB + OUI:903809* ID_OUI_FROM_DATABASE=Ericsson AB @@ -114494,6 +115487,9 @@ OUI:904D4A* OUI:904DC3* ID_OUI_FROM_DATABASE=Flonidan A/S +OUI:904DE2* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:904E2B* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -114830,6 +115826,9 @@ OUI:90842B* OUI:90848B* ID_OUI_FROM_DATABASE=HDR10+ Technologies, LLC +OUI:90848E* + ID_OUI_FROM_DATABASE=FUJIAN STAR-NET COMMUNICATION CO.,LTD + OUI:908674* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD @@ -114932,6 +115931,9 @@ OUI:909060* OUI:909164* ID_OUI_FROM_DATABASE=ChongQing Lavid Technology Co., Ltd. +OUI:90922C* + ID_OUI_FROM_DATABASE=Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. + OUI:9092B4* ID_OUI_FROM_DATABASE=Diehl BGT Defence GmbH & Co. KG @@ -115199,6 +116201,9 @@ OUI:90B97D* OUI:90B9F9* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:90BA09* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:90BDE6* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -115340,6 +116345,9 @@ OUI:90D473* OUI:90D689* ID_OUI_FROM_DATABASE=Huahao Fangzhou Technology Co.,Ltd +OUI:90D733* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:90D74F* ID_OUI_FROM_DATABASE=Bookeen @@ -115364,6 +116372,9 @@ OUI:90DA4E* OUI:90DA6A* ID_OUI_FROM_DATABASE=FOCUS H&S Co., Ltd. +OUI:90DA72* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:90DAF9* ID_OUI_FROM_DATABASE=Siemens Rail Automation SAU @@ -115511,6 +116522,9 @@ OUI:90EF68* OUI:90F005* ID_OUI_FROM_DATABASE=Xi'an Molead Technology Co., Ltd +OUI:90F04C* + ID_OUI_FROM_DATABASE=Nokia Solutions (Shanghai) Co.,Ltd. + OUI:90F052* ID_OUI_FROM_DATABASE=MEIZU Technology Co., Ltd. @@ -115631,6 +116645,9 @@ OUI:90FB93* OUI:90FBA6* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:90FC55* + ID_OUI_FROM_DATABASE=Hyve Solutions + OUI:90FD61* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -116075,6 +117092,9 @@ OUI:944452* OUI:944560* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:944667* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:944696* ID_OUI_FROM_DATABASE=BaudTec Corporation @@ -116162,6 +117182,9 @@ OUI:9458CB* OUI:945907* ID_OUI_FROM_DATABASE=Shanghai HITE-BELDEN Network Technology Co., Ltd. +OUI:945915* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:94592D* ID_OUI_FROM_DATABASE=EKE Building Technology Systems Ltd @@ -116207,6 +117230,9 @@ OUI:9463D1* OUI:946424* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:946442* + ID_OUI_FROM_DATABASE=CELESTICA INC. + OUI:94652D* ID_OUI_FROM_DATABASE=OnePlus Technology (Shenzhen) Co., Ltd @@ -116315,6 +117341,9 @@ OUI:9487E0* OUI:948815* ID_OUI_FROM_DATABASE=Infinique Worldwide Inc +OUI:948835* + ID_OUI_FROM_DATABASE=CRESTRON ELECTRONICS, INC. + OUI:948854* ID_OUI_FROM_DATABASE=Texas Instruments @@ -118166,6 +119195,9 @@ OUI:989E63* OUI:989E80* ID_OUI_FROM_DATABASE=tonies GmbH +OUI:989E85* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:989F1A* ID_OUI_FROM_DATABASE=Private @@ -118490,6 +119522,9 @@ OUI:98E165* OUI:98E255* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd +OUI:98E301* + ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited + OUI:98E476* ID_OUI_FROM_DATABASE=Zentan @@ -118745,6 +119780,9 @@ OUI:98FE03* OUI:98FE3E* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:98FE54* + ID_OUI_FROM_DATABASE=Raspberry Pi (Trading) Ltd + OUI:98FE94* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -118805,6 +119843,9 @@ OUI:9C0971* OUI:9C098B* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:9C09CA* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:9C0B05* ID_OUI_FROM_DATABASE=eero inc. @@ -118880,6 +119921,9 @@ OUI:9C1FCA* OUI:9C1FDD* ID_OUI_FROM_DATABASE=Accupix Inc. +OUI:9C1FE6* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Display Technologies Co.,Ltd + OUI:9C207B* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -119150,6 +120194,9 @@ OUI:9C50D1* OUI:9C50EE* ID_OUI_FROM_DATABASE=Cambridge Industries(Group) Co.,Ltd. +OUI:9C5187* + ID_OUI_FROM_DATABASE=SUNITEC TECHNOLOGY CO.,LIMITED + OUI:9C52F8* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -119447,6 +120494,9 @@ OUI:9C7DC0* OUI:9C7F57* ID_OUI_FROM_DATABASE=UNIC Memory Technology Co Ltd +OUI:9C7F64* + ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. + OUI:9C7F81* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD @@ -119480,6 +120530,9 @@ OUI:9C84BF* OUI:9C8566* ID_OUI_FROM_DATABASE=Wingtech Mobile Communications Co.,Ltd. +OUI:9C862B* + ID_OUI_FROM_DATABASE=MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. + OUI:9C86DA* ID_OUI_FROM_DATABASE=Phoenix Geophysics Ltd. @@ -119600,6 +120653,9 @@ OUI:9C9C1F* OUI:9C9C40* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:9C9D07* + ID_OUI_FROM_DATABASE=FN-LINK TECHNOLOGY Ltd. + OUI:9C9D5D* ID_OUI_FROM_DATABASE=Raden Inc @@ -120077,6 +121133,9 @@ OUI:9CF155* OUI:9CF1D4* ID_OUI_FROM_DATABASE=Roku, Inc +OUI:9CF27E* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:9CF387* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -120320,6 +121379,9 @@ OUI:A013CB* OUI:A0143D* ID_OUI_FROM_DATABASE=PARROT SA +OUI:A0146D* + ID_OUI_FROM_DATABASE=Suzhou NODKA Automation Technology Co.,Ltd + OUI:A0165C* ID_OUI_FROM_DATABASE=Triteka LTD @@ -120386,6 +121448,9 @@ OUI:A019B2E* OUI:A01AE3* ID_OUI_FROM_DATABASE=Edgecore Americas Networking Corporation +OUI:A01B04* + ID_OUI_FROM_DATABASE=Hefei Huanxin Microelectronics Technology Co., Ltd. + OUI:A01B29* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -120857,6 +121922,9 @@ OUI:A05AA4* OUI:A05B21* ID_OUI_FROM_DATABASE=ENVINET GmbH +OUI:A05D0E* + ID_OUI_FROM_DATABASE=ALPSALPINE CO.,LTD. + OUI:A05DC1* ID_OUI_FROM_DATABASE=TMCT Co., LTD. @@ -121037,6 +122105,9 @@ OUI:A086C6* OUI:A086EC* ID_OUI_FROM_DATABASE=SAEHAN HITEC Co., Ltd +OUI:A087BE* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:A0885E* ID_OUI_FROM_DATABASE=Anhui Xiangyao New Energy Technology Co., Ltd. @@ -121451,6 +122522,9 @@ OUI:A0C5F2D* OUI:A0C5F2E* ID_OUI_FROM_DATABASE=Synapsys Solutions Ltd. +OUI:A0C6A5* + ID_OUI_FROM_DATABASE=Lierda Science & Technology Group Co., Ltd + OUI:A0C6EC* ID_OUI_FROM_DATABASE=ShenZhen ANYK Technology Co.,LTD @@ -121643,6 +122717,9 @@ OUI:A0EF84* OUI:A0F217* ID_OUI_FROM_DATABASE=GE Medical System(China) Co., Ltd. +OUI:A0F261* + ID_OUI_FROM_DATABASE=Palo Alto Networks + OUI:A0F262* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -121805,6 +122882,9 @@ OUI:A40E2B* OUI:A40E75* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:A40F25* + ID_OUI_FROM_DATABASE=eero inc. + OUI:A40F98* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -122108,6 +123188,9 @@ OUI:A43BFAE* OUI:A43BFAF* ID_OUI_FROM_DATABASE=Private +OUI:A43CD4* + ID_OUI_FROM_DATABASE=JBL Professional + OUI:A43CD7* ID_OUI_FROM_DATABASE=NTX Electronics YangZhou co.,LTD @@ -122246,6 +123329,51 @@ OUI:A44F29E* OUI:A44F29F* ID_OUI_FROM_DATABASE=Private +OUI:A44F3E0* + ID_OUI_FROM_DATABASE=United Automotive Electronic Systems Co.,Ltd + +OUI:A44F3E1* + ID_OUI_FROM_DATABASE=Netshield Europe Srl + +OUI:A44F3E2* + ID_OUI_FROM_DATABASE=RINVENT INDUSTRIES PRIVATE LIMITED + +OUI:A44F3E3* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:A44F3E4* + ID_OUI_FROM_DATABASE=ShenZhen hionetech Co,.ltd + +OUI:A44F3E5* + ID_OUI_FROM_DATABASE=Mobilint + +OUI:A44F3E6* + ID_OUI_FROM_DATABASE=Maven Pet Inc + +OUI:A44F3E7* + ID_OUI_FROM_DATABASE=Vinfast Trading and Production JSC + +OUI:A44F3E8* + ID_OUI_FROM_DATABASE=Neurable + +OUI:A44F3E9* + ID_OUI_FROM_DATABASE=LINK Group Inc. + +OUI:A44F3EA* + ID_OUI_FROM_DATABASE=CMCNI Co., Ltd + +OUI:A44F3EB* + ID_OUI_FROM_DATABASE=Suzhou AIDomex Intelligent Technology Co., Ltd. + +OUI:A44F3EC* + ID_OUI_FROM_DATABASE=JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED + +OUI:A44F3ED* + ID_OUI_FROM_DATABASE=NTT sonority, Inc. + +OUI:A44F3EE* + ID_OUI_FROM_DATABASE=ShenZhen Chainway Information Technology Co., Ltd. + OUI:A45006* ID_OUI_FROM_DATABASE=SHENZHEN HUACHUANG SHIDAI TECHNOLOGYCO.,LTD @@ -123485,6 +124613,9 @@ OUI:A81FAF* OUI:A82066* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:A821C8* + ID_OUI_FROM_DATABASE=shenzhen phoenix telecom technology Co.,Ltd. + OUI:A82316* ID_OUI_FROM_DATABASE=Nokia @@ -123515,6 +124646,9 @@ OUI:A82948* OUI:A8294C* ID_OUI_FROM_DATABASE=Precision Optical Transceivers, Inc. +OUI:A829DC* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:A82AD6* ID_OUI_FROM_DATABASE=Arthrex Inc. @@ -123977,6 +125111,9 @@ OUI:A8741D* OUI:A87484* ID_OUI_FROM_DATABASE=zte corporation +OUI:A8754E* + ID_OUI_FROM_DATABASE=Nexlawn Intelligent Technology (Suzhou) Co., Ltd. + OUI:A875D6* ID_OUI_FROM_DATABASE=FreeTek International Co., Ltd. @@ -124433,6 +125570,9 @@ OUI:A8DC5A* OUI:A8DD9F* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. +OUI:A8DDEC* + ID_OUI_FROM_DATABASE=Hangzhou BroadLink Technology Co., Ltd + OUI:A8DE68* ID_OUI_FROM_DATABASE=Beijing Wide Technology Co.,Ltd @@ -124517,6 +125657,9 @@ OUI:A8F038* OUI:A8F059* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:A8F07C* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:A8F1B2* ID_OUI_FROM_DATABASE=Allwinner Technology Co., Ltd @@ -124625,6 +125768,9 @@ OUI:AC0481* OUI:AC04AA* ID_OUI_FROM_DATABASE=GoPro +OUI:AC05C7* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:AC0613* ID_OUI_FROM_DATABASE=Senselogix Ltd @@ -124817,6 +125963,9 @@ OUI:AC2205* OUI:AC220B* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. +OUI:AC2241* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:AC2316* ID_OUI_FROM_DATABASE=Mist Systems, Inc. @@ -124826,6 +125975,9 @@ OUI:AC2334* OUI:AC233F* ID_OUI_FROM_DATABASE=Shenzhen Minew Technologies Co., Ltd. +OUI:AC2477* + ID_OUI_FROM_DATABASE=Shenzhen Tinno Mobile Technology Corp + OUI:AC276E* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -124853,6 +126005,9 @@ OUI:AC2DA9* OUI:AC2FA8* ID_OUI_FROM_DATABASE=Humannix Co.,Ltd. +OUI:AC3019* + ID_OUI_FROM_DATABASE=Shenzhen Hailingwei Electronics Co., Ltd. + OUI:AC3184* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -124979,6 +126134,9 @@ OUI:AC45CA* OUI:AC45EF* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:AC46A7* + ID_OUI_FROM_DATABASE=SERCOMM PHILIPPINES INC + OUI:AC471B* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -125051,6 +126209,9 @@ OUI:AC51AB* OUI:AC51EE* ID_OUI_FROM_DATABASE=Adtran Inc +OUI:AC5322* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:AC5474* ID_OUI_FROM_DATABASE=China Mobile IOT Company Limited @@ -125132,6 +126293,9 @@ OUI:AC61EA* OUI:AC620D* ID_OUI_FROM_DATABASE=Jabil Circuit(Wuxi) Co.,Ltd +OUI:AC62FF* + ID_OUI_FROM_DATABASE=Vantiva USA LLC + OUI:AC63BE* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -125411,6 +126575,9 @@ OUI:AC86D1D* OUI:AC86D1E* ID_OUI_FROM_DATABASE=Retina Development B.V. +OUI:AC8746* + ID_OUI_FROM_DATABASE=Huizhou BYD Electronic Co., Ltd. + OUI:AC87A3* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -125564,6 +126731,9 @@ OUI:ACA7F1* OUI:ACA88E* ID_OUI_FROM_DATABASE=SHARP Corporation +OUI:ACA899* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:ACA919* ID_OUI_FROM_DATABASE=TrekStor GmbH @@ -125585,6 +126755,9 @@ OUI:ACACE2* OUI:ACAD4B* ID_OUI_FROM_DATABASE=zte corporation +OUI:ACADEF* + ID_OUI_FROM_DATABASE=Wanan Hongsheng Electronic Co.Ltd + OUI:ACAE19* ID_OUI_FROM_DATABASE=Roku, Inc @@ -126332,6 +127505,9 @@ OUI:B03CDC* OUI:B03D96* ID_OUI_FROM_DATABASE=Vision Valley FZ LLC +OUI:B03DBF* + ID_OUI_FROM_DATABASE=shenzhen ceita communications technology co.,ltd + OUI:B03DC2* ID_OUI_FROM_DATABASE=Wasp artificial intelligence(Shenzhen) Co.,ltd @@ -126344,6 +127520,9 @@ OUI:B03EB0* OUI:B03F64* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B03FD3* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B04089* ID_OUI_FROM_DATABASE=Senient Systems LTD @@ -126548,6 +127727,9 @@ OUI:B061A9* OUI:B061C7* ID_OUI_FROM_DATABASE=Ericsson-LG Enterprise +OUI:B064E0* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B0653A* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. @@ -126824,9 +128006,15 @@ OUI:B09AE2* OUI:B09BD4* ID_OUI_FROM_DATABASE=GNH Software India Private Limited +OUI:B09C18* + ID_OUI_FROM_DATABASE=Shenzhen Taichi Technology Limited + OUI:B09C63* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:B09CB2* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:B09E1B* ID_OUI_FROM_DATABASE=Butlr Technologies, Inc. @@ -127271,6 +128459,9 @@ OUI:B0DD74* OUI:B0DE28* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B0DE31* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B0DF3A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -127631,6 +128822,9 @@ OUI:B41513* OUI:B4157E* ID_OUI_FROM_DATABASE=Celona Inc. +OUI:B41584* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B41678* ID_OUI_FROM_DATABASE=Juniper Networks @@ -127661,6 +128855,9 @@ OUI:B41C30* OUI:B41CAB* ID_OUI_FROM_DATABASE=ICR, inc. +OUI:B41CAF* + ID_OUI_FROM_DATABASE=UAB TELTONIKA NETWORKS + OUI:B41D2B* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd @@ -127859,6 +129056,9 @@ OUI:B43A96* OUI:B43AE2* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:B43B52* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:B43D08* ID_OUI_FROM_DATABASE=GX International BV @@ -128114,6 +129314,9 @@ OUI:B4636F* OUI:B46415* ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +OUI:B465DC* + ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED + OUI:B46698* ID_OUI_FROM_DATABASE=Zealabs srl @@ -128273,6 +129476,9 @@ OUI:B48B19* OUI:B48C9D* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:B490E5* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:B49107* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -128300,6 +129506,9 @@ OUI:B4994C* OUI:B499BA* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:B49A7D* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:B49A95* ID_OUI_FROM_DATABASE=Shenzhen Boomtech Industrial Corporation @@ -128729,6 +129938,9 @@ OUI:B4DE31* OUI:B4DEDF* ID_OUI_FROM_DATABASE=zte corporation +OUI:B4DF09* + ID_OUI_FROM_DATABASE=FLUX:: + OUI:B4DF3B* ID_OUI_FROM_DATABASE=Chromlech @@ -128966,6 +130178,9 @@ OUI:B80B9D* OUI:B80BDA* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. +OUI:B80E1D* + ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. + OUI:B810D4* ID_OUI_FROM_DATABASE=Masimo Corporation @@ -129035,6 +130250,9 @@ OUI:B81E9E* OUI:B81EA4* ID_OUI_FROM_DATABASE=Liteon Technology Corporation +OUI:B81F3F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B81F5E* ID_OUI_FROM_DATABASE=Apption Labs Limited @@ -129485,6 +130703,9 @@ OUI:B8797E* OUI:B87AC9* ID_OUI_FROM_DATABASE=Siemens Ltd. +OUI:B87B4D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B87BC5* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -129689,6 +130910,9 @@ OUI:B89BE4* OUI:B89C13* ID_OUI_FROM_DATABASE=Alps Alpine +OUI:B89DE5* + ID_OUI_FROM_DATABASE=ASIX Electronics Corporation + OUI:B89EA6* ID_OUI_FROM_DATABASE=SPBEC-MINING CO.LTD @@ -129944,6 +131168,9 @@ OUI:B8CEF6* OUI:B8D06F* ID_OUI_FROM_DATABASE=GUANGZHOU HKUST FOK YING TUNG RESEARCH INSTITUTE +OUI:B8D08F* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:B8D0F0* ID_OUI_FROM_DATABASE=FCNT LLC @@ -130046,6 +131273,9 @@ OUI:B8D94D* OUI:B8D9CE* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:B8DA5E* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:B8DAE8* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -130061,6 +131291,12 @@ OUI:B8DB1C* OUI:B8DB38* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:B8DC28* + ID_OUI_FROM_DATABASE=Extreme Networks Headquarters + +OUI:B8DC7D* + ID_OUI_FROM_DATABASE=VusionGroup + OUI:B8DC87* ID_OUI_FROM_DATABASE=IAI Corporation @@ -130727,6 +131963,9 @@ OUI:BC4E3C* OUI:BC4E5D* ID_OUI_FROM_DATABASE=ZhongMiao Technology Co., Ltd. +OUI:BC4F2D* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:BC515F* ID_OUI_FROM_DATABASE=Nokia Solutions and Networks India Private Limited @@ -131054,6 +132293,9 @@ OUI:BC89A6* OUI:BC89A7* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:BC89C1* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:BC89F8* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. @@ -131096,6 +132338,9 @@ OUI:BC9307* OUI:BC9325* ID_OUI_FROM_DATABASE=Ningbo Joyson Preh Car Connect Co.,Ltd. +OUI:BC932A* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:BC9424* ID_OUI_FROM_DATABASE=TCT mobile ltd @@ -131255,6 +132500,9 @@ OUI:BCA5A9* OUI:BCA68D* ID_OUI_FROM_DATABASE=Continetal Automotive Systems Sibiu +OUI:BCA6E7* + ID_OUI_FROM_DATABASE=Sichuan Odot Automation System Co., Ltd. + OUI:BCA8A6* ID_OUI_FROM_DATABASE=Intel Corporate @@ -131321,6 +132569,9 @@ OUI:BCB2CC* OUI:BCB308* ID_OUI_FROM_DATABASE=HONGKONG RAGENTEK COMMUNICATION TECHNOLOGY CO.,LIMITED +OUI:BCB30E* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:BCB4FD* ID_OUI_FROM_DATABASE=NXP Semiconductor (Tianjin) LTD. @@ -131363,6 +132614,9 @@ OUI:BCBD9E* OUI:BCBEFB* ID_OUI_FROM_DATABASE=ASL Xiamen Technology CO., LTD +OUI:BCBF2E* + ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. + OUI:BCC00F* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -131657,6 +132911,9 @@ OUI:BCF9F2* OUI:BCFAB8* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:BCFABA* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:BCFAEB* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -132452,6 +133709,9 @@ OUI:C08ADE* OUI:C08B05* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:C08B27* + ID_OUI_FROM_DATABASE=FN-LINK TECHNOLOGY Ltd. + OUI:C08B2A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -132620,6 +133880,9 @@ OUI:C0A26D* OUI:C0A364* ID_OUI_FROM_DATABASE=3D Systems Massachusetts +OUI:C0A36D* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:C0A36E* ID_OUI_FROM_DATABASE=SKY UK LIMITED @@ -132635,6 +133898,9 @@ OUI:C0A476* OUI:C0A4B9* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. +OUI:C0A4CF* + ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd + OUI:C0A53E* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -132723,7 +133989,7 @@ OUI:C0B8E6* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD OUI:C0BA1F* - ID_OUI_FROM_DATABASE=Private + ID_OUI_FROM_DATABASE=Xiamen Milesight IoT Co., Ltd. OUI:C0BAE6* ID_OUI_FROM_DATABASE=Zenitel GB Ltd @@ -132977,6 +134243,9 @@ OUI:C0E434* OUI:C0E54E* ID_OUI_FROM_DATABASE=ARIES Embedded GmbH +OUI:C0E579* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:C0E5DA* ID_OUI_FROM_DATABASE=Qingdao Intelligent&Precise Electronics Co.,Ltd. @@ -133886,6 +135155,9 @@ OUI:C482721* OUI:C482722* ID_OUI_FROM_DATABASE=Digisine Energytech Co., Ltd. +OUI:C482723* + ID_OUI_FROM_DATABASE=NextSilicon + OUI:C482724* ID_OUI_FROM_DATABASE=Melecs EWS GmbH @@ -133997,6 +135269,9 @@ OUI:C489ED* OUI:C48A5A* ID_OUI_FROM_DATABASE=JFCONTROL +OUI:C48ACE* + ID_OUI_FROM_DATABASE=HISENSE VISUAL TECHNOLOGY CO.,LTD + OUI:C48B66* ID_OUI_FROM_DATABASE=Hui Zhou Gaoshengda Technology Co.,LTD @@ -134024,6 +135299,9 @@ OUI:C491CF* OUI:C4924C* ID_OUI_FROM_DATABASE=KEISOKUKI CENTER CO.,LTD. +OUI:C492D9* + ID_OUI_FROM_DATABASE=zte corporation + OUI:C49300* ID_OUI_FROM_DATABASE=8Devices @@ -134093,6 +135371,9 @@ OUI:C4955F* OUI:C495A2* ID_OUI_FROM_DATABASE=SHENZHEN WEIJIU INDUSTRY AND TRADE DEVELOPMENT CO., LTD +OUI:C4969F* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:C49805* ID_OUI_FROM_DATABASE=Minieum Networks, Inc @@ -135113,6 +136394,9 @@ OUI:C82E47* OUI:C82E94* ID_OUI_FROM_DATABASE=Halfa Enterprise Co., Ltd. +OUI:C83049* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:C83168* ID_OUI_FROM_DATABASE=eZEX corporation @@ -135194,6 +136478,9 @@ OUI:C84029* OUI:C84052* ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. +OUI:C8412E* + ID_OUI_FROM_DATABASE=AM Telecom co., Ltd. + OUI:C8418A* ID_OUI_FROM_DATABASE=Samsung Electronics.,LTD @@ -136134,7 +137421,7 @@ OUI:C8D719* ID_OUI_FROM_DATABASE=Cisco-Linksys, LLC OUI:C8D778* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:C8D779* ID_OUI_FROM_DATABASE=QING DAO HAIER TELECOM CO.,LTD. @@ -136517,6 +137804,9 @@ OUI:CC115A* OUI:CC1228* ID_OUI_FROM_DATABASE=HISENSE VISUAL TECHNOLOGY CO.,LTD +OUI:CC13F3* + ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. + OUI:CC14A6* ID_OUI_FROM_DATABASE=Yichun MyEnergy Domain, Inc @@ -137042,6 +138332,9 @@ OUI:CC60BB* OUI:CC60C8* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:CC6146* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:CC61E5* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -137171,6 +138464,9 @@ OUI:CC79D7* OUI:CC7A30* ID_OUI_FROM_DATABASE=CMAX Wireless Co., Ltd. +OUI:CC7A8B* + ID_OUI_FROM_DATABASE=SHENZHEN TECNO TECHNOLOGY + OUI:CC7B35* ID_OUI_FROM_DATABASE=zte corporation @@ -137204,6 +138500,9 @@ OUI:CC808F* OUI:CC812A* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:CC8130* + ID_OUI_FROM_DATABASE=Intelbras + OUI:CC817D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -137357,6 +138656,9 @@ OUI:CCA223* OUI:CCA260* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:CCA30C* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:CCA374* ID_OUI_FROM_DATABASE=Guangdong Guanglian Electronic Technology Co.Ltd @@ -137768,6 +139070,9 @@ OUI:CCDBA7* OUI:CCDC55* ID_OUI_FROM_DATABASE=Dragonchip Limited +OUI:CCDD28* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:CCDD58* ID_OUI_FROM_DATABASE=Robert Bosch GmbH @@ -137804,6 +139109,51 @@ OUI:CCE686* OUI:CCE798* ID_OUI_FROM_DATABASE=My Social Stuff +OUI:CCE7DE0* + ID_OUI_FROM_DATABASE=Shenzhen Jooan Technology Co., Ltd + +OUI:CCE7DE1* + ID_OUI_FROM_DATABASE=Kaze.AI Technology Co.,Ltd. + +OUI:CCE7DE2* + ID_OUI_FROM_DATABASE=Octopus Energy Ltd + +OUI:CCE7DE3* + ID_OUI_FROM_DATABASE=Private + +OUI:CCE7DE4* + ID_OUI_FROM_DATABASE=Fareco + +OUI:CCE7DE5* + ID_OUI_FROM_DATABASE=Chengdu Vantron Technology Co., Ltd. + +OUI:CCE7DE6* + ID_OUI_FROM_DATABASE=Skylight + +OUI:CCE7DE7* + ID_OUI_FROM_DATABASE=Shanghai Dabuziduo Information and Technology Co., Ltd. + +OUI:CCE7DE8* + ID_OUI_FROM_DATABASE=Private + +OUI:CCE7DE9* + ID_OUI_FROM_DATABASE=Shenzhen Qichang Intelligent Technology Co., Ltd. + +OUI:CCE7DEA* + ID_OUI_FROM_DATABASE=Opal Camera Inc. + +OUI:CCE7DEB* + ID_OUI_FROM_DATABASE=3D Computing + +OUI:CCE7DEC* + ID_OUI_FROM_DATABASE=Guangdong Sirivision Communication Technology Co.,LTD. + +OUI:CCE7DED* + ID_OUI_FROM_DATABASE=Juke Audio + +OUI:CCE7DEE* + ID_OUI_FROM_DATABASE=Shenzhen Xingyi Intelligent Technology Co.,Ltd + OUI:CCE7DF* ID_OUI_FROM_DATABASE=American Magnetics, Inc. @@ -137942,6 +139292,9 @@ OUI:D003EB* OUI:D00401* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:D00477* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:D00492* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -137975,6 +139328,9 @@ OUI:D00AAB* OUI:D00B27* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:D00C5E* + ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. + OUI:D00DF7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -138002,6 +139358,9 @@ OUI:D012CB* OUI:D0131E* ID_OUI_FROM_DATABASE=Sunrex Technology Corp +OUI:D013C1* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + OUI:D013FD* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) @@ -138800,6 +140159,9 @@ OUI:D082EB* OUI:D083D4* ID_OUI_FROM_DATABASE=Xtel Wireless ApS +OUI:D0845D* + ID_OUI_FROM_DATABASE=B&C Transit, Inc. + OUI:D084B0* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -139217,6 +140579,9 @@ OUI:D0B5C2* OUI:D0B60A* ID_OUI_FROM_DATABASE=Xingluo Technology Company Limited +OUI:D0B646* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:D0B66F* ID_OUI_FROM_DATABASE=SERNET (SUZHOU) TECHNOLOGIES CORPORATION @@ -139418,6 +140783,9 @@ OUI:D0D471* OUI:D0D49F* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:D0D4FB* + ID_OUI_FROM_DATABASE=Home Control Singapore Pte Ltd + OUI:D0D6CC* ID_OUI_FROM_DATABASE=Wintop @@ -139529,6 +140897,9 @@ OUI:D0E828* OUI:D0EA11* ID_OUI_FROM_DATABASE=Routerboard.com +OUI:D0EA30* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:D0EB03* ID_OUI_FROM_DATABASE=Zhehua technology limited @@ -139646,6 +141017,9 @@ OUI:D404E6* OUI:D404FF* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:D40592* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:D40598* ID_OUI_FROM_DATABASE=Commscope @@ -139892,6 +141266,12 @@ OUI:D429A7* OUI:D429EA* ID_OUI_FROM_DATABASE=Zimory GmbH +OUI:D42B6F* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + +OUI:D42BF0* + ID_OUI_FROM_DATABASE=Tiinlab Corporation + OUI:D42C0F* ID_OUI_FROM_DATABASE=Commscope @@ -140339,6 +141719,9 @@ OUI:D47208* OUI:D47226* ID_OUI_FROM_DATABASE=zte corporation +OUI:D47327* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:D47350* ID_OUI_FROM_DATABASE=DBG Commnunications Technology Co., Ltd. @@ -140567,6 +141950,9 @@ OUI:D49400* OUI:D4945A* ID_OUI_FROM_DATABASE=COSMO CO., LTD +OUI:D49477* + ID_OUI_FROM_DATABASE=FONEX Data Systems Inc. + OUI:D494A1* ID_OUI_FROM_DATABASE=Texas Instruments @@ -140981,6 +142367,9 @@ OUI:D4D51B* OUI:D4D659* ID_OUI_FROM_DATABASE=Meta Platforms, Inc. +OUI:D4D6DF* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:D4D748* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -141551,6 +142940,9 @@ OUI:D84567* OUI:D84606* ID_OUI_FROM_DATABASE=Silicon Valley Global Marketing +OUI:D846CE* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:D84710* ID_OUI_FROM_DATABASE=Sichuan Changhong Electric Ltd. @@ -141689,6 +143081,9 @@ OUI:D860B0* OUI:D860B3* ID_OUI_FROM_DATABASE=Guangdong Global Electronic Technology CO.,LTD +OUI:D860C5* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:D86162* ID_OUI_FROM_DATABASE=WNC Corporation @@ -142061,6 +143456,9 @@ OUI:D8A315* OUI:D8A35C* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:D8A469* + ID_OUI_FROM_DATABASE=Sonova AG + OUI:D8A491* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -142130,6 +143528,9 @@ OUI:D8B12A* OUI:D8B190* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:D8B1DE* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:D8B249* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -142184,6 +143585,9 @@ OUI:D8BE1F* OUI:D8BE65* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:D8BF42* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:D8BF4C* ID_OUI_FROM_DATABASE=Victory Concept Electronics Limited @@ -142262,6 +143666,9 @@ OUI:D8CF89* OUI:D8CF9C* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:D8CFB1* + ID_OUI_FROM_DATABASE=BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED + OUI:D8CFBF* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -142607,6 +144014,9 @@ OUI:DC1A01* OUI:DC1AC5* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:DC1B48* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:DC1BA1* ID_OUI_FROM_DATABASE=Intel Corporate @@ -142689,7 +144099,7 @@ OUI:DC2DCB* ID_OUI_FROM_DATABASE=Beijing Unis HengYue Technology Co., Ltd. OUI:DC2DDE* - ID_OUI_FROM_DATABASE=Ledworks SRL + ID_OUI_FROM_DATABASE=Illucere Srl OUI:DC2E6A* ID_OUI_FROM_DATABASE=HCT. Co., Ltd. @@ -142718,6 +144128,9 @@ OUI:DC3262* OUI:DC330D* ID_OUI_FROM_DATABASE=QING DAO HAIER TELECOM CO.,LTD. +OUI:DC330E* + ID_OUI_FROM_DATABASE=Qingdao Haier Technology Co.Ltd + OUI:DC333D* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -143255,6 +144668,9 @@ OUI:DC86D8* OUI:DC87CB* ID_OUI_FROM_DATABASE=Beijing Perfectek Technologies Co., Ltd. +OUI:DC87F8* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:DC88A1* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -143279,6 +144695,9 @@ OUI:DC8D91* OUI:DC8DB7* ID_OUI_FROM_DATABASE=ATW TECHNOLOGY, INC. +OUI:DC8E6D* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:DC8E8D* ID_OUI_FROM_DATABASE=Netis Technology Co., Ltd. @@ -143360,6 +144779,9 @@ OUI:DC9C99* OUI:DC9C9F* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd +OUI:DC9DED* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:DC9E8F* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -143561,6 +144983,9 @@ OUI:DCC793* OUI:DCC8F5* ID_OUI_FROM_DATABASE=Shanghai UMEinfo CO.,LTD. +OUI:DCCB35* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:DCCBA8* ID_OUI_FROM_DATABASE=Explora Technologies Inc @@ -143822,6 +145247,9 @@ OUI:DCF090* OUI:DCF110* ID_OUI_FROM_DATABASE=Nokia Corporation +OUI:DCF144* + ID_OUI_FROM_DATABASE=Ocean Solution Technology + OUI:DCF31C* ID_OUI_FROM_DATABASE=Texas Instruments @@ -144404,6 +145832,9 @@ OUI:E04E7A* OUI:E04F43* ID_OUI_FROM_DATABASE=Universal Global Scientific Industrial., Ltd +OUI:E04F95* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:E04FBD* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD @@ -144564,7 +145995,7 @@ OUI:E06C4E* ID_OUI_FROM_DATABASE=Shenzhen TINNO Mobile Technology Corp. OUI:E06CA6* - ID_OUI_FROM_DATABASE=Creotech Instruments S.A. + ID_OUI_FROM_DATABASE=Creotech Quantum SA OUI:E06CC5* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -144719,6 +146150,9 @@ OUI:E092A7* OUI:E09467* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:E09559* + ID_OUI_FROM_DATABASE=Arcadyan Corporation + OUI:E09579* ID_OUI_FROM_DATABASE=ORTHOsoft inc, d/b/a Zimmer CAS @@ -144800,6 +146234,9 @@ OUI:E0A366* OUI:E0A3AC* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E0A447* + ID_OUI_FROM_DATABASE=zte corporation + OUI:E0A498* ID_OUI_FROM_DATABASE=SHENZHEN ORFA TECH CO.,LTD @@ -145085,6 +146522,9 @@ OUI:E0D173* OUI:E0D1E6* ID_OUI_FROM_DATABASE=Aliph dba Jawbone +OUI:E0D239* + ID_OUI_FROM_DATABASE=TECHNOLID, LLC + OUI:E0D31A* ID_OUI_FROM_DATABASE=EQUES Technology Co., Limited @@ -145097,6 +146537,9 @@ OUI:E0D38E* OUI:E0D3B4* ID_OUI_FROM_DATABASE=Cisco Meraki +OUI:E0D3F0* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:E0D462* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -145211,6 +146654,9 @@ OUI:E0E751* OUI:E0E7BB* ID_OUI_FROM_DATABASE=Nureva, Inc. +OUI:E0E805* + ID_OUI_FROM_DATABASE=SERNET (SUZHOU) TECHNOLOGIES CORPORATION + OUI:E0E8BB* ID_OUI_FROM_DATABASE=Unicom Vsens Telecommunications Co., Ltd. @@ -146051,6 +147497,9 @@ OUI:E48F34* OUI:E48F65* ID_OUI_FROM_DATABASE=Yelatma Instrument Making Enterprise, JSC +OUI:E48FB7* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:E4902A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -146144,6 +147593,9 @@ OUI:E498D1* OUI:E498D6* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:E498E0* + ID_OUI_FROM_DATABASE=Tonly Technology Co. Ltd + OUI:E4995F* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -146357,6 +147809,9 @@ OUI:E4C90B* OUI:E4CA12* ID_OUI_FROM_DATABASE=zte corporation +OUI:E4CA5F* + ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. + OUI:E4CB59* ID_OUI_FROM_DATABASE=Beijing Loveair Science and Technology Co. Ltd. @@ -146537,6 +147992,9 @@ OUI:E4FA5B* OUI:E4FAC4* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:E4FADE* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:E4FAE4* ID_OUI_FROM_DATABASE=Shenzhen SDMC Technology CP,.LTD @@ -146576,6 +148034,9 @@ OUI:E4FED9* OUI:E4FEE4* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:E4FF69* + ID_OUI_FROM_DATABASE=Holiday Robotics + OUI:E4FFDD* ID_OUI_FROM_DATABASE=ELECTRON INDIA @@ -147608,6 +149069,12 @@ OUI:E8B723* OUI:E8B748* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:E8B853* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + +OUI:E8BA17* + ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd + OUI:E8BA70* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -147659,6 +149126,9 @@ OUI:E8C320* OUI:E8C386* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:E8C3C5* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:E8C417* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -147857,6 +149327,9 @@ OUI:E8E8B7* OUI:E8E98E* ID_OUI_FROM_DATABASE=SOLAR controls s.r.o. +OUI:E8EA34* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:E8EA4D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -147950,6 +149423,9 @@ OUI:E8F654* OUI:E8F673* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:E8F674* + ID_OUI_FROM_DATABASE=Jiang Su Fulian Communication Technology Co.,Ltd + OUI:E8F6D70* ID_OUI_FROM_DATABASE=Mono Technologies Inc. @@ -148277,6 +149753,9 @@ OUI:EC2AF0* OUI:EC2BEB* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:EC2C0D* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:EC2C11* ID_OUI_FROM_DATABASE=CWD INNOVATION LIMITED @@ -148326,7 +149805,7 @@ OUI:EC316D* ID_OUI_FROM_DATABASE=Hansgrohe OUI:EC34E2* - ID_OUI_FROM_DATABASE=Private + ID_OUI_FROM_DATABASE=Yasmina Labs Trading FZE OUI:EC3532* ID_OUI_FROM_DATABASE=Tactrix Inc. @@ -148640,6 +150119,9 @@ OUI:EC6FF9* OUI:EC7097* ID_OUI_FROM_DATABASE=Commscope +OUI:EC715E* + ID_OUI_FROM_DATABASE=Freefly Systems Inc + OUI:EC71DB* ID_OUI_FROM_DATABASE=Reolink Innovation Limited @@ -148844,6 +150326,9 @@ OUI:EC8EAE* OUI:EC8EB5* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:EC8F72* + ID_OUI_FROM_DATABASE=Barrot Technology Co.,Ltd. + OUI:EC90C1* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -149021,6 +150506,9 @@ OUI:EC9F0DE* OUI:ECA138* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:ECA1CC* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:ECA1D1* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -149144,6 +150632,9 @@ OUI:ECB1D7* OUI:ECB1E0* ID_OUI_FROM_DATABASE=Eltex Enterprise LTD +OUI:ECB293* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:ECB313* ID_OUI_FROM_DATABASE=SHENZHEN GONGJIN ELECTRONICS CO.,LT @@ -149174,9 +150665,15 @@ OUI:ECB878* OUI:ECB907* ID_OUI_FROM_DATABASE=CloudGenix Inc +OUI:ECB931* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:ECB970* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD +OUI:ECB9A5* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:ECBAFE* ID_OUI_FROM_DATABASE=GIROPTIC @@ -149300,6 +150797,9 @@ OUI:ECDA59* OUI:ECDB86* ID_OUI_FROM_DATABASE=API-K +OUI:ECDCAA* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:ECDD24* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -149976,7 +151476,7 @@ OUI:F040AFE* ID_OUI_FROM_DATABASE=Shanghai Kanghai Information System CO.,LTD. OUI:F040EC* - ID_OUI_FROM_DATABASE=RainX PTE. LTD. + ID_OUI_FROM_DATABASE=LOOPDESIGNLAB PTE. LTD OUI:F041C6* ID_OUI_FROM_DATABASE=Heat Tech Company, Ltd. @@ -150176,6 +151676,9 @@ OUI:F065DD* OUI:F06728* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:F067B1* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:F06853* ID_OUI_FROM_DATABASE=Integrated Corporation @@ -150428,6 +151931,9 @@ OUI:F0A35A* OUI:F0A3B2* ID_OUI_FROM_DATABASE=Hui Zhou Gaoshengda Technology Co.,LTD +OUI:F0A4EA* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:F0A654* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -150551,6 +152057,9 @@ OUI:F0B11D* OUI:F0B13F* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:F0B163* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:F0B2B9* ID_OUI_FROM_DATABASE=Intel Corporate @@ -150977,6 +152486,12 @@ OUI:F0FEE7* OUI:F40046* ID_OUI_FROM_DATABASE=ON Semiconductor +OUI:F400A2* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + +OUI:F401CC* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:F40223* ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. @@ -151127,6 +152642,9 @@ OUI:F41563* OUI:F415FD* ID_OUI_FROM_DATABASE=Shanghai Pateo Electronic Equipment Manufacturing Co., Ltd. +OUI:F416E7* + ID_OUI_FROM_DATABASE=Skyverse Limited + OUI:F417B8* ID_OUI_FROM_DATABASE=AirTies Wireless Networks @@ -151226,6 +152744,9 @@ OUI:F42012* OUI:F42015* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:F4204D* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:F420550* ID_OUI_FROM_DATABASE=AsicFlag @@ -151289,6 +152810,9 @@ OUI:F42462* OUI:F4248B* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:F4253C* + ID_OUI_FROM_DATABASE=eero inc. + OUI:F42679* ID_OUI_FROM_DATABASE=Intel Corporate @@ -151337,6 +152861,9 @@ OUI:F42E48* OUI:F42E7F* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:F42F97* + ID_OUI_FROM_DATABASE=Embrava USA, Inc + OUI:F4308B* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -152069,6 +153596,9 @@ OUI:F4A157* OUI:F4A17F* ID_OUI_FROM_DATABASE=Marquardt Electronics Technology (Shanghai) Co.Ltd +OUI:F4A1A6* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:F4A294* ID_OUI_FROM_DATABASE=EAGLE WORLD DEVELOPMENT CO., LIMITED @@ -152219,6 +153749,9 @@ OUI:F4B7B3* OUI:F4B7E2* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:F4B821* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:F4B85E* ID_OUI_FROM_DATABASE=Texas Instruments @@ -152543,6 +154076,9 @@ OUI:F4F647* OUI:F4F70C* ID_OUI_FROM_DATABASE=Avang - neterbit +OUI:F4F91E* + ID_OUI_FROM_DATABASE=INGRAM MICRO SERVICES + OUI:F4F951* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -152981,6 +154517,9 @@ OUI:F83094* OUI:F8313E* ID_OUI_FROM_DATABASE=endeavour GmbH +OUI:F832BA* + ID_OUI_FROM_DATABASE=VusionGroup + OUI:F832E4* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. @@ -153053,6 +154592,9 @@ OUI:F83EB0* OUI:F83F51* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:F84068* + ID_OUI_FROM_DATABASE=SZ DJI Ronin Technology Co., Ltd. + OUI:F84288* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -153239,6 +154781,9 @@ OUI:F862AA* OUI:F8633F* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:F86347* + ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. + OUI:F863D9* ID_OUI_FROM_DATABASE=Commscope @@ -153329,6 +154874,51 @@ OUI:F873A2* OUI:F873DF* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:F875280* + ID_OUI_FROM_DATABASE=Qube Cinema Technologies Pvt Ltd + +OUI:F875281* + ID_OUI_FROM_DATABASE=Wuhan Xingtuxinke ELectronic Co.,Ltd + +OUI:F875282* + ID_OUI_FROM_DATABASE=Lyte AI + +OUI:F875283* + ID_OUI_FROM_DATABASE=SGSG Science&Technology Co., Ltd. Zhuhai + +OUI:F875284* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F875285* + ID_OUI_FROM_DATABASE=NORBIT ASA + +OUI:F875286* + ID_OUI_FROM_DATABASE=KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. + +OUI:F875287* + ID_OUI_FROM_DATABASE=PANASONIC AUTOMOTIVE SYSTEM MALAYSIA + +OUI:F875288* + ID_OUI_FROM_DATABASE=Myers Emergency Power Systems + +OUI:F875289* + ID_OUI_FROM_DATABASE=RLS d.o.o. + +OUI:F87528A* + ID_OUI_FROM_DATABASE=Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. + +OUI:F87528B* + ID_OUI_FROM_DATABASE=SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD + +OUI:F87528C* + ID_OUI_FROM_DATABASE=Origin Acoustics, LLC + +OUI:F87528D* + ID_OUI_FROM_DATABASE=After Technologies + +OUI:F87528E* + ID_OUI_FROM_DATABASE=Ingersoll-Rand + OUI:F87588* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -153665,6 +155255,9 @@ OUI:F8A4FB* OUI:F8A5C5* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:F8A5E6* + ID_OUI_FROM_DATABASE=Magicyo Technology CO.,Ltd + OUI:F8A73A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -153806,6 +155399,9 @@ OUI:F8B8B4* OUI:F8B95A* ID_OUI_FROM_DATABASE=LG Innotek +OUI:F8BA98* + ID_OUI_FROM_DATABASE=HUAQIN TECHNOLOGY CO., LTD + OUI:F8BAE6* ID_OUI_FROM_DATABASE=Nokia @@ -153881,6 +155477,51 @@ OUI:F8C903* OUI:F8C96C* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:F8C9D60* + ID_OUI_FROM_DATABASE=Ningbo Ming Sing Optical R&D Co.,Ltd + +OUI:F8C9D61* + ID_OUI_FROM_DATABASE=Beijing Mlink Technology Inc. + +OUI:F8C9D62* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F8C9D63* + ID_OUI_FROM_DATABASE=CPflight_srl + +OUI:F8C9D64* + ID_OUI_FROM_DATABASE=Active Research Limited + +OUI:F8C9D65* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F8C9D66* + ID_OUI_FROM_DATABASE=Shanghai Innovatech Information Technology Co., Ltd. + +OUI:F8C9D67* + ID_OUI_FROM_DATABASE=Lecip Arcontia AB + +OUI:F8C9D68* + ID_OUI_FROM_DATABASE=Miri Technologies, Inc + +OUI:F8C9D69* + ID_OUI_FROM_DATABASE=Dimetix AG + +OUI:F8C9D6A* + ID_OUI_FROM_DATABASE=DIAS Infrared GmbH + +OUI:F8C9D6B* + ID_OUI_FROM_DATABASE=VT100 SRL + +OUI:F8C9D6C* + ID_OUI_FROM_DATABASE=Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD + +OUI:F8C9D6D* + ID_OUI_FROM_DATABASE=Fortis Medical Devices LTD + +OUI:F8C9D6E* + ID_OUI_FROM_DATABASE=Shenzhen smart-core technology co.,ltd. + OUI:F8CA59* ID_OUI_FROM_DATABASE=NetComm Wireless @@ -153959,6 +155600,9 @@ OUI:F8D758* OUI:F8D7BF* ID_OUI_FROM_DATABASE=REV Ritter GmbH +OUI:F8D811* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:F8D9B8* ID_OUI_FROM_DATABASE=Open Mesh, Inc. @@ -154040,6 +155684,9 @@ OUI:F8E61A* OUI:F8E71E* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:F8E73C* + ID_OUI_FROM_DATABASE=Ufispace Co., LTD. + OUI:F8E7A0* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -154067,6 +155714,9 @@ OUI:F8E968* OUI:F8EA0A* ID_OUI_FROM_DATABASE=Dipl.-Math. Michael Rauch +OUI:F8ECFE* + ID_OUI_FROM_DATABASE=Owl Home Inc. + OUI:F8EDA5* ID_OUI_FROM_DATABASE=Commscope @@ -154352,6 +156002,9 @@ OUI:FC2325* OUI:FC23CD* ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD +OUI:FC2422* + ID_OUI_FROM_DATABASE=Hangzhou Ezviz Software Co.,Ltd. + OUI:FC253F* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -154433,6 +156086,9 @@ OUI:FC35E6* OUI:FC372B* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:FC376D* + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD + OUI:FC386A* ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd @@ -154619,6 +156275,9 @@ OUI:FC58DF* OUI:FC58FA* ID_OUI_FROM_DATABASE=Shen Zhen Shi Xin Zhong Xin Technology Co.,Ltd. +OUI:FC597A* + ID_OUI_FROM_DATABASE=Zebra Technologies Inc. + OUI:FC599F* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD @@ -154766,6 +156425,9 @@ OUI:FC6DC0* OUI:FC6DD1* ID_OUI_FROM_DATABASE=APRESIA Systems, Ltd. +OUI:FC6E83* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:FC6FB7* ID_OUI_FROM_DATABASE=Commscope @@ -155225,6 +156887,9 @@ OUI:FCB6D8* OUI:FCB7F0* ID_OUI_FROM_DATABASE=Idaho National Laboratory +OUI:FCB948* + ID_OUI_FROM_DATABASE=McScience Inc. + OUI:FCB97E* ID_OUI_FROM_DATABASE=GE Appliances @@ -155549,6 +157214,9 @@ OUI:FCE9D8* OUI:FCEA50* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. +OUI:FCEB7B* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:FCECDA* ID_OUI_FROM_DATABASE=Ubiquiti Inc @@ -155612,6 +157280,9 @@ OUI:FCFBFB* OUI:FCFC48* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:FCFD71* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:FCFE77* ID_OUI_FROM_DATABASE=Hitachi Reftechno, Inc. diff --git a/hwdb.d/20-acpi-vendor.hwdb b/hwdb.d/20-acpi-vendor.hwdb index c34293fef8fe6..f8e90d6fc6419 100644 --- a/hwdb.d/20-acpi-vendor.hwdb +++ b/hwdb.d/20-acpi-vendor.hwdb @@ -384,6 +384,9 @@ acpi:SHRP*: acpi:SILC*: ID_VENDOR_FROM_DATABASE=Silicom Ltd. Connectivity Solutions +acpi:SIPL*: + ID_VENDOR_FROM_DATABASE=SIPEARL + acpi:SNSL*: ID_VENDOR_FROM_DATABASE=Sensel, Inc. diff --git a/hwdb.d/20-acpi-vendor.hwdb.patch b/hwdb.d/20-acpi-vendor.hwdb.patch index 8c6427858ffc2..88312fa5d4817 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-17 17:31:25.705001902 +0000 -+++ 20-acpi-vendor.hwdb 2026-03-17 17:31:25.713002098 +0000 +--- 20-acpi-vendor.hwdb.base 2026-05-18 12:14:07.881439644 +0100 ++++ 20-acpi-vendor.hwdb 2026-05-18 12:14:07.885439708 +0100 @@ -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 -@@ -460,6 +462,9 @@ +@@ -463,6 +465,9 @@ acpi:AAA*: ID_VENDOR_FROM_DATABASE=Avolites Ltd @@ -19,7 +19,7 @@ acpi:AAE*: ID_VENDOR_FROM_DATABASE=Anatek Electronics Inc. -@@ -487,6 +492,9 @@ +@@ -490,6 +495,9 @@ acpi:ABO*: ID_VENDOR_FROM_DATABASE=D-Link Systems Inc @@ -29,7 +29,7 @@ acpi:ABS*: ID_VENDOR_FROM_DATABASE=Abaco Systems, Inc. -@@ -532,7 +540,7 @@ +@@ -535,7 +543,7 @@ acpi:ACO*: ID_VENDOR_FROM_DATABASE=Allion Computer Inc. @@ -38,7 +38,7 @@ ID_VENDOR_FROM_DATABASE=Aspen Tech Inc acpi:ACR*: -@@ -811,6 +819,9 @@ +@@ -814,6 +822,9 @@ acpi:AMT*: ID_VENDOR_FROM_DATABASE=AMT International Industry @@ -48,7 +48,7 @@ acpi:AMX*: ID_VENDOR_FROM_DATABASE=AMX LLC -@@ -859,6 +870,9 @@ +@@ -862,6 +873,9 @@ acpi:AOA*: ID_VENDOR_FROM_DATABASE=AOpen Inc. @@ -58,7 +58,7 @@ acpi:AOE*: ID_VENDOR_FROM_DATABASE=Advanced Optics Electronics, Inc. -@@ -868,6 +882,9 @@ +@@ -871,6 +885,9 @@ acpi:AOT*: ID_VENDOR_FROM_DATABASE=Alcatel @@ -68,7 +68,7 @@ acpi:APC*: ID_VENDOR_FROM_DATABASE=American Power Conversion -@@ -1049,7 +1066,7 @@ +@@ -1052,7 +1069,7 @@ ID_VENDOR_FROM_DATABASE=ALPS ALPINE CO., LTD. acpi:AUO*: @@ -77,7 +77,7 @@ acpi:AUR*: ID_VENDOR_FROM_DATABASE=Aureal Semiconductor -@@ -1129,6 +1146,9 @@ +@@ -1132,6 +1149,9 @@ acpi:AXE*: ID_VENDOR_FROM_DATABASE=Axell Corporation @@ -87,7 +87,7 @@ acpi:AXI*: ID_VENDOR_FROM_DATABASE=American Magnetics -@@ -1288,6 +1308,9 @@ +@@ -1291,6 +1311,9 @@ acpi:BML*: ID_VENDOR_FROM_DATABASE=BIOMED Lab @@ -97,7 +97,7 @@ acpi:BMS*: ID_VENDOR_FROM_DATABASE=BIOMEDISYS -@@ -1300,6 +1323,9 @@ +@@ -1303,6 +1326,9 @@ acpi:BNO*: ID_VENDOR_FROM_DATABASE=Bang & Olufsen @@ -107,7 +107,7 @@ acpi:BNS*: ID_VENDOR_FROM_DATABASE=Boulder Nonlinear Systems -@@ -1546,6 +1572,9 @@ +@@ -1549,6 +1575,9 @@ acpi:CHA*: ID_VENDOR_FROM_DATABASE=Chase Research PLC @@ -117,7 +117,7 @@ acpi:CHD*: ID_VENDOR_FROM_DATABASE=ChangHong Electric Co.,Ltd -@@ -1711,6 +1740,9 @@ +@@ -1714,6 +1743,9 @@ acpi:COD*: ID_VENDOR_FROM_DATABASE=CODAN Pty. Ltd. @@ -127,7 +127,7 @@ acpi:COI*: ID_VENDOR_FROM_DATABASE=Codec Inc. -@@ -2129,7 +2161,7 @@ +@@ -2132,7 +2164,7 @@ ID_VENDOR_FROM_DATABASE=Dragon Information Technology acpi:DJE*: @@ -136,7 +136,7 @@ acpi:DJP*: ID_VENDOR_FROM_DATABASE=Maygay Machines, Ltd -@@ -2482,6 +2514,9 @@ +@@ -2485,6 +2517,9 @@ acpi:EIN*: ID_VENDOR_FROM_DATABASE=Elegant Invention @@ -146,7 +146,7 @@ acpi:EKA*: ID_VENDOR_FROM_DATABASE=MagTek Inc. -@@ -2752,6 +2787,9 @@ +@@ -2755,6 +2790,9 @@ acpi:FCG*: ID_VENDOR_FROM_DATABASE=First International Computer Ltd @@ -156,7 +156,7 @@ acpi:FCS*: ID_VENDOR_FROM_DATABASE=Focus Enhancements, Inc. -@@ -3128,7 +3166,7 @@ +@@ -3131,7 +3169,7 @@ ID_VENDOR_FROM_DATABASE=General Standards Corporation acpi:GSM*: @@ -165,7 +165,7 @@ acpi:GSN*: ID_VENDOR_FROM_DATABASE=Grandstream Networks, Inc. -@@ -3238,6 +3276,9 @@ +@@ -3241,6 +3279,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 -@@ -3373,6 +3414,9 @@ +@@ -3376,6 +3417,9 @@ acpi:HSD*: ID_VENDOR_FROM_DATABASE=HannStar Display Corp @@ -185,7 +185,7 @@ acpi:HSM*: ID_VENDOR_FROM_DATABASE=AT&T Microelectronics -@@ -3499,6 +3543,9 @@ +@@ -3502,6 +3546,9 @@ acpi:ICI*: ID_VENDOR_FROM_DATABASE=Infotek Communication Inc @@ -195,7 +195,7 @@ acpi:ICM*: ID_VENDOR_FROM_DATABASE=Intracom SA -@@ -3595,6 +3642,9 @@ +@@ -3598,6 +3645,9 @@ acpi:IKE*: ID_VENDOR_FROM_DATABASE=Ikegami Tsushinki Co. Ltd. @@ -205,7 +205,7 @@ acpi:IKS*: ID_VENDOR_FROM_DATABASE=Ikos Systems Inc -@@ -3643,6 +3693,9 @@ +@@ -3646,6 +3696,9 @@ acpi:IMX*: ID_VENDOR_FROM_DATABASE=arpara Technology Co., Ltd. @@ -215,7 +215,7 @@ acpi:INA*: ID_VENDOR_FROM_DATABASE=Inventec Corporation -@@ -4171,6 +4224,9 @@ +@@ -4174,6 +4227,9 @@ acpi:LAN*: ID_VENDOR_FROM_DATABASE=Sodeman Lancom Inc @@ -225,7 +225,7 @@ acpi:LAS*: ID_VENDOR_FROM_DATABASE=LASAT Comm. A/S -@@ -4222,6 +4278,9 @@ +@@ -4225,6 +4281,9 @@ acpi:LED*: ID_VENDOR_FROM_DATABASE=Long Engineering Design Inc @@ -235,7 +235,7 @@ acpi:LEG*: ID_VENDOR_FROM_DATABASE=Legerity, Inc -@@ -4240,6 +4299,9 @@ +@@ -4243,6 +4302,9 @@ acpi:LGD*: ID_VENDOR_FROM_DATABASE=LG Display @@ -245,7 +245,7 @@ acpi:LGI*: ID_VENDOR_FROM_DATABASE=Logitech Inc -@@ -4306,6 +4368,9 @@ +@@ -4309,6 +4371,9 @@ acpi:LND*: ID_VENDOR_FROM_DATABASE=Land Computer Company Ltd @@ -255,7 +255,7 @@ acpi:LNK*: ID_VENDOR_FROM_DATABASE=Link Tech Inc -@@ -4340,7 +4405,7 @@ +@@ -4343,7 +4408,7 @@ ID_VENDOR_FROM_DATABASE=Design Technology acpi:LPL*: @@ -264,7 +264,7 @@ acpi:LSC*: ID_VENDOR_FROM_DATABASE=LifeSize Communications -@@ -4516,6 +4581,9 @@ +@@ -4519,6 +4584,9 @@ acpi:MCX*: ID_VENDOR_FROM_DATABASE=Millson Custom Solutions Inc. @@ -274,7 +274,7 @@ acpi:MDA*: ID_VENDOR_FROM_DATABASE=Media4 Inc -@@ -4762,6 +4830,9 @@ +@@ -4765,6 +4833,9 @@ acpi:MOM*: ID_VENDOR_FROM_DATABASE=Momentum Data Systems @@ -284,7 +284,7 @@ acpi:MOS*: ID_VENDOR_FROM_DATABASE=Moses Corporation -@@ -5002,6 +5073,9 @@ +@@ -5005,6 +5076,9 @@ acpi:NAL*: ID_VENDOR_FROM_DATABASE=Network Alchemy @@ -294,7 +294,7 @@ acpi:NAT*: ID_VENDOR_FROM_DATABASE=NaturalPoint Inc. -@@ -5542,6 +5616,9 @@ +@@ -5545,6 +5619,9 @@ acpi:PCX*: ID_VENDOR_FROM_DATABASE=PC Xperten @@ -304,7 +304,7 @@ acpi:PDM*: ID_VENDOR_FROM_DATABASE=Psion Dacom Plc. -@@ -5605,9 +5682,6 @@ +@@ -5608,9 +5685,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 -@@ -5698,9 +5772,6 @@ +@@ -5701,9 +5775,6 @@ acpi:PNL*: ID_VENDOR_FROM_DATABASE=Panelview, Inc. @@ -324,7 +324,7 @@ acpi:PNR*: ID_VENDOR_FROM_DATABASE=Planar Systems, Inc. -@@ -6178,9 +6249,6 @@ +@@ -6181,9 +6252,6 @@ acpi:RTI*: ID_VENDOR_FROM_DATABASE=Rancho Tech Inc @@ -334,7 +334,7 @@ acpi:RTL*: ID_VENDOR_FROM_DATABASE=Realtek Semiconductor Company Ltd -@@ -6355,9 +6423,6 @@ +@@ -6358,9 +6426,6 @@ acpi:SEE*: ID_VENDOR_FROM_DATABASE=SeeColor Corporation @@ -344,7 +344,7 @@ acpi:SEI*: ID_VENDOR_FROM_DATABASE=Seitz & Associates Inc -@@ -6841,6 +6906,9 @@ +@@ -6844,6 +6909,9 @@ acpi:SVD*: ID_VENDOR_FROM_DATABASE=SVD Computer @@ -354,7 +354,7 @@ acpi:SVI*: ID_VENDOR_FROM_DATABASE=Sun Microsystems -@@ -6925,6 +6993,9 @@ +@@ -6928,6 +6996,9 @@ acpi:SZM*: ID_VENDOR_FROM_DATABASE=Shenzhen MTC Co., Ltd @@ -364,7 +364,7 @@ acpi:TAA*: ID_VENDOR_FROM_DATABASE=Tandberg -@@ -7015,6 +7086,9 @@ +@@ -7018,6 +7089,9 @@ acpi:TDG*: ID_VENDOR_FROM_DATABASE=Six15 Technologies @@ -374,7 +374,7 @@ acpi:TDM*: ID_VENDOR_FROM_DATABASE=Tandem Computer Europe Inc -@@ -7057,6 +7131,9 @@ +@@ -7060,6 +7134,9 @@ acpi:TEV*: ID_VENDOR_FROM_DATABASE=Televés, S.A. @@ -384,7 +384,7 @@ acpi:TEZ*: ID_VENDOR_FROM_DATABASE=Tech Source Inc. -@@ -7186,9 +7263,6 @@ +@@ -7189,9 +7266,6 @@ acpi:TNC*: ID_VENDOR_FROM_DATABASE=TNC Industrial Company Ltd @@ -394,7 +394,7 @@ acpi:TNM*: ID_VENDOR_FROM_DATABASE=TECNIMAGEN SA -@@ -7501,14 +7575,14 @@ +@@ -7504,14 +7578,14 @@ acpi:UNC*: ID_VENDOR_FROM_DATABASE=Unisys Corporation @@ -415,7 +415,7 @@ acpi:UNI*: ID_VENDOR_FROM_DATABASE=Uniform Industry Corp. -@@ -7543,6 +7617,9 @@ +@@ -7546,6 +7620,9 @@ acpi:USA*: ID_VENDOR_FROM_DATABASE=Utimaco Safeware AG @@ -425,7 +425,7 @@ acpi:USD*: ID_VENDOR_FROM_DATABASE=U.S. Digital Corporation -@@ -7804,9 +7881,6 @@ +@@ -7807,9 +7884,6 @@ acpi:WAL*: ID_VENDOR_FROM_DATABASE=Wave Access @@ -435,7 +435,7 @@ acpi:WAV*: ID_VENDOR_FROM_DATABASE=Wavephore -@@ -7934,7 +8008,7 @@ +@@ -7937,7 +8011,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. -@@ -7948,9 +8022,6 @@ +@@ -7951,9 +8025,6 @@ acpi:XDM*: ID_VENDOR_FROM_DATABASE=XDM Ltd. @@ -454,7 +454,7 @@ acpi:XES*: ID_VENDOR_FROM_DATABASE=Extreme Engineering Solutions, Inc. -@@ -7984,9 +8055,6 @@ +@@ -7987,9 +8058,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 -@@ -8053,6 +8121,9 @@ +@@ -8056,6 +8124,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 dc48494c5197f..fa49ae4574b29 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -14,6 +14,24 @@ pci:v00000010d00008139* pci:v00000014* ID_VENDOR_FROM_DATABASE=Loongson Technology LLC +pci:v00000014d00003B0F* + ID_MODEL_FROM_DATABASE=DMA Adress Translation Unit [Loongson 3 Processor Family] + +pci:v00000014d00003C09* + ID_MODEL_FROM_DATABASE=Internal PCI to PCI Bridge [Loongson 3 Processor Family] + +pci:v00000014d00003C0F* + ID_MODEL_FROM_DATABASE=DMA Adress Translation Unit [Loongson 3 Processor Family] + +pci:v00000014d00003C19* + ID_MODEL_FROM_DATABASE=PCI Express x16 Root Port [Loongson 3 Processor Family] + +pci:v00000014d00003C29* + ID_MODEL_FROM_DATABASE=PCI Express x8 Root Port [Loongson 3 Processor Family] + +pci:v00000014d00003C39* + ID_MODEL_FROM_DATABASE=PCI Express x4 Root Port [Loongson 3 Processor Family] + pci:v00000014d00007A00* ID_MODEL_FROM_DATABASE=7A1000 Chipset Hyper Transport Bridge Controller @@ -33,7 +51,7 @@ pci:v00000014d00007A06* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000 Chipset Display Controller pci:v00000014d00007A07* - ID_MODEL_FROM_DATABASE=2K1000/2000 / 7A1000/2000 Chipset HD Audio Controller + ID_MODEL_FROM_DATABASE=2K1000/2000/3000 / 3B6000M / 7A1000/2000 Chipset HD Audio Controller pci:v00000014d00007A08* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller @@ -72,7 +90,7 @@ pci:v00000014d00007A17* ID_MODEL_FROM_DATABASE=7A1000 Chipset AC97 Audio Controller pci:v00000014d00007A18* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset 6Gb/s SATA AHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset 6Gb/s SATA AHCI Controller pci:v00000014d00007A19* ID_MODEL_FROM_DATABASE=PCI-to-PCI Bridge @@ -81,10 +99,10 @@ pci:v00000014d00007A1A* ID_MODEL_FROM_DATABASE=2K2000 Configuration Bus pci:v00000014d00007A1B* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset SPI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset SPI Controller pci:v00000014d00007A1D* - ID_MODEL_FROM_DATABASE=2K2000 RapidIO Interface + ID_MODEL_FROM_DATABASE=2K2000 / 2K3000 / 3B6000M RapidIO Interface pci:v00000014d00007A1E* ID_MODEL_FROM_DATABASE=2K2000 DES Controller @@ -92,6 +110,9 @@ pci:v00000014d00007A1E* pci:v00000014d00007A22* ID_MODEL_FROM_DATABASE=2K2000 Advanced Peripheral Bus Controller +pci:v00000014d00007A23* + ID_MODEL_FROM_DATABASE=Gigabit Ethernet Controller [Chipset / CPU inside] + pci:v00000014d00007A24* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000/2000 Chipset USB OHCI Controller @@ -102,7 +123,7 @@ pci:v00000014d00007A26* ID_MODEL_FROM_DATABASE=2K1000 Camera Controller pci:v00000014d00007A27* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset I2S Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset I2S Controller pci:v00000014d00007A29* ID_MODEL_FROM_DATABASE=7A1000 Chipset PCIe x8 Bridge @@ -114,13 +135,16 @@ pci:v00000014d00007A2F* ID_MODEL_FROM_DATABASE=2K2000 DMA Controller pci:v00000014d00007A34* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset USB 3.0 xHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset USB 3.0 xHCI Controller + +pci:v00000014d00007A35* + ID_MODEL_FROM_DATABASE=LG200 GPU pci:v00000014d00007A36* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset Display Controller pci:v00000014d00007A37* - ID_MODEL_FROM_DATABASE=2K2000 HDMI Audio Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M HDMI Audio Controller pci:v00000014d00007A39* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset PCIe x1 Root Port @@ -128,11 +152,20 @@ pci:v00000014d00007A39* pci:v00000014d00007A3E* ID_MODEL_FROM_DATABASE=2K2000 RNG Controller +pci:v00000014d00007A42* + ID_MODEL_FROM_DATABASE=Advanced Peripheral Bus Controller [Chipset / CPU inside] + pci:v00000014d00007A44* - ID_MODEL_FROM_DATABASE=2K2000 USB 2.0 xHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M USB 2.0 xHCI Controller + +pci:v00000014d00007A46* + ID_MODEL_FROM_DATABASE=Video Display Controller [Chipset / CPU inside] + +pci:v00000014d00007A47* + ID_MODEL_FROM_DATABASE=Pulse-Code Modulation [Chipset / CPU inside] pci:v00000014d00007A48* - ID_MODEL_FROM_DATABASE=2K2000 SDIO Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M SDIO Controller pci:v00000014d00007A49* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset PCIe x4 Root Port @@ -140,9 +173,15 @@ pci:v00000014d00007A49* pci:v00000014d00007A54* ID_MODEL_FROM_DATABASE=2K2000 OTG USB Controller +pci:v00000014d00007A56* + ID_MODEL_FROM_DATABASE=Video Processing Unit Decoder [Chipset / CPU inside] + pci:v00000014d00007A59* ID_MODEL_FROM_DATABASE=7A2000 Chipset PCIe x8 Root Port +pci:v00000014d00007A66* + ID_MODEL_FROM_DATABASE=Video Processing Unit Encoder [Chipset / CPU inside] + pci:v00000014d00007A69* ID_MODEL_FROM_DATABASE=7A2000 Chipset PCIe x16 Root Port @@ -150,11 +189,17 @@ pci:v00000014d00007A79* ID_MODEL_FROM_DATABASE=2K2000 PCIe Root Complex pci:v00000014d00007A88* - ID_MODEL_FROM_DATABASE=2K2000 eMMC Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M eMMC Controller + +pci:v00000014d00007A89* + ID_MODEL_FROM_DATABASE=PCI Express x1 Root Port [Chipset / CPU inside] pci:v00000014d00007A8E* ID_MODEL_FROM_DATABASE=2K2000 SE Controller +pci:v00000014d00007A99* + ID_MODEL_FROM_DATABASE=PCI Express x4 Root Port [Chipset / CPU inside] + pci:v00000014d00007AF9* ID_MODEL_FROM_DATABASE=2K2000 PCIe Endpoint @@ -584,6 +629,9 @@ pci:v00000731d0000F111* pci:v00000731d0000FF11* ID_MODEL_FROM_DATABASE=JM1100-YV +pci:v00000771* + ID_VENDOR_FROM_DATABASE=Xi'an Microelectronics Technology Institute + pci:v00000777* ID_VENDOR_FROM_DATABASE=Ubiquiti Networks, Inc. @@ -3428,6 +3476,9 @@ pci:v00001000d0000C034sv00001000sd00002004* pci:v00001000d0000C034sv00001000sd00002005* ID_MODEL_FROM_DATABASE=PEX890xx PCIe Gen 5 Switch (PEX89000 Virtual PCIe gDMA Endpoint) +pci:v00001000d0000C040* + ID_MODEL_FROM_DATABASE=PEX90xxx PCIe Gen 6 Switch + pci:v00001001* ID_VENDOR_FROM_DATABASE=Kolter Electronic @@ -3548,6 +3599,9 @@ pci:v00001002d0000131D* pci:v00001002d000013C0* ID_MODEL_FROM_DATABASE=Granite Ridge [Radeon Graphics] +pci:v00001002d000013DB* + ID_MODEL_FROM_DATABASE=Cyan Skillfish [PlayStation 5 APU] + pci:v00001002d000013E9* ID_MODEL_FROM_DATABASE=Ariel/Navi10Lite @@ -11742,13 +11796,13 @@ pci:v00001002d0000731Fsv00001DA2sd0000E411* ID_MODEL_FROM_DATABASE=Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] (Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT]) pci:v00001002d00007340* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] pci:v00001002d00007340sv0000106Bsd00000210* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] (Radeon Pro 5300M) + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] (MacBookPro16,1 (16", 2019) [Radeon Pro 5300M]) pci:v00001002d00007340sv0000106Bsd00000219* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] (iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300]) + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] (iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300]) pci:v00001002d00007341* ID_MODEL_FROM_DATABASE=Navi 14 [Radeon Pro W5500] @@ -12095,6 +12149,9 @@ pci:v00001002d00007551* pci:v00001002d00007590* ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] +pci:v00001002d00007590sv00001043sd00000639* + ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] + pci:v00001002d00007590sv00001458sd00002429* ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] (GV-R9060XTGAMING OC-16GD [Radeon RX 9060 XT GAMING OC 16G]) @@ -12428,6 +12485,9 @@ pci:v00001002d00009501* pci:v00001002d00009501sv0000174Bsd0000E620* ID_MODEL_FROM_DATABASE=RV670 [Radeon HD 3870] (Radeon HD 3870) +pci:v00001002d00009501sv00001787sd00002244* + ID_MODEL_FROM_DATABASE=RV670 [Radeon HD 3870] (Radeon HD 3870) + pci:v00001002d00009504* ID_MODEL_FROM_DATABASE=RV670/M88 [Mobility Radeon HD 3850] @@ -22286,9 +22346,54 @@ pci:v0000105B* pci:v0000105Bd00009602* ID_MODEL_FROM_DATABASE=RS780/RS880 PCI to PCI bridge (int gfx) +pci:v0000105Bd0000E0AB* + ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0B0* + ID_MODEL_FROM_DATABASE=DW5930e 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0B1* + ID_MODEL_FROM_DATABASE=DW5930e 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0BF* + ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] + pci:v0000105Bd0000E0C3* ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] +pci:v0000105Bd0000E0D8* + ID_MODEL_FROM_DATABASE=T99W368 5G Modem [Snapdragon X65] + +pci:v0000105Bd0000E0D9* + ID_MODEL_FROM_DATABASE=T99W373 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E0F0* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F1* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F2* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F5* + ID_MODEL_FROM_DATABASE=DW5932e-eSIM 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E0F9* + ID_MODEL_FROM_DATABASE=DW5932e 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E118* + ID_MODEL_FROM_DATABASE=T99W640 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E11D* + ID_MODEL_FROM_DATABASE=DW5934e-eSIM 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E11E* + ID_MODEL_FROM_DATABASE=DW5934e 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E123* + ID_MODEL_FROM_DATABASE=T99W760 5G Redcap Modem [Snapdragon X35] + pci:v0000105C* ID_VENDOR_FROM_DATABASE=Wipro Infotech Limited @@ -30041,95 +30146,89 @@ pci:v000010DEd00000029sv000014AFsd00005820* pci:v000010DEd00000029sv00004843sd00004F34* ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Ultra] (Dynamite) -pci:v000010DEd0000002A* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2] - -pci:v000010DEd0000002B* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2] - pci:v000010DEd0000002C* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] pci:v000010DEd0000002Csv00001043sd00000200* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (AGP-V3800 Combat SDRAM) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (AGP-V3800 Combat SDRAM) pci:v000010DEd0000002Csv00001043sd00000201* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (AGP-V3800 Combat) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (AGP-V3800 Combat) pci:v000010DEd0000002Csv00001048sd00000C20* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta) pci:v000010DEd0000002Csv00001048sd00000C21* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta) pci:v000010DEd0000002Csv00001048sd00000C25* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta 16MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta 16MB) pci:v000010DEd0000002Csv00001092sd00006820* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (Viper V730) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (Viper V730) pci:v000010DEd0000002Csv00001102sd00001031* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (CT6938 VANTA 8MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (CT6938 VANTA 8MB) pci:v000010DEd0000002Csv00001102sd00001034* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (CT6894 VANTA 16MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (CT6894 VANTA 16MB) pci:v000010DEd0000002Csv000014AFsd00005008* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (Maxi Gamer Phoenix 2) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (Maxi Gamer Phoenix 2) pci:v000010DEd0000002D* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] pci:v000010DEd0000002Dsv00001043sd00000200* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) pci:v000010DEd0000002Dsv00001043sd00000201* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) pci:v000010DEd0000002Dsv00001048sd00000C3A* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) pci:v000010DEd0000002Dsv00001048sd00000C3B* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) pci:v000010DEd0000002Dsv0000107Dsd00002137* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (WinFast 3D S325) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (WinFast 3D S325) pci:v000010DEd0000002Dsv000010DEsd00000006* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (RIVA TNT2 Model 64/Model 64 Pro) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (RIVA TNT2 Model 64/Model 64 Pro) pci:v000010DEd0000002Dsv000010DEsd0000001E* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (M64 AGP4x) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (M64 AGP4x) pci:v000010DEd0000002Dsv00001102sd00001023* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6892 RIVA TNT2 Value) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6892 RIVA TNT2 Value) pci:v000010DEd0000002Dsv00001102sd00001024* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6932 RIVA TNT2 Value 32Mb) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6932 RIVA TNT2 Value 32Mb) pci:v000010DEd0000002Dsv00001102sd0000102C* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value [Jumper]) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value [Jumper]) pci:v000010DEd0000002Dsv00001102sd00001030* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value) pci:v000010DEd0000002Dsv0000110Asd0000006F* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) pci:v000010DEd0000002Dsv0000110Asd00000081* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) pci:v000010DEd0000002Dsv00001462sd00008808* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (MSI-8808) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (MSI-8808) pci:v000010DEd0000002Dsv000014AFsd00005620* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Gamer Cougar Video Edition) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Gamer Cougar Video Edition) pci:v000010DEd0000002Dsv00001554sd00001041* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Pixelview RIVA TNT2 M64) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Pixelview RIVA TNT2 M64) pci:v000010DEd0000002Dsv00001569sd0000002D* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Palit Microsystems Daytona TNT2 M64) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Palit Microsystems Daytona TNT2 M64) pci:v000010DEd00000034* ID_MODEL_FROM_DATABASE=MCP04 SMBus @@ -38972,6 +39071,9 @@ pci:v000010DEd00001F81* pci:v000010DEd00001F82* ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1650] +pci:v000010DEd00001F82sv000019DAsd00003595* + ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1650] (GeForce GTX 1650 OC GDDR6) + pci:v000010DEd00001F83* ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1630] @@ -39209,6 +39311,9 @@ pci:v000010DEd00002204* pci:v000010DEd00002204sv000010DEsd0000147D* ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (GeForce RTX 3090 Founders Edition) +pci:v000010DEd00002204sv00001462sd00003881* + ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (MSI RTX 3090 VENTUS 3X OC) + pci:v000010DEd00002204sv00003842sd00003973* ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (GeForce RTX 3090 XC3) @@ -39905,6 +40010,9 @@ pci:v000010DEd00002900* pci:v000010DEd00002901* ID_MODEL_FROM_DATABASE=GB100 [B200] +pci:v000010DEd00002909* + ID_MODEL_FROM_DATABASE=GB100 [HGX B200 168GB] + pci:v000010DEd00002920* ID_MODEL_FROM_DATABASE=GB100 [TS4 / B100] @@ -40103,6 +40211,9 @@ pci:v000010DEd000031C0* pci:v000010DEd000031C2* ID_MODEL_FROM_DATABASE=GB110 [GB300] +pci:v000010DEd000031C3* + ID_MODEL_FROM_DATABASE=GB110 [GB300] + pci:v000010DEd000031FE* ID_MODEL_FROM_DATABASE=GB110 @@ -47936,6 +48047,12 @@ pci:v0000117Cd000000BBsv0000117Csd000000CA* pci:v0000117Cd000000BBsv0000117Csd000000D4* ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (Celerity FC-644E) +pci:v0000117Cd000000BBsv0000117Csd000040D8* + ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (ThunderLink FC 5642) + +pci:v0000117Cd000000BBsv0000117Csd000040D9* + ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (ThunderLink FC 5322) + pci:v0000117Cd000000C5* ID_MODEL_FROM_DATABASE=ExpressNVM PCIe Gen4 Switch @@ -47963,6 +48080,21 @@ pci:v0000117Cd000000E6sv0000117Csd000000C3* pci:v0000117Cd000000E6sv0000117Csd000000C4* ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H120F GT) +pci:v0000117Cd000000E6sv0000117Csd000000E1* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1280 GT) + +pci:v0000117Cd000000E6sv0000117Csd000000E2* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1208 GT) + +pci:v0000117Cd000000E6sv0000117Csd000000E3* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1244 GT) + +pci:v0000117Cd000000E6sv0000117Csd000040E0* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ThunderLink SH 5128) + +pci:v0000117Cd000000E6sv0000117Csd000040E4* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ThunderLink SH 5128) + pci:v0000117Cd00008013* ID_MODEL_FROM_DATABASE=ExpressPCI UL4D @@ -47985,7 +48117,7 @@ pci:v0000117Cd00008070sv0000117Csd00000080* ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ExpressSAS H1244) pci:v0000117Cd00008070sv0000117Csd000040AE* - ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ThunderLink TLSH-3128) + ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ThunderLink SH 3128) pci:v0000117Cd00008072* ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA @@ -50534,18 +50666,87 @@ pci:v000011F7* pci:v000011F8* ID_VENDOR_FROM_DATABASE=Microchip Technology +pci:v000011F8d00004000* + ID_MODEL_FROM_DATABASE=PM40100 Switchtec PFX 100xG4 Fanout PCIe Switch + +pci:v000011F8d00004028* + ID_MODEL_FROM_DATABASE=PM40028 Switchtec PFX 28xG4 Fanout PCIe Switch + pci:v000011F8d00004036* ID_MODEL_FROM_DATABASE=PM40036 Switchtec PFX 36xG4 Fanout PCIe Switch pci:v000011F8d00004052* ID_MODEL_FROM_DATABASE=PM40052 Switchtec PFX 52xG4 Fanout PCIe Switch +pci:v000011F8d00004068* + ID_MODEL_FROM_DATABASE=PM40068 Switchtec PFX 68xG4 Fanout PCIe Switch + pci:v000011F8d00004084* ID_MODEL_FROM_DATABASE=PM40084 Switchtec PFX 84xG4 Fanout PCIe Switch +pci:v000011F8d00004100* + ID_MODEL_FROM_DATABASE=PM41100 Switchtec PSX 100xG4 Programmable PCIe Switch + pci:v000011F8d00004128* ID_MODEL_FROM_DATABASE=PM41028 Switchtec PSX 28xG4 Programmable PCIe Switch +pci:v000011F8d00004136* + ID_MODEL_FROM_DATABASE=PM41036 Switchtec PSX 36xG4 Programmable PCIe Switch + +pci:v000011F8d00004152* + ID_MODEL_FROM_DATABASE=PM41052 Switchtec PSX 52xG4 Programmable PCIe Switch + +pci:v000011F8d00004168* + ID_MODEL_FROM_DATABASE=PM41068 Switchtec PSX 68xG4 Programmable PCIe Switch + +pci:v000011F8d00004184* + ID_MODEL_FROM_DATABASE=PM41084 Switchtec PSX 84xG4 Programmable PCIe Switch + +pci:v000011F8d00004200* + ID_MODEL_FROM_DATABASE=PM42100 Switchtec PAX 100xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004228* + ID_MODEL_FROM_DATABASE=PM42028 Switchtec PAX 28xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004236* + ID_MODEL_FROM_DATABASE=PM42036 Switchtec PAX 36xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004252* + ID_MODEL_FROM_DATABASE=PM42052 Switchtec PAX 52xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004268* + ID_MODEL_FROM_DATABASE=PM42068 Switchtec PAX 68xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004284* + ID_MODEL_FROM_DATABASE=PM42084 Switchtec PAX 84xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004328* + ID_MODEL_FROM_DATABASE=PM43028 Switchtec PFXA 28xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004336* + ID_MODEL_FROM_DATABASE=PM43036 Switchtec PFXA 36xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004352* + ID_MODEL_FROM_DATABASE=PM43052 Switchtec PFXA 52xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004428* + ID_MODEL_FROM_DATABASE=PM44028 Switchtec PSXA 28xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004436* + ID_MODEL_FROM_DATABASE=PM44036 Switchtec PSXA 36xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004452* + ID_MODEL_FROM_DATABASE=PM44052 Switchtec PSXA 52xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004528* + ID_MODEL_FROM_DATABASE=PM45028 Switchtec PAXA 28xG4 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004536* + ID_MODEL_FROM_DATABASE=PM45036 Switchtec PAXA 36xG4 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004552* + ID_MODEL_FROM_DATABASE=PM45052 Switchtec PAXA 52xG4 Automotive Programmable Advanced Fabric PCIe Switch + pci:v000011F8d00005000* ID_MODEL_FROM_DATABASE=PM50100 Switchtec PFX 100xG5 Fanout PCIe Switch @@ -50582,9 +50783,129 @@ pci:v000011F8d00005168* pci:v000011F8d00005184* ID_MODEL_FROM_DATABASE=PM51084 Switchtec PSX 84xG5 Programmable PCIe Switch +pci:v000011F8d00005200* + ID_MODEL_FROM_DATABASE=PM52100 Switchtec PAX 100xG5 Programmable Advanced Fabric PCIe Switch + pci:v000011F8d00005220* ID_MODEL_FROM_DATABASE=BR522x [PMC-Sierra maxRAID SAS Controller] +pci:v000011F8d00005228* + ID_MODEL_FROM_DATABASE=PM52028 Switchtec PAX 28xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005236* + ID_MODEL_FROM_DATABASE=PM52036 Switchtec PAX 36xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005252* + ID_MODEL_FROM_DATABASE=PM52052 Switchtec PAX 52xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005268* + ID_MODEL_FROM_DATABASE=PM52068 Switchtec PAX 68xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005284* + ID_MODEL_FROM_DATABASE=PM52084 Switchtec PAX 84xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005300* + ID_MODEL_FROM_DATABASE=PM53100 Switchtec PFXA 100xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005328* + ID_MODEL_FROM_DATABASE=PM53028 Switchtec PFXA 28xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005336* + ID_MODEL_FROM_DATABASE=PM53036 Switchtec PFXA 36xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005352* + ID_MODEL_FROM_DATABASE=PM53052 Switchtec PFXA 52xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005368* + ID_MODEL_FROM_DATABASE=PM53068 Switchtec PFXA 68xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005384* + ID_MODEL_FROM_DATABASE=PM53084 Switchtec PFXA 84xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005400* + ID_MODEL_FROM_DATABASE=PM54100 Switchtec PSXA 100xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005428* + ID_MODEL_FROM_DATABASE=PM54028 Switchtec PSXA 28xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005436* + ID_MODEL_FROM_DATABASE=PM54036 Switchtec PSXA 36xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005452* + ID_MODEL_FROM_DATABASE=PM54052 Switchtec PSXA 52xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005468* + ID_MODEL_FROM_DATABASE=PM54068 Switchtec PSXA 68xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005484* + ID_MODEL_FROM_DATABASE=PM54084 Switchtec PSXA 84xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005500* + ID_MODEL_FROM_DATABASE=PM55100 Switchtec PAXA 100xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005528* + ID_MODEL_FROM_DATABASE=PM55028 Switchtec PAXA 28xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005536* + ID_MODEL_FROM_DATABASE=PM55036 Switchtec PAXA 36xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005552* + ID_MODEL_FROM_DATABASE=PM55052 Switchtec PAXA 52xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005568* + ID_MODEL_FROM_DATABASE=PM55068 Switchtec PAXA 68xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005584* + ID_MODEL_FROM_DATABASE=PM55084 Switchtec PAXA 84xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00006044* + ID_MODEL_FROM_DATABASE=PM60144 Switchtec PFXs 144xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006048* + ID_MODEL_FROM_DATABASE=PM60048 Switchtec PFXs 48xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006060* + ID_MODEL_FROM_DATABASE=PM60160 Switchtec PFXs 160xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006064* + ID_MODEL_FROM_DATABASE=PM60064 Switchtec PFXs 64xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006144* + ID_MODEL_FROM_DATABASE=PM61144 Switchtec PSXs 144xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006148* + ID_MODEL_FROM_DATABASE=PM61048 Switchtec PSXs 48xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006160* + ID_MODEL_FROM_DATABASE=PM61160 Switchtec PSXs 160xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006164* + ID_MODEL_FROM_DATABASE=PM61064 Switchtec PSXs 64xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006244* + ID_MODEL_FROM_DATABASE=PM62144 Switchtec PFX 144xG6 Fanout PCIe Switch + +pci:v000011F8d00006248* + ID_MODEL_FROM_DATABASE=PM62048 Switchtec PFX 48xG6 Fanout PCIe Switch + +pci:v000011F8d00006260* + ID_MODEL_FROM_DATABASE=PM62160 Switchtec PFX 160xG6 Fanout PCIe Switch + +pci:v000011F8d00006264* + ID_MODEL_FROM_DATABASE=PM62064 Switchtec PFX 64xG6 Fanout PCIe Switch + +pci:v000011F8d00006344* + ID_MODEL_FROM_DATABASE=PM63144 Switchtec PSX 144xG6 Programmable PCIe Switch + +pci:v000011F8d00006348* + ID_MODEL_FROM_DATABASE=PM63048 Switchtec PSX 48xG6 Programmable PCIe Switch + +pci:v000011F8d00006360* + ID_MODEL_FROM_DATABASE=PM63160 Switchtec PSX 160xG6 Programmable PCIe Switch + +pci:v000011F8d00006364* + ID_MODEL_FROM_DATABASE=PM63064 Switchtec PSX 64xG6 Programmable PCIe Switch + pci:v000011F8d00007364* ID_MODEL_FROM_DATABASE=PM7364 [FREEDM - 32 Frame Engine & Datalink Mgr] @@ -50669,12 +50990,78 @@ pci:v000011F8d00008536* pci:v000011F8d00008536sv00001BD4sd00000081* ID_MODEL_FROM_DATABASE=PM8536 PFX 96xG3 PCIe Fanout Switch +pci:v000011F8d00008541* + ID_MODEL_FROM_DATABASE=PM8541 PSX 24xG3 Programmable PCIe Switch + +pci:v000011F8d00008542* + ID_MODEL_FROM_DATABASE=PM8542 PSX 32xG3 Programmable PCIe Switch + +pci:v000011F8d00008543* + ID_MODEL_FROM_DATABASE=PM8543 PSX 48xG3 Programmable PCIe Switch + +pci:v000011F8d00008544* + ID_MODEL_FROM_DATABASE=PM8544 PSX 64xG3 Programmable PCIe Switch + +pci:v000011F8d00008545* + ID_MODEL_FROM_DATABASE=PM8545 PSX 80xG3 Programmable PCIe Switch + pci:v000011F8d00008546* ID_MODEL_FROM_DATABASE=PM8546 B-FEIP PSX 96xG3 PCIe Storage Switch +pci:v000011F8d00008551* + ID_MODEL_FROM_DATABASE=PM8551 PAX 24xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008552* + ID_MODEL_FROM_DATABASE=PM8552 PAX 32xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008553* + ID_MODEL_FROM_DATABASE=PM8553 PAX 48xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008554* + ID_MODEL_FROM_DATABASE=PM8554 PAX 64xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008555* + ID_MODEL_FROM_DATABASE=PM8555 PAX 80xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008556* + ID_MODEL_FROM_DATABASE=PM8556 PAX 96xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008561* + ID_MODEL_FROM_DATABASE=PM8561 Switchtec PFX-L 24xG3 Fanout-Lite PCIe Switch + pci:v000011F8d00008562* ID_MODEL_FROM_DATABASE=PM8562 Switchtec PFX-L 32xG3 Fanout-Lite PCIe Gen3 Switch +pci:v000011F8d00008563* + ID_MODEL_FROM_DATABASE=PM8563 Switchtec PFX-L 48xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008564* + ID_MODEL_FROM_DATABASE=PM8564 Switchtec PFX-L 64xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008565* + ID_MODEL_FROM_DATABASE=PM8565 Switchtec PFX-L 80xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008566* + ID_MODEL_FROM_DATABASE=PM8566 Switchtec PFX-L 96xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008571* + ID_MODEL_FROM_DATABASE=PM8571 Switchtec PFX-I 24xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008572* + ID_MODEL_FROM_DATABASE=PM8572 Switchtec PFX-I 32xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008573* + ID_MODEL_FROM_DATABASE=PM8573 Switchtec PFX-I 48xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008574* + ID_MODEL_FROM_DATABASE=PM8574 Switchtec PFX-I 64xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008575* + ID_MODEL_FROM_DATABASE=PM8575 Switchtec PFX-I 80xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008576* + ID_MODEL_FROM_DATABASE=PM8576 Switchtec PFX-I 96xG3 Industrial Fanout PCIe Switch + pci:v000011F9* ID_VENDOR_FROM_DATABASE=I-Cube Inc @@ -61233,7 +61620,7 @@ pci:v0000144Dd0000A900sv00001028sd00002343* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 3.84TB) pci:v0000144Dd0000A900sv00001028sd00002344* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 7.68GTB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002345* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 15.36TB) @@ -61254,28 +61641,28 @@ pci:v0000144Dd0000A900sv00001028sd0000234A* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI U.2 15.36TB) pci:v0000144Dd0000A900sv00001028sd0000234D* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 1.92TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 1.92TB) pci:v0000144Dd0000A900sv00001028sd0000234E* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 3.84TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 3.84TB) pci:v0000144Dd0000A900sv00001028sd0000234F* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 7.68GTB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002350* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 15.36TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 15.36TB) pci:v0000144Dd0000A900sv00001028sd00002351* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 1.92TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 1.92TB) pci:v0000144Dd0000A900sv00001028sd00002352* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 3.84TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 3.84TB) pci:v0000144Dd0000A900sv00001028sd00002353* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 7.68TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002354* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 15.36TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 15.36TB) pci:v0000144Dd0000A900sv00001028sd00002355* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU U.2 800GB) @@ -61290,13 +61677,13 @@ pci:v0000144Dd0000A900sv00001028sd00002358* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU U.2 6.4TB) pci:v0000144Dd0000A900sv00001028sd00002359* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 1.6TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 1.6TB) pci:v0000144Dd0000A900sv00001028sd0000235A* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 3.2TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 3.2TB) pci:v0000144Dd0000A900sv00001028sd0000235B* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 6.4TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 6.4TB) pci:v0000144Dd0000AA00* ID_MODEL_FROM_DATABASE=NVMe SSD Controller BM1743 @@ -61331,6 +61718,81 @@ pci:v0000144Dd0000AA00sv00001028sd00002367* pci:v0000144Dd0000AC00* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x +pci:v0000144Dd0000AC00sv00001028sd00002383* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T9HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002384* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T9HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002385* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T8HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002386* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T8HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002387* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L97T6HFLTAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002388* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L97T6HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002389* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L915THBLCAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238A* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L915THBLCAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238B* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L930THBLFAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238C* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L930THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238D* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T6HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238E* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T6HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238F* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T2HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002390* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T2HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002391* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L96T4HFLTAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002392* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L96T4HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002394* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL91T9HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002396* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL93T8HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002398* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL97T6HFLAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239A* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL915THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239B* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL930THBLFAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000239C* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL930THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239E* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL91T6HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239F* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL93T2HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd000023A0* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL96T4HFLAAD3) + pci:v0000144Dd0000ECEC* ID_MODEL_FROM_DATABASE=Exynos 8895 PCIe Root Complex @@ -61970,6 +62432,9 @@ pci:v000014C3d00007992* pci:v000014C3d0000799A* ID_MODEL_FROM_DATABASE=MT7992 secondary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] +pci:v000014C3d00008188* + ID_MODEL_FROM_DATABASE=MT8188 [Kompanio 838] Root Complex + pci:v000014C3d00008650* ID_MODEL_FROM_DATABASE=MT7650 Bluetooth @@ -63548,6 +64013,9 @@ pci:v000014E4d000016D7* pci:v000014E4d000016D7sv0000117Csd000000CC* ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (FastFrame N422 Dual-port 25Gb Ethernet Adapter) +pci:v000014E4d000016D7sv0000117Csd000040D7* + ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (ThunderLink NS 5252 Dual-port 25Gb Ethernet Adapter) + pci:v000014E4d000016D7sv000014E4sd00001402* ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (BCM957414A4142CC 10Gb/25Gb Ethernet PCIe) @@ -63753,7 +64221,7 @@ pci:v000014E4d00001750sv0000117Csd000000CF* ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (FastFrame N412 Dual-port 100Gb Ethernet Adapter) pci:v000014E4d00001750sv0000117Csd000040D6* - ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (ThunderLink TLNS-5102 Dual-port 100Gb Ethernet Adapter) + ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (ThunderLink NS 5102 Dual-port 100Gb Ethernet Adapter) pci:v000014E4d00001750sv000014E4sd00002100* ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (NetXtreme-E Dual-port 100G QSFP56 Ethernet PCIe4.0 x16 Adapter (BCM957508-P2100G)) @@ -63785,6 +64253,9 @@ pci:v000014E4d00001751sv00001028sd00000B1B* pci:v000014E4d00001751sv0000117Csd000000DA* ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (FastFrame N424 Quad-port 25Gb Ethernet Adapter) +pci:v000014E4d00001751sv0000117Csd000040DF* + ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (ThunderLink NS 5254 Quad-port 25Gb Ethernet Adapter) + pci:v000014E4d00001751sv000014E4sd00004250* ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (NetXtreme-E Quad-port 25G SFP28 Ethernet PCIe4.0 x16 Adapter (BCM957504-P425G)) @@ -63815,6 +64286,9 @@ pci:v000014E4d00001752* pci:v000014E4d00001760* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet +pci:v000014E4d00001760sv0000117Csd000000CF* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (FastFrame N522 Dual-port 200Gb Ethernet Adapter) + pci:v000014E4d00001760sv000014E4sd00009110* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (BCM57608 1x400G PCIe Ethernet NIC) @@ -64487,6 +64961,12 @@ pci:v000014E4d000043BB* pci:v000014E4d000043BC* ID_MODEL_FROM_DATABASE=BCM43602 802.11ac Wireless LAN SoC +pci:v000014E4d000043C3* + ID_MODEL_FROM_DATABASE=BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter + +pci:v000014E4d000043C3sv00001043sd000086FB* + ID_MODEL_FROM_DATABASE=BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter (PCE-AC88) + pci:v000014E4d000043D3* ID_MODEL_FROM_DATABASE=BCM43567 802.11ac Wireless Network Adapter @@ -67094,6 +67574,9 @@ pci:v000015B3d0000027D* pci:v000015B3d0000027E* ID_MODEL_FROM_DATABASE=Spectrum-7 Tile +pci:v000015B3d0000027F* + ID_MODEL_FROM_DATABASE=Spectrum-8 tile + pci:v000015B3d00000281* ID_MODEL_FROM_DATABASE=NPS-600 Flash Recovery @@ -67116,7 +67599,7 @@ pci:v000015B3d00000287* ID_MODEL_FROM_DATABASE=LibraE RMA pci:v000015B3d00000288* - ID_MODEL_FROM_DATABASE=Arcus2 + ID_MODEL_FROM_DATABASE=Arcus2 Flash Recovery pci:v000015B3d00000289* ID_MODEL_FROM_DATABASE=Arcus2 RMA @@ -67131,19 +67614,19 @@ pci:v000015B3d00000293* ID_MODEL_FROM_DATABASE=Arcus3 RMA pci:v000015B3d00000294* - ID_MODEL_FROM_DATABASE=Ophy 2.1 (SagittaZ) + ID_MODEL_FROM_DATABASE=OPHY2.1 [SagittaZ] pci:v000015B3d00000296* - ID_MODEL_FROM_DATABASE=OPHY2.6 + ID_MODEL_FROM_DATABASE=OPHY2.6 [Sagitta] pci:v000015B3d00000298* - ID_MODEL_FROM_DATABASE=OPHY3.0 + ID_MODEL_FROM_DATABASE=OPHY3.0 [Sagitta] pci:v000015B3d0000029A* - ID_MODEL_FROM_DATABASE=OPHY3.1 + ID_MODEL_FROM_DATABASE=OPHY3.1 [Sagitta] pci:v000015B3d0000029C* - ID_MODEL_FROM_DATABASE=OPHY3.5 + ID_MODEL_FROM_DATABASE=OPHY3.5 [Sagitta] pci:v000015B3d000002A0* ID_MODEL_FROM_DATABASE=NVLink-8 Switch in Flash Recovery Mode @@ -67163,6 +67646,12 @@ pci:v000015B3d000002A6* pci:v000015B3d000002A7* ID_MODEL_FROM_DATABASE=OrionR RMA +pci:v000015B3d000002A8* + ID_MODEL_FROM_DATABASE=Spectrum-8 in Flash Recovery Mode + +pci:v000015B3d000002A9* + ID_MODEL_FROM_DATABASE=Spectrum-8 RMA + pci:v000015B3d00001002* ID_MODEL_FROM_DATABASE=MT25400 Family [ConnectX-2 Virtual Function] @@ -67799,6 +68288,9 @@ pci:v000015B3d0000C738* pci:v000015B3d0000C739* ID_MODEL_FROM_DATABASE=MT51136 GW +pci:v000015B3d0000C788* + ID_MODEL_FROM_DATABASE=Spectrum-8 + pci:v000015B3d0000C838* ID_MODEL_FROM_DATABASE=MT52236 @@ -71409,7 +71901,40 @@ pci:v000017CBd00000306* ID_MODEL_FROM_DATABASE=SDX55 [Snapdragon X55 5G] pci:v000017CBd00000308* - ID_MODEL_FROM_DATABASE=SDX62 [Snapdragon X62 5G] + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] + +pci:v000017CBd00000308sv0000105Bsd0000E142* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E143* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E144* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E145* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E146* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E150* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E151* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E152* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E153* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E154* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E155* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) pci:v000017CBd00000400* ID_MODEL_FROM_DATABASE=Datacenter Technologies QDF2432 PCI Express Root Port @@ -74708,6 +75233,12 @@ pci:v000019E5d00000222sv000019E5sd00000052* pci:v000019E5d00000222sv000019E5sd000000A1* ID_MODEL_FROM_DATABASE=Hi1822 Family (Hi1822 SP670 (2*100GE)) +pci:v000019E5d00000229* + ID_MODEL_FROM_DATABASE=Hi1872 Family + +pci:v000019E5d0000022A* + ID_MODEL_FROM_DATABASE=Hi1872 Family Virtual Function + pci:v000019E5d00001710* ID_MODEL_FROM_DATABASE=iBMA Virtual Network Adapter @@ -75479,9 +76010,18 @@ pci:v00001AA9d00000017* pci:v00001AA9d00000018* ID_MODEL_FROM_DATABASE=SEL-3390E4 Ethernet Adapter +pci:v00001AA9d00000019* + ID_MODEL_FROM_DATABASE=SEL-2241-2/SEL-3361 Mainboard + pci:v00001AA9d0000001C* ID_MODEL_FROM_DATABASE=SEL-3390E4 Ethernet Adapter +pci:v00001AA9d0000001D* + ID_MODEL_FROM_DATABASE=SEL-3350 GPIO Expansion Board + +pci:v00001AA9d0000001E* + ID_MODEL_FROM_DATABASE=SEL-3350 Serial Expansion Board + pci:v00001AAB* ID_VENDOR_FROM_DATABASE=Silver Creations AG @@ -77124,7 +77664,7 @@ pci:v00001BB1d00005026* ID_MODEL_FROM_DATABASE=FireCuda 540 SSD pci:v00001BB1d00005027* - ID_MODEL_FROM_DATABASE=LaCie Rugged SSD Pro5 + ID_MODEL_FROM_DATABASE=BarraCuda 530 SSD / LaCie Rugged SSD Pro5 pci:v00001BB1d00005100* ID_MODEL_FROM_DATABASE=PCIe Gen3 SSD @@ -77204,6 +77744,9 @@ pci:v00001BC0d00005216* pci:v00001BC0d00005236* ID_MODEL_FROM_DATABASE=PCIe 4TG2-P Controller +pci:v00001BC0d0000523A* + ID_MODEL_FROM_DATABASE=PCIe 4TS2-P Controller + pci:v00001BCD* ID_VENDOR_FROM_DATABASE=Apacer Technology @@ -78146,12 +78689,48 @@ pci:v00001C5Fd00000027sv00001C5Fsd00001431* pci:v00001C5Fd00000027sv00001C5Fsd00001441* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 7680G 2.5" U.2) +pci:v00001C5Fd00000027sv00001C5Fsd00001631* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 3840G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001641* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 7680G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001651* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 15360G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001661* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 30720G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001751* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 15360G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001761* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 30720G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001771* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 61440G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001781* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 122880G 2.5" U.2) + pci:v00001C5Fd00000027sv00001C5Fsd00005431* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 3200G 2.5" U.2) pci:v00001C5Fd00000027sv00001C5Fsd00005441* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 6400G 2.5" U.2) +pci:v00001C5Fd00000027sv00001C5Fsd00005631* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 3200G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005641* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 6400G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005651* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 12800G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005661* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 25600G 2.5" U.2) + pci:v00001C5Fd0000003D* ID_MODEL_FROM_DATABASE=PBlaze5 920/926 @@ -79283,6 +79862,9 @@ pci:v00001D0Fd0000EFA2* pci:v00001D0Fd0000EFA3* ID_MODEL_FROM_DATABASE=Elastic Fabric Adapter (EFA) +pci:v00001D0Fd0000EFA4* + ID_MODEL_FROM_DATABASE=Elastic Fabric Adapter (EFA) + pci:v00001D17* ID_VENDOR_FROM_DATABASE=Zhaoxin @@ -82340,6 +82922,15 @@ pci:v00001E0Fd00000034* pci:v00001E0Fd00000035* ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD +pci:v00001E0Fd0000003D* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD + +pci:v00001E0Fd0000003Dsv00001028sd0000244F* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD (RLC9GZV245T) + +pci:v00001E0Fd0000003Dsv00001028sd00002450* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD (RLC9CZV245T) + pci:v00001E0Fd0000003E* ID_MODEL_FROM_DATABASE=LC9 E3.S NVMe SSD @@ -83606,6 +84197,24 @@ pci:v00001E95d0000100Dsv00001E95sd00000002* pci:v00001E95d0000100Dsv00001E95sd00000003* ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (M.2 22110 3840 GB) +pci:v00001E95d0000100Dsv00001E95sd00000004* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 1920 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000005* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 3840 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000006* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 7680 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000007* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 1600 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000008* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 3200 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000009* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 6400 GB) + pci:v00001E95d0000100F* ID_MODEL_FROM_DATABASE=EJ5-2W3840 NVMe SSD U.2 @@ -84215,6 +84824,96 @@ pci:v00001EE4d00001180sv00001EE4sd00000626* pci:v00001EE4d00001180sv00001EE4sd00000627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8128Z3)) +pci:v00001EE4d00001180sv00001EE4sd00000715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000815* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000816* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000817* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000825* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000826* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000827* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000915* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000916* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000917* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000925* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000926* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000927* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A15* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A16* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A17* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A25* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A26* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A27* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000B15* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B16* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B17* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B25* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B26* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B27* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H3)) + pci:v00001EE4d00001180sv00001EE4sd00003013* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 480GB (P8118E)) @@ -84686,6 +85385,54 @@ pci:v00001EE4d00001182sv00001EE4sd00001626* pci:v00001EE4d00001182sv00001EE4sd00001627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8128Z3)) +pci:v00001EE4d00001182sv00001EE4sd00001714* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 960GB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.92TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.84TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 7.68TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001724* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 800GB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.6TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.2TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001814* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 960GB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001815* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.92TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001816* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.84TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001817* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 7.68TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001824* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 800GB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001825* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.6TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001826* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.2TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001827* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8118H4)) + pci:v00001EE9* ID_VENDOR_FROM_DATABASE=SUSE LLC @@ -85125,14 +85872,17 @@ pci:v00001F0Fd00003504* ID_MODEL_FROM_DATABASE=M18305 Family BASE-T pci:v00001F0Fd00003504sv00001F0Fsd00000001* - ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8, Fan) + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) pci:v00001F0Fd00003504sv00001F0Fsd00000002* - ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, BASE-T, PCIe 4.0 x4, Fan) pci:v00001F0Fd00003504sv00001F0Fsd00000003* ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8) +pci:v00001F0Fd00003504sv00001F0Fsd00000004* + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, BASE-T, PCIe 4.0 x8, Fan) + pci:v00001F0Fd0000350A* ID_MODEL_FROM_DATABASE=M18305 Family Virtual Function @@ -85517,6 +86267,39 @@ pci:v00001F47d00002018* pci:v00001F47d00002020* ID_MODEL_FROM_DATABASE=DPU +pci:v00001F47d00003011* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] + +pci:v00001F47d00003011sv00001F47sd0000000A* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000B* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 2*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000C* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 4*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000D* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 8*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000E* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000F* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 2*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd00000010* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 4*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd00000011* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 8*100GE Ethernet Adapter) + +pci:v00001F47d00003012* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R Virtual Function] + +pci:v00001F47d00003013* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R MGMT Function] + pci:v00001F47d00003101* ID_MODEL_FROM_DATABASE=FLEXFLOW-2100R Ethernet Controller @@ -85571,15 +86354,6 @@ pci:v00001F47d00003201sv00001F47sd00000007* pci:v00001F47d00003201sv00001F47sd00000008* ID_MODEL_FROM_DATABASE=FLEXFLOW-2200R Ethernet Controller (Ethernet 100G 2P FLEXFLOW-2200R) -pci:v00001F47d00003301* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] - -pci:v00001F47d00003301sv00001F47sd00000001* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) - -pci:v00001F47d00003302* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R Virtual Function] - pci:v00001F47d00003303* ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100R MGMT Function] @@ -85767,7 +86541,7 @@ pci:v00001F52* ID_VENDOR_FROM_DATABASE=MangoBoost Inc. pci:v00001F52d00001008* - ID_MODEL_FROM_DATABASE=Mango GPUBoost - RDMA + ID_MODEL_FROM_DATABASE=Mango BoostX - RoCE AI pci:v00001F52d00001020* ID_MODEL_FROM_DATABASE=Mango NetworkBoost - TCP @@ -86792,6 +87566,9 @@ pci:v00002036d00008000sv00002036sd00001006* pci:v0000203B* ID_VENDOR_FROM_DATABASE=XTX Markets Technologies Ltd. +pci:v00002042* + ID_VENDOR_FROM_DATABASE=Xi'an UniIC Semiconductors Co., Ltd + pci:v00002044* ID_VENDOR_FROM_DATABASE=Shenzhen Jiahua Zhongli Technology Co., LTD. @@ -87368,6 +88145,9 @@ pci:v000020F6d00000001* pci:v000020F9* ID_VENDOR_FROM_DATABASE=Shenzhen Silicon Dynamic Networks Co., Ltd. +pci:v00002100* + ID_VENDOR_FROM_DATABASE=Shenzhen Kimviking Semiconductor Co., Ltd. + pci:v00002105* ID_VENDOR_FROM_DATABASE=Shanghai Timar Integrated Circuit Co., LTD @@ -87383,9 +88163,24 @@ pci:v00002106d00000001sv00002106sd00000001* pci:v00002108* ID_VENDOR_FROM_DATABASE=HuiLink Technologies (Xiamen) Co., Ltd. +pci:v00002108d00002401* + ID_MODEL_FROM_DATABASE=PCIe4.0 to USB3.2 Gen2 Host Controller + +pci:v00002114* + ID_VENDOR_FROM_DATABASE=EigenQ, Inc. + +pci:v00002114d00000007* + ID_MODEL_FROM_DATABASE=QMA Board M.2 Gen2 + +pci:v00002114d0000000A* + ID_MODEL_FROM_DATABASE=QMA Board PCIe Gen2 + pci:v00002116* ID_VENDOR_FROM_DATABASE=ZyDAS Technology Corp. +pci:v00002123* + ID_VENDOR_FROM_DATABASE=Shanghai Warpdrive Technology Co., Ltd + pci:v000021B4* ID_VENDOR_FROM_DATABASE=Hunan Goke Microelectronics Co., Ltd @@ -87447,10 +88242,10 @@ pci:v00002646d0000500B* ID_MODEL_FROM_DATABASE=DC1000M NVMe SSD [SM2270] pci:v00002646d0000500C* - ID_MODEL_FROM_DATABASE=OM8PCP Design-In PCIe 3 NVMe SSD (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PCP3 PCIe 3 NVMe SSD (DRAM-less) pci:v00002646d0000500D* - ID_MODEL_FROM_DATABASE=OM3PDP3 NVMe SSD + ID_MODEL_FROM_DATABASE=OM3PDP3 PCIe 3 NVMe SSD (DRAM-less) pci:v00002646d0000500E* ID_MODEL_FROM_DATABASE=NV1 NVMe SSD [E13T] (DRAM-less) @@ -87468,10 +88263,10 @@ pci:v00002646d00005013* ID_MODEL_FROM_DATABASE=KC3000/FURY Renegade NVMe SSD [E18] pci:v00002646d00005014* - ID_MODEL_FROM_DATABASE=OM8SEP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8SEP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) pci:v00002646d00005016* - ID_MODEL_FROM_DATABASE=OM3PGP4 NVMe SSD (DRAM-less) + ID_MODEL_FROM_DATABASE=OM3PGP4 PCIe 4 NVMe SSD (DRAM-less) pci:v00002646d00005017* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [SM2267XT] (DRAM-less) @@ -87483,7 +88278,7 @@ pci:v00002646d00005019* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [E21T] (DRAM-less) pci:v00002646d0000501A* - ID_MODEL_FROM_DATABASE=OM8PGP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PGP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) pci:v00002646d0000501B* ID_MODEL_FROM_DATABASE=OM8PGP4 NVMe PCIe SSD (DRAM-less) @@ -87501,10 +88296,10 @@ pci:v00002646d0000501F* ID_MODEL_FROM_DATABASE=FURY Renegade NVMe SSD [E18] (Heatsink) pci:v00002646d00005021* - ID_MODEL_FROM_DATABASE=OM8SEP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8SEP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) pci:v00002646d00005022* - ID_MODEL_FROM_DATABASE=OM8PGP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PGP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) pci:v00002646d00005023* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [SM2269XT] (DRAM-less) @@ -87539,6 +88334,9 @@ pci:v00002646d0000502D* pci:v00002646d00005030* ID_MODEL_FROM_DATABASE=NV3 2230 NVMe SSD [SM2268XT2] (DRAM-less) +pci:v00002646d00005034* + ID_MODEL_FROM_DATABASE=NV3 NVMe SSD [E33T] (DRAM-less) + pci:v0000270B* ID_VENDOR_FROM_DATABASE=Xantel Corporation @@ -88118,9 +88916,24 @@ pci:v0000434Ed00000001sv0000434Esd00000004* pci:v0000434Ed00000002* ID_MODEL_FROM_DATABASE=CN6000 HFI Silicon, Dual Port, BGA [discrete] +pci:v0000434Ed00000002sv0000434Esd00000001* + ID_MODEL_FROM_DATABASE=CN6000 HFI Silicon, Dual Port, BGA [discrete] (CN6000 SuperNIC, Single Port, QSFP-DD, x16 PCIe Gen 6) + pci:v0000434Ed00008001* ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA +pci:v0000434Ed00008001sv0000434Esd00000101* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Switch) + +pci:v0000434Ed00008001sv0000434Esd00000103* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Director Class Switch Spine) + +pci:v0000434Ed00008001sv0000434Esd00000104* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Director Class Switch Leaf) + +pci:v0000434Ed00008001sv0000434Esd00000106* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN6000 Switch) + pci:v00004444* ID_VENDOR_FROM_DATABASE=Internext Compression Inc @@ -88805,6 +89618,24 @@ pci:v00004C54* pci:v00004C54d00005000* ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics +pci:v00004C54d00005001* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005002* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005003* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005004* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005005* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005006* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + pci:v00004CA1* ID_VENDOR_FROM_DATABASE=Seanix Technology Inc @@ -90258,10 +91089,10 @@ pci:v00008086d00000155sv00008086sd00002010* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port (Server Board S1200BTS) pci:v00008086d00000156* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT1 [HD Graphics] pci:v00008086d00000156sv00001043sd0000108D* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (VivoBook X202EV) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT1 [HD Graphics] (VivoBook X202EV) pci:v00008086d00000158* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/Ivy Bridge DRAM Controller @@ -90300,16 +91131,16 @@ pci:v00008086d00000162sv00001849sd00000162* ID_MODEL_FROM_DATABASE=IvyBridge GT2 [HD Graphics 4000] (Motherboard) pci:v00008086d00000166* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] pci:v00008086d00000166sv00001043sd00001517* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (Zenbook Prime UX31A) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (Zenbook Prime UX31A) pci:v00008086d00000166sv00001043sd00002103* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (N56VZ) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (N56VZ) pci:v00008086d00000166sv000010CFsd000016C1* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (LIFEBOOK E752) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (LIFEBOOK E752) pci:v00008086d0000016A* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/3rd Gen Core processor Graphics Controller @@ -90327,103 +91158,178 @@ pci:v00008086d00000201* ID_MODEL_FROM_DATABASE=Arctic Sound pci:v00008086d00000284* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPC Premium Controller/eSPI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller pci:v00008086d00000284sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPC Premium Controller/eSPI Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller (Latitude 7410) + +pci:v00008086d00000285* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Mainstream/Base U LPC/eSPI Controller + +pci:v00008086d000002A0* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package P2SB + +pci:v00008086d000002A1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PMC pci:v00008086d000002A3* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SMBus Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SMBus pci:v00008086d000002A3sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SMBus Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SMBus (Latitude 7410) pci:v00008086d000002A4* - ID_MODEL_FROM_DATABASE=Comet Lake SPI (flash) Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI (flash) Controller pci:v00008086d000002A4sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake SPI (flash) Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI (flash) Controller (Latitude 7410) pci:v00008086d000002A6* - ID_MODEL_FROM_DATABASE=Comet Lake North Peak + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Trace Hub + +pci:v00008086d000002A8* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #0 + +pci:v00008086d000002A9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #1 + +pci:v00008086d000002AA* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #0 + +pci:v00008086d000002AB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #1 pci:v00008086d000002B0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #9 pci:v00008086d000002B1* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #10 + +pci:v00008086d000002B2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #11 pci:v00008086d000002B3* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #12 pci:v00008086d000002B4* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #13 pci:v00008086d000002B5* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #14 + +pci:v00008086d000002B6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #15 + +pci:v00008086d000002B7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #16 pci:v00008086d000002B8* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #1 + +pci:v00008086d000002B9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #2 + +pci:v00008086d000002BA* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #3 + +pci:v00008086d000002BB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #4 pci:v00008086d000002BC* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #5 + +pci:v00008086d000002BD* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #6 + +pci:v00008086d000002BE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #7 pci:v00008086d000002BF* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #8 + +pci:v00008086d000002C4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package eMMC pci:v00008086d000002C5* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #4 pci:v00008086d000002C5sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #4 (Latitude 7410) + +pci:v00008086d000002C6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #5 + +pci:v00008086d000002C7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #2 pci:v00008086d000002C8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP cAVS + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package HD Audio pci:v00008086d000002C8sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP cAVS (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package HD Audio (Latitude 7410) pci:v00008086d000002D3* - ID_MODEL_FROM_DATABASE=Comet Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (AHCI) + +pci:v00008086d000002D5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium pci:v00008086d000002D7* - ID_MODEL_FROM_DATABASE=Comet Lake RAID Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium pci:v00008086d000002E0* - ID_MODEL_FROM_DATABASE=Comet Lake Management Engine Interface + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #1 pci:v00008086d000002E0sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Management Engine Interface (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #1 (Latitude 7410) + +pci:v00008086d000002E1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #2 + +pci:v00008086d000002E2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package IDE Redirection (IDER-R) pci:v00008086d000002E3* - ID_MODEL_FROM_DATABASE=Comet Lake AMT SOL Redirection + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Keyboard and Text (KT) Redirection + +pci:v00008086d000002E4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #3 + +pci:v00008086d000002E5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #4 pci:v00008086d000002E8* - ID_MODEL_FROM_DATABASE=Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #0 pci:v00008086d000002E8sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #0 (Latitude 7410) pci:v00008086d000002E9* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #1 pci:v00008086d000002E9sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #1 (Latitude 7410) pci:v00008086d000002EA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPSS: I2C Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #2 + +pci:v00008086d000002EB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #3 pci:v00008086d000002ED* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP USB 3.1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller pci:v00008086d000002EDsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP USB 3.1 xHCI Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller (Latitude 7410) + +pci:v00008086d000002EE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d000002EF* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP Shared SRAM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Shared SRAM pci:v00008086d000002EFsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP Shared SRAM (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Shared SRAM (Latitude 7410) pci:v00008086d000002F0* ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi @@ -90450,19 +91356,22 @@ pci:v00008086d000002F0sv00008086sd00004070* ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) pci:v00008086d000002F5* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SCS3 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SDXC pci:v00008086d000002F9* - ID_MODEL_FROM_DATABASE=Comet Lake Thermal Subsytem + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Thermal Subsystem pci:v00008086d000002F9sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Thermal Subsytem (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Thermal Subsystem (Latitude 7410) + +pci:v00008086d000002FB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #2 pci:v00008086d000002FC* - ID_MODEL_FROM_DATABASE=Comet Lake Integrated Sensor Solution + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Integrated Sensor Hub pci:v00008086d000002FCsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Integrated Sensor Solution (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Integrated Sensor Hub (Latitude 7410) pci:v00008086d00000309* ID_MODEL_FROM_DATABASE=80303 I/O Processor PCI-to-PCI Bridge @@ -90719,86 +91628,164 @@ pci:v00008086d0000068E* pci:v00008086d00000697* ID_MODEL_FROM_DATABASE=W480 Chipset LPC/eSPI Controller +pci:v00008086d000006A0* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform P2SB + +pci:v00008086d000006A1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform PMC + pci:v00008086d000006A3* - ID_MODEL_FROM_DATABASE=Comet Lake PCH SMBus Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform SMBus pci:v00008086d000006A4* - ID_MODEL_FROM_DATABASE=Comet Lake PCH SPI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform SPI (flash) Controller + +pci:v00008086d000006A6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform Trace Hub pci:v00008086d000006A8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO UART Host Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform UART #0 pci:v00008086d000006A9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO UART Host Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform UART #1 pci:v00008086d000006AA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform GSPI #0 pci:v00008086d000006AB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform GSPI #1 pci:v00008086d000006AC* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d000006AD* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d000006AE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d000006AF* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #24 pci:v00008086d000006B0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d000006B1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d000006B2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d000006B3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #12 + +pci:v00008086d000006B4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d000006B5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d000006B6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d000006B7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #16 pci:v00008086d000006B8* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d000006B9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #2 pci:v00008086d000006BA* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #3 pci:v00008086d000006BB* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #4 + +pci:v00008086d000006BC* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #5 pci:v00008086d000006BD* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Port #6 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #6 pci:v00008086d000006BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #7 pci:v00008086d000006BF* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Port #8 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #8 pci:v00008086d000006C0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d000006C1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d000006C2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d000006C3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d000006C7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family UART #2 pci:v00008086d000006C8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH cAVS + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HD Audio pci:v00008086d000006D2* - ID_MODEL_FROM_DATABASE=Comet Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) (Desktop) + +pci:v00008086d000006D3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) (Mobile) + +pci:v00008086d000006D5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) pci:v00008086d000006D6* ID_MODEL_FROM_DATABASE=Comet Lake PCH-H RAID pci:v00008086d000006D7* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-H RAID + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + +pci:v00008086d000006DE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) Optane Caching pci:v00008086d000006E0* - ID_MODEL_FROM_DATABASE=Comet Lake HECI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #1 + +pci:v00008086d000006E1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #2 + +pci:v00008086d000006E2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family IDE Redirection (IDE-R) pci:v00008086d000006E3* - ID_MODEL_FROM_DATABASE=Comet Lake Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d000006E4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #3 + +pci:v00008086d000006E5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #4 pci:v00008086d000006E8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #0 pci:v00008086d000006E9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #1 pci:v00008086d000006EA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #2 pci:v00008086d000006EB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #3 pci:v00008086d000006ED* - ID_MODEL_FROM_DATABASE=Comet Lake USB 3.1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller pci:v00008086d000006EF* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Shared SRAM pci:v00008086d000006F0* ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi @@ -90821,14 +91808,17 @@ pci:v00008086d000006F0sv00008086sd000002A4* pci:v00008086d000006F0sv00008086sd000042A4* ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) +pci:v00008086d000006F5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SCS3 SDXC + pci:v00008086d000006F9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Thermal Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Thermal Subsystem pci:v00008086d000006FB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GSPI #2 pci:v00008086d000006FC* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Integrated Sensor Solution + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Integrated Sensor Hub pci:v00008086d00000700* ID_MODEL_FROM_DATABASE=CE Media Processor A/V Bridge @@ -91629,22 +92619,22 @@ pci:v00008086d00000975* ID_MODEL_FROM_DATABASE=Optane NVME SSD H10 with Solid State Storage [Teton Glacier] pci:v00008086d00000998* - ID_MODEL_FROM_DATABASE=Ice Lake IEH + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IEH pci:v00008086d000009A2* - ID_MODEL_FROM_DATABASE=Ice Lake Memory Map/VT-d + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors VT-d pci:v00008086d000009A3* - ID_MODEL_FROM_DATABASE=Ice Lake RAS + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors RAS pci:v00008086d000009A4* - ID_MODEL_FROM_DATABASE=Ice Lake Mesh 2 PCIe + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UFI pci:v00008086d000009A6* - ID_MODEL_FROM_DATABASE=Ice Lake MSM + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors MSM pci:v00008086d000009A7* - ID_MODEL_FROM_DATABASE=Ice Lake PMON MSM + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PMON MSM pci:v00008086d000009AB* ID_MODEL_FROM_DATABASE=RST VMD Managed Controller @@ -91878,7 +92868,7 @@ pci:v00008086d00000B00* ID_MODEL_FROM_DATABASE=Ice Lake CBDMA [QuickData Technology] pci:v00008086d00000B23* - ID_MODEL_FROM_DATABASE=Xeon Root Event Collector + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IEH pci:v00008086d00000B25* ID_MODEL_FROM_DATABASE=Data Streaming Accelerator (DSA) @@ -92280,19 +93270,19 @@ pci:v00008086d00000D36* ID_MODEL_FROM_DATABASE=Crystal Well Integrated Graphics Controller pci:v00008086d00000D4C* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-LM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform GbE Controller (Corporate/vPro) pci:v00008086d00000D4D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform GbE Controller (Consumer) pci:v00008086d00000D4Dsv00008086sd00000D4D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Platform GbE Controller (Consumer) (Ethernet Connection (11) I219-V) pci:v00008086d00000D4E* - ID_MODEL_FROM_DATABASE=Ethernet Connection (10) I219-LM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package GbE Controller (Corporate/vPro) pci:v00008086d00000D4F* - ID_MODEL_FROM_DATABASE=Ethernet Connection (10) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package GbE Controller (Consumer) pci:v00008086d00000D53* ID_MODEL_FROM_DATABASE=Ethernet Connection (12) I219-LM @@ -92312,6 +93302,33 @@ pci:v00008086d00000D58sv00008086sd00000001* pci:v00008086d00000D9F* ID_MODEL_FROM_DATABASE=Ethernet Controller I225-IT +pci:v00008086d00000DB0* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #0 + +pci:v00008086d00000DB1* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #1 + +pci:v00008086d00000DB2* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #2 + +pci:v00008086d00000DB3* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #3 + +pci:v00008086d00000DB4* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors NTB + +pci:v00008086d00000DB6* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #4 + +pci:v00008086d00000DB7* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #5 + +pci:v00008086d00000DB8* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #6 + +pci:v00008086d00000DB9* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #7 + pci:v00008086d00000DC5* ID_MODEL_FROM_DATABASE=Ethernet Connection (23) I219-LM @@ -94622,6 +95639,9 @@ pci:v00008086d000011C3* pci:v00008086d000011C4* ID_MODEL_FROM_DATABASE=Quark SoC X1000 PCIe Root Port 1 +pci:v00008086d000011DF* + ID_MODEL_FROM_DATABASE=Infrastructure Data Path Function + pci:v00008086d000011EB* ID_MODEL_FROM_DATABASE=Simics NVMe Controller @@ -96737,6 +97757,9 @@ pci:v00008086d0000158Bsv00008086sd0000000D* pci:v00008086d0000158Bsv00008086sd00004001* ID_MODEL_FROM_DATABASE=Ethernet Controller XXV710 for 25GbE SFP28 (Ethernet Network Adapter XXV710-2) +pci:v00008086d00001590* + ID_MODEL_FROM_DATABASE=Ethernet Connection E810-C + pci:v00008086d00001591* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C for backplane @@ -96881,6 +97904,15 @@ pci:v00008086d00001593sv00008086sd00004013* pci:v00008086d00001593sv00008086sd0000401C* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C for SFP (Ethernet Network Adapter E810-XXV-4 for OCP 3.0) +pci:v00008086d00001594* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C/X557-AT 10GBASE-T + +pci:v00008086d00001595* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C 1GbE + +pci:v00008086d00001598* + ID_MODEL_FROM_DATABASE=Ethernet Connection E810-XXV + pci:v00008086d00001599* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV for backplane @@ -96947,6 +97979,12 @@ pci:v00008086d0000159Bsv00008086sd00004003* pci:v00008086d0000159Bsv00008086sd00004015* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV for SFP (Ethernet Network Adapter E810-XXV-2 for OCP 3.0) +pci:v00008086d0000159C* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV/X557-AT 10GBASE-T + +pci:v00008086d0000159D* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV 1GbE + pci:v00008086d000015A0* ID_MODEL_FROM_DATABASE=Ethernet Connection (2) I218-LM @@ -97221,10 +98259,10 @@ pci:v00008086d000015F6* ID_MODEL_FROM_DATABASE=I210 Gigabit Ethernet Connection pci:v00008086d000015F9* - ID_MODEL_FROM_DATABASE=Ethernet Connection (14) I219-LM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GbE Controller (Corporate/vPro) pci:v00008086d000015FA* - ID_MODEL_FROM_DATABASE=Ethernet Connection (14) I219-V + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GbE Controller (Consumer) pci:v00008086d000015FB* ID_MODEL_FROM_DATABASE=Ethernet Connection (13) I219-LM @@ -103535,6 +104573,9 @@ pci:v00008086d00002710* pci:v00008086d00002714* ID_MODEL_FROM_DATABASE=Dynamic Load Balancer 2.5 (DLB) +pci:v00008086d00002715* + ID_MODEL_FROM_DATABASE=Dynamic Load Balancer (DLB) Virtual Function + pci:v00008086d00002723* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX200 @@ -104808,19 +105849,19 @@ pci:v00008086d00002821* ID_MODEL_FROM_DATABASE=82801HR/HO/HH (ICH8R/DO/DH) 6 port SATA Controller [AHCI mode] pci:v00008086d00002822* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) pci:v00008086d00002822sv00001028sd0000020D* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (Inspiron 530) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (Inspiron 530) pci:v00008086d00002822sv0000103Csd00002A6F* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (Asus IPIBL-LB Motherboard) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (Asus IPIBL-LB Motherboard) pci:v00008086d00002822sv00001043sd00008277* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (P5K PRO Motherboard: 82801IR [ICH9R]) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (P5K PRO Motherboard: 82801IR [ICH9R]) pci:v00008086d00002822sv00001462sd00007345* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (MS-7345 Motherboard: Intel 82801I/IR [ICH9/ICH9R]) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (MS-7345 Motherboard: Intel 82801I/IR [ICH9/ICH9R]) pci:v00008086d00002823* ID_MODEL_FROM_DATABASE=sSATA Controller [RAID Mode] @@ -104841,25 +105882,25 @@ pci:v00008086d00002825sv00001462sd00007235* ID_MODEL_FROM_DATABASE=82801HR/HO/HH (ICH8R/DO/DH) 2 port SATA Controller [IDE mode] (P965 Neo MS-7235 mainboard) pci:v00008086d00002826* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) pci:v00008086d00002826sv00001D49sd00000100* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000101* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000102* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000103* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000104* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000105* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002827* ID_MODEL_FROM_DATABASE=sSATA Controller [RAID Mode] @@ -104919,13 +105960,13 @@ pci:v00008086d00002829sv0000E4BFsd0000CC47* ID_MODEL_FROM_DATABASE=82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [AHCI mode] (CCG-RUMBA) pci:v00008086d0000282A* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) pci:v00008086d0000282Asv00001028sd0000040B* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] (Latitude E6510) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) (Latitude E6510) pci:v00008086d0000282Asv0000E4BFsd000050C1* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] (PC1-GROOVE) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) (PC1-GROOVE) pci:v00008086d0000282B* ID_MODEL_FROM_DATABASE=C740 Series (Emmitsburg) Chipsets SATA2 Controller (RAID) Alternate ID @@ -108314,17 +109355,38 @@ pci:v00008086d00003200* pci:v00008086d00003200sv00001775sd0000C200* ID_MODEL_FROM_DATABASE=GD31244 PCI-X SATA HBA (C2K onboard SATA host bus adapter) +pci:v00008086d00003240* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI Misc + +pci:v00008086d00003241* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI link/Phy0 + +pci:v00008086d00003242* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI Phy0 + pci:v00008086d00003245* - ID_MODEL_FROM_DATABASE=Xeon UPI Mesh Stop M2UPI Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI pci:v00008086d0000324A* - ID_MODEL_FROM_DATABASE=Xeon IMC0 Mesh to Mem Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IMC pci:v00008086d0000324C* - ID_MODEL_FROM_DATABASE=Xeon Unicast Group1 CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 1 pci:v00008086d0000324D* - ID_MODEL_FROM_DATABASE=Xeon Unicast Group0 CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 0 + +pci:v00008086d00003250* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Event Control + +pci:v00008086d00003251* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Register Access Control Unit + +pci:v00008086d00003252* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Decode + +pci:v00008086d00003256* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Trace Hub pci:v00008086d00003258* ID_MODEL_FROM_DATABASE=Power Control Unit (PCU) CR0 @@ -108603,7 +109665,7 @@ pci:v00008086d0000344D* ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers pci:v00008086d0000344F* - ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 0 pci:v00008086d00003450* ID_MODEL_FROM_DATABASE=Ice Lake Ubox Registers @@ -108621,7 +109683,7 @@ pci:v00008086d00003456* ID_MODEL_FROM_DATABASE=Ice Lake NorthPeak pci:v00008086d00003457* - ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 1 pci:v00008086d00003458* ID_MODEL_FROM_DATABASE=Ice Lake PCU Registers @@ -110699,6 +111761,9 @@ pci:v00008086d00003E9A* pci:v00008086d00003E9B* ID_MODEL_FROM_DATABASE=CoffeeLake-H GT2 [UHD Graphics 630] +pci:v00008086d00003E9Bsv0000106Bsd0000019C* + ID_MODEL_FROM_DATABASE=CoffeeLake-H GT2 [UHD Graphics 630] (MacBookPro16,1 (16", 2019)) + pci:v00008086d00003E9C* ID_MODEL_FROM_DATABASE=Coffee Lake-S GT1 [UHD Graphics 610] @@ -111135,40 +112200,40 @@ pci:v00008086d0000423Dsv00008086sd00001316* ID_MODEL_FROM_DATABASE=WiMAX/WiFi Link 5150 (ABG) pci:v00008086d00004384* - ID_MODEL_FROM_DATABASE=Q570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q570 Chipset eSPI Controller pci:v00008086d00004385* - ID_MODEL_FROM_DATABASE=Z590 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z590 Chipset eSPI Controller pci:v00008086d00004386* - ID_MODEL_FROM_DATABASE=H570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H570 Chipset eSPI Controller pci:v00008086d00004387* - ID_MODEL_FROM_DATABASE=B560 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B560 Chipset eSPI Controller pci:v00008086d00004388* - ID_MODEL_FROM_DATABASE=H510 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H510 Chipset eSPI Controller pci:v00008086d00004389* - ID_MODEL_FROM_DATABASE=WM590 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM590 Chipset eSPI Controller pci:v00008086d0000438A* - ID_MODEL_FROM_DATABASE=QM580 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=QM580 Chipset eSPI Controller pci:v00008086d0000438B* - ID_MODEL_FROM_DATABASE=HM570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM570 Chipset eSPI Controller pci:v00008086d0000438C* - ID_MODEL_FROM_DATABASE=C252 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C252 Chipset eSPI Controller pci:v00008086d0000438D* - ID_MODEL_FROM_DATABASE=C256 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C256 Chipset eSPI Controller pci:v00008086d0000438E* ID_MODEL_FROM_DATABASE=H310D LPC/eSPI Controller pci:v00008086d0000438F* - ID_MODEL_FROM_DATABASE=W580 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W580 Chipset eSPI Controller pci:v00008086d00004390* ID_MODEL_FROM_DATABASE=RM590E LPC/eSPI Controller @@ -111176,59 +112241,206 @@ pci:v00008086d00004390* pci:v00008086d00004391* ID_MODEL_FROM_DATABASE=R580E LPC/eSPI Controller +pci:v00008086d000043A0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family P2SB + +pci:v00008086d000043A1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PMC + pci:v00008086d000043A3* - ID_MODEL_FROM_DATABASE=Tiger Lake-H SMBus Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SMBus pci:v00008086d000043A4* - ID_MODEL_FROM_DATABASE=Tiger Lake-H SPI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SPI (flash) Controller + +pci:v00008086d000043A6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Trace Hub + +pci:v00008086d000043A7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #2 + +pci:v00008086d000043A8* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #0 + +pci:v00008086d000043A9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #1 + +pci:v00008086d000043AA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #0 + +pci:v00008086d000043AB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #1 + +pci:v00008086d000043AD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #4 + +pci:v00008086d000043AE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #5 pci:v00008086d000043B0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d000043B1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d000043B2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d000043B3* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #12 + +pci:v00008086d000043B4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d000043B5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d000043B6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d000043B7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #16 pci:v00008086d000043B8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d000043B9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #2 pci:v00008086d000043BA* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #3 pci:v00008086d000043BB* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #4 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #4 pci:v00008086d000043BC* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #5 + +pci:v00008086d000043BD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #6 pci:v00008086d000043BE* - ID_MODEL_FROM_DATABASE=11th Gen Core Processor PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #7 + +pci:v00008086d000043BF* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #8 pci:v00008086d000043C0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #17 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d000043C1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d000043C2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d000043C3* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d000043C4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d000043C5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d000043C6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #23 pci:v00008086d000043C7* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #24 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #24 pci:v00008086d000043C8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H HD Audio Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043C9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CC* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CF* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043D0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Touch Host Controller (THC) #0 + +pci:v00008086d000043D1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Touch Host Controller (THC) #1 + +pci:v00008086d000043D2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (AHCI) (Server/Desktop) pci:v00008086d000043D3* - ID_MODEL_FROM_DATABASE=Tiger Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (AHCI) (Mobile) + +pci:v00008086d000043D4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Desktop) + +pci:v00008086d000043D5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) + +pci:v00008086d000043D6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Server/Desktop) + +pci:v00008086d000043D7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + +pci:v00008086d000043D8* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #6 + +pci:v00008086d000043DA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #3 pci:v00008086d000043E0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Management Engine Interface + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #1 + +pci:v00008086d000043E1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #2 + +pci:v00008086d000043E2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d000043E3* - ID_MODEL_FROM_DATABASE=Tiger Lake AMT SOL Redirection + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME Keyboard and Text (KT) Redirection + +pci:v00008086d000043E4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #3 + +pci:v00008086d000043E5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #4 pci:v00008086d000043E8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #0 pci:v00008086d000043E9* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #1 + +pci:v00008086d000043EA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #2 + +pci:v00008086d000043EB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #3 pci:v00008086d000043ED* - ID_MODEL_FROM_DATABASE=Tiger Lake-H USB 3.2 Gen 2x1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + +pci:v00008086d000043EE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d000043EF* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Shared SRAM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Shared SRAM pci:v00008086d000043F0* ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi @@ -111245,8 +112457,14 @@ pci:v00008086d000043F0sv00008086sd00000264* pci:v00008086d000043F0sv00008086sd000002A4* ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi (Wireless-AC 9462) +pci:v00008086d000043FB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #2 + pci:v00008086d000043FC* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d000043FD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #3 pci:v00008086d0000444E* ID_MODEL_FROM_DATABASE=Turbo Memory Controller @@ -112257,10 +113475,10 @@ pci:v00008086d0000550B* ID_MODEL_FROM_DATABASE=Ethernet Connection (18) I219-LM pci:v00008086d0000550C* - ID_MODEL_FROM_DATABASE=Ethernet Connection (19) I219-LM + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GbE Controller (Corporate/vPro) pci:v00008086d0000550D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (19) I219-V + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GbE Controller (Consumer) pci:v00008086d0000550E* ID_MODEL_FROM_DATABASE=Ethernet Connection (20) I219-LM @@ -112406,14 +113624,23 @@ pci:v00008086d00005786* pci:v00008086d00005787* ID_MODEL_FROM_DATABASE=JHL9480 Thunderbolt 5 80/120G USB Controller [Barlow Ridge Hub 80G 2023] +pci:v00008086d00005792* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors ILMI + +pci:v00008086d00005793* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors ACPI + pci:v00008086d00005794* - ID_MODEL_FROM_DATABASE=Granite Rapids SPI Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors SPI pci:v00008086d00005795* - ID_MODEL_FROM_DATABASE=Granite Rapids Chipset LPC Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors eSPI pci:v00008086d00005796* - ID_MODEL_FROM_DATABASE=Granite Rapids SMBus Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors SMBus + +pci:v00008086d00005797* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UART pci:v00008086d0000579C* ID_MODEL_FROM_DATABASE=Ethernet Connection E825-C for backplane @@ -112439,8 +113666,11 @@ pci:v00008086d000057A4* pci:v00008086d000057A5* ID_MODEL_FROM_DATABASE=JHL9440 Thunderbolt 4 USB Controller [Barlow Ridge Hub 40G 2023] +pci:v00008086d000057AC* + ID_MODEL_FROM_DATABASE=Ethernet Controller E610 + pci:v00008086d000057AD* - ID_MODEL_FROM_DATABASE=E610 Virtual Function + ID_MODEL_FROM_DATABASE=Ethernet Controller E610 Virtual Function pci:v00008086d000057AE* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 Backplane @@ -112449,31 +113679,31 @@ pci:v00008086d000057AF* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 SFP pci:v00008086d000057B0* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T pci:v00008086d000057B0sv00008086sd00000001* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT4) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT4) pci:v00008086d000057B0sv00008086sd00000002* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT2) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT2) pci:v00008086d000057B0sv00008086sd00000003* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT4 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT4 for OCP 3.0) pci:v00008086d000057B0sv00008086sd00000004* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT2 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT2 for OCP 3.0) pci:v00008086d000057B1* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T pci:v00008086d000057B1sv00008086sd00000000* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Converged Network Adapter E610) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Converged Network Adapter E610) pci:v00008086d000057B1sv00008086sd00000002* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Network Adapter E610-IT4) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Network Adapter E610-IT4) pci:v00008086d000057B1sv00008086sd00000003* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Network Adapter E610-IT4 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Network Adapter E610-IT4 for OCP 3.0) pci:v00008086d000057B2* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 SGMII @@ -113742,7 +114972,7 @@ pci:v00008086d0000777D* ID_MODEL_FROM_DATABASE=Arrow Lake USB 3.2 xHCI Controller pci:v00008086d0000777F* - ID_MODEL_FROM_DATABASE=Arrow Lake Shared SRAM + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Shared SRAM pci:v00008086d00007800* ID_MODEL_FROM_DATABASE=82740 (i740) AGP Graphics Accelerator @@ -113769,181 +114999,184 @@ pci:v00008086d00007800sv00008086sd00000100* ID_MODEL_FROM_DATABASE=82740 (i740) AGP Graphics Accelerator (Intel740 Graphics Accelerator) pci:v00008086d00007A04* - ID_MODEL_FROM_DATABASE=Z790 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z790 Chipset eSPI Controller pci:v00008086d00007A05* - ID_MODEL_FROM_DATABASE=H770 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H770 Chipset eSPI Controller pci:v00008086d00007A06* - ID_MODEL_FROM_DATABASE=B760 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B760 Chipset eSPI Controller pci:v00008086d00007A0C* - ID_MODEL_FROM_DATABASE=HM770 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM770 Chipset eSPI Controller pci:v00008086d00007A0D* ID_MODEL_FROM_DATABASE=WM790 Chipset LPC/eSPI Controller pci:v00008086d00007A13* - ID_MODEL_FROM_DATABASE=C266 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C266 Chipset eSPI Controller pci:v00008086d00007A14* - ID_MODEL_FROM_DATABASE=C262 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C262 Chipset eSPI Controller pci:v00008086d00007A20* - ID_MODEL_FROM_DATABASE=700 Series Chipset P2SB + ID_MODEL_FROM_DATABASE=700 Series Chipset Family P2SB pci:v00008086d00007A21* - ID_MODEL_FROM_DATABASE=700 Series Chipset Power Management Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PMC pci:v00008086d00007A23* - ID_MODEL_FROM_DATABASE=700 Series Chipset SMBus Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SMBus pci:v00008086d00007A24* - ID_MODEL_FROM_DATABASE=Raptor Lake SPI (flash) Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007A26* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Trace Hub pci:v00008086d00007A27* - ID_MODEL_FROM_DATABASE=Raptor Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Shared SRAM pci:v00008086d00007A28* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #0 pci:v00008086d00007A29* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #1 pci:v00008086d00007A2A* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #0 pci:v00008086d00007A2B* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #1 pci:v00008086d00007A30* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #9 pci:v00008086d00007A31* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #10 pci:v00008086d00007A32* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #11 pci:v00008086d00007A33* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007A34* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #13 pci:v00008086d00007A35* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #14 pci:v00008086d00007A36* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #15 pci:v00008086d00007A37* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007A38* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #1 pci:v00008086d00007A39* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #2 pci:v00008086d00007A3A* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #3 pci:v00008086d00007A3B* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #4 pci:v00008086d00007A3C* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #5 pci:v00008086d00007A3D* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #6 pci:v00008086d00007A3E* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #7 pci:v00008086d00007A3F* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #8 pci:v00008086d00007A40* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #17 pci:v00008086d00007A41* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #18 pci:v00008086d00007A42* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #19 pci:v00008086d00007A43* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007A44* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #21 pci:v00008086d00007A45* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #22 pci:v00008086d00007A46* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #23 pci:v00008086d00007A47* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007A48* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #25 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #25 pci:v00008086d00007A49* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #26 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #26 pci:v00008086d00007A4A* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #27 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #27 pci:v00008086d00007A4B* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #28 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #28 pci:v00008086d00007A4C* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #0 pci:v00008086d00007A4D* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #1 pci:v00008086d00007A4E* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #2 pci:v00008086d00007A4F* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #3 pci:v00008086d00007A50* - ID_MODEL_FROM_DATABASE=Raptor Lake High Definition Audio Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio Controller pci:v00008086d00007A5C* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #3 pci:v00008086d00007A60* - ID_MODEL_FROM_DATABASE=Raptor Lake USB 3.2 Gen 2x2 (20 Gb/s) XHCI Host Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller pci:v00008086d00007A61* - ID_MODEL_FROM_DATABASE=Raptor Lake USB 3.2 Gen 1x1 (5 Gb/s) xDCI Device Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d00007A62* - ID_MODEL_FROM_DATABASE=Raptor Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SATA Controller (AHCI) pci:v00008086d00007A68* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #1 pci:v00008086d00007A69* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #2 pci:v00008086d00007A6A* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME IDE Redirection + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d00007A6B* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME Keyboard and Text (KT) Redirection pci:v00008086d00007A6C* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #3 pci:v00008086d00007A6D* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #4 pci:v00008086d00007A70* ID_MODEL_FROM_DATABASE=700 Series Chipset CNVi WiFi @@ -113954,119 +115187,209 @@ pci:v00008086d00007A70sv00008086sd00000074* pci:v00008086d00007A70sv00008086sd00000090* ID_MODEL_FROM_DATABASE=700 Series Chipset CNVi WiFi (WiFi 6E AX211 160MHz) +pci:v00008086d00007A78* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Integrated Sensor Hub + pci:v00008086d00007A79* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #3 pci:v00008086d00007A7B* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #2 pci:v00008086d00007A7C* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #4 pci:v00008086d00007A7D* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #5 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #5 pci:v00008086d00007A7E* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #2 pci:v00008086d00007A83* - ID_MODEL_FROM_DATABASE=Q670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q670 Chipset eSPI Controller pci:v00008086d00007A84* - ID_MODEL_FROM_DATABASE=Z690 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z690 Chipset eSPI Controller pci:v00008086d00007A85* - ID_MODEL_FROM_DATABASE=H670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H670 Chipset eSPI Controller pci:v00008086d00007A86* - ID_MODEL_FROM_DATABASE=B660 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B660 Chipset eSPI Controller pci:v00008086d00007A87* - ID_MODEL_FROM_DATABASE=H610 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H610 Chipset eSPI Controller pci:v00008086d00007A88* - ID_MODEL_FROM_DATABASE=W680 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W680 Chipset eSPI Controller + +pci:v00008086d00007A8A* + ID_MODEL_FROM_DATABASE=W790 Chipset eSPI Controller pci:v00008086d00007A8C* - ID_MODEL_FROM_DATABASE=HM670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM670 Chipset eSPI Controller pci:v00008086d00007A8D* - ID_MODEL_FROM_DATABASE=WM690 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM690 Chipset eSPI Controller + +pci:v00008086d00007AA0* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family P2SB + +pci:v00008086d00007AA1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PMC pci:v00008086d00007AA3* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SMBus Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SMBus pci:v00008086d00007AA4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SPI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007AA6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Trace Hub pci:v00008086d00007AA7* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Shared SRAM + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Shared SRAM pci:v00008086d00007AA8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO UART #0 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #0 + +pci:v00008086d00007AA9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #1 + +pci:v00008086d00007AAA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #0 pci:v00008086d00007AAB* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #1 pci:v00008086d00007AB0* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d00007AB1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d00007AB2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d00007AB3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007AB4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d00007AB5* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d00007AB6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d00007AB7* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007AB8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #1 pci:v00008086d00007AB9* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #2 pci:v00008086d00007ABA* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #3 + +pci:v00008086d00007ABB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #4 pci:v00008086d00007ABC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #5 pci:v00008086d00007ABD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #6 + +pci:v00008086d00007ABE* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #7 pci:v00008086d00007ABF* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #8 + +pci:v00008086d00007AC0* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d00007AC1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d00007AC2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d00007AC3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007AC4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d00007AC5* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d00007AC6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d00007AC7* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007AC8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #25 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #25 + +pci:v00008086d00007AC9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #26 + +pci:v00008086d00007ACA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #27 + +pci:v00008086d00007ACB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #28 pci:v00008086d00007ACC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #0 pci:v00008086d00007ACD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #1 pci:v00008086d00007ACE* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #2 pci:v00008086d00007ACF* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family 12C Controller #3 pci:v00008086d00007AD0* - ID_MODEL_FROM_DATABASE=Alder Lake-S HD Audio Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007ADC* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #3 pci:v00008086d00007AE0* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH USB 3.2 Gen 2x2 XHCI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family USB 3.2 Gen 2x2 (20Gbs) XHCI Host Controller pci:v00008086d00007AE1* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH USB 3.2 Gen 1x1 xDCI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family USB 3.2 Gen 1x1 (5Gbs) Device Controller (xDCI) pci:v00008086d00007AE2* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SATA Controller [AHCI Mode] + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SATA Controller (AHCI) pci:v00008086d00007AE8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH HECI Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #1 + +pci:v00008086d00007AE9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #2 + +pci:v00008086d00007AEA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d00007AEB* - ID_MODEL_FROM_DATABASE=Alder Lake-S Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME Keyboard and Text (KT) Redirection + +pci:v00008086d00007AEC* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #3 + +pci:v00008086d00007AED* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #4 pci:v00008086d00007AF0* ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi @@ -114081,13 +115404,22 @@ pci:v00008086d00007AF0sv00008086sd00000094* ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi (Wi-Fi 6 AX201 160MHz) pci:v00008086d00007AF8* - ID_MODEL_FROM_DATABASE=Alder Lake-S Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d00007AF9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #3 + +pci:v00008086d00007AFB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #2 pci:v00008086d00007AFC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #4 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #4 pci:v00008086d00007AFD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #5 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #5 + +pci:v00008086d00007AFE* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #2 pci:v00008086d00007D01* ID_MODEL_FROM_DATABASE=Meteor Lake-H 6p+8e cores Host Bridge/DRAM Controller @@ -114258,76 +115590,196 @@ pci:v00008086d00007ECC* ID_MODEL_FROM_DATABASE=Meteor Lake-H PCIe Root Port #12 pci:v00008086d00007F03* - ID_MODEL_FROM_DATABASE=Q870 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q870 Chipset eSPI Controller pci:v00008086d00007F04* - ID_MODEL_FROM_DATABASE=Z890 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z890 Chipset eSPI Controller pci:v00008086d00007F06* - ID_MODEL_FROM_DATABASE=B860 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B860 Chipset eSPI Controller pci:v00008086d00007F07* - ID_MODEL_FROM_DATABASE=H810 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H810 Chipset eSPI Controller pci:v00008086d00007F08* - ID_MODEL_FROM_DATABASE=W880 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W880 Chipset eSPI Controller pci:v00008086d00007F0C* - ID_MODEL_FROM_DATABASE=HM870 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM870 Chipset eSPI Controller pci:v00008086d00007F0D* - ID_MODEL_FROM_DATABASE=WM880 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM880 Chipset eSPI Controller pci:v00008086d00007F20* - ID_MODEL_FROM_DATABASE=800 Series PCH P2SB + ID_MODEL_FROM_DATABASE=800 Series Chipset Family P2SB pci:v00008086d00007F21* - ID_MODEL_FROM_DATABASE=800 Series PCH Power Management Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PMC pci:v00008086d00007F23* - ID_MODEL_FROM_DATABASE=800 Series PCH SMBus Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SMBus pci:v00008086d00007F24* - ID_MODEL_FROM_DATABASE=800 Series PCH SPI (flash) Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007F26* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Trace Hub pci:v00008086d00007F27* - ID_MODEL_FROM_DATABASE=800 Series PCH Shared SRAM + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Shared SRAM + +pci:v00008086d00007F28* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #0 + +pci:v00008086d00007F29* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #1 + +pci:v00008086d00007F2A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #0 + +pci:v00008086d00007F2B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #1 pci:v00008086d00007F30* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #9 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d00007F31* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d00007F32* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d00007F33* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007F34* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #13 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d00007F35* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #14 pci:v00008086d00007F36* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #15 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d00007F37* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007F38* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d00007F39* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #2 + +pci:v00008086d00007F3A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #3 + +pci:v00008086d00007F3B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #4 + +pci:v00008086d00007F3C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #5 + +pci:v00008086d00007F3D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #6 pci:v00008086d00007F3E* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #7 + +pci:v00008086d00007F3F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #8 pci:v00008086d00007F40* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #17 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d00007F41* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d00007F42* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d00007F43* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007F44* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #21 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d00007F45* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d00007F46* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d00007F47* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007F4C* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #0 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #0 + +pci:v00008086d00007F4D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #1 + +pci:v00008086d00007F4E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #2 pci:v00008086d00007F4F* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #3 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #3 pci:v00008086d00007F50* - ID_MODEL_FROM_DATABASE=800 Series ACE (Audio Context Engine) + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Audio Context Engine (ACE) + +pci:v00008086d00007F58* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #0 ID1 + +pci:v00008086d00007F59* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #0 ID2 + +pci:v00008086d00007F5A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #1 ID1 + +pci:v00008086d00007F5B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #1 ID2 + +pci:v00008086d00007F5C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #2 + +pci:v00008086d00007F5D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #3 + +pci:v00008086d00007F5E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #2 + +pci:v00008086d00007F5F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #3 + +pci:v00008086d00007F62* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SATA Controller (AHCI) + +pci:v00008086d00007F66* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium pci:v00008086d00007F68* - ID_MODEL_FROM_DATABASE=800 Series PCH HECI #1 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #1 + +pci:v00008086d00007F69* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #2 + +pci:v00008086d00007F6A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family IDE Redirection (IDER-R) + +pci:v00008086d00007F6B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d00007F6C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #3 + +pci:v00008086d00007F6D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #4 pci:v00008086d00007F6E* - ID_MODEL_FROM_DATABASE=800 Series PCH USB 3.1 xHCI HC + ID_MODEL_FROM_DATABASE=800 Series Chipset Family USB 3.1 xHCI HC + +pci:v00008086d00007F6F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family USB Device Controller (OTG) (xDCI) pci:v00008086d00007F70* ID_MODEL_FROM_DATABASE=Arrow Lake-S PCH CNVi WiFi @@ -114335,11 +115787,26 @@ pci:v00008086d00007F70* pci:v00008086d00007F70sv00008086sd00000094* ID_MODEL_FROM_DATABASE=Arrow Lake-S PCH CNVi WiFi (WiFi 6E AX211 160MHz) +pci:v00008086d00007F78* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d00007F79* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I3C + pci:v00008086d00007F7A* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #4 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #4 pci:v00008086d00007F7B* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #5 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #5 + +pci:v00008086d00007F7C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #1 + +pci:v00008086d00007F7D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #2 + +pci:v00008086d00007F7E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #3 pci:v00008086d00008002* ID_MODEL_FROM_DATABASE=Trusted Execution Technology Registers @@ -116553,88 +118020,139 @@ pci:v00008086d0000A013* ID_MODEL_FROM_DATABASE=Atom Processor D4xx/D5xx/N4xx/N5xx CHAPS counter pci:v00008086d0000A082* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP LPC Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package eSPI Controller + +pci:v00008086d0000A0A0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Primary-to-Sideband Bridge (P2SB) + +pci:v00008086d0000A0A1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Power Management Controller (PMC) pci:v00008086d0000A0A3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SMBus Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package System Management Bus (SMBus) pci:v00008086d0000A0A4* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SPI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SPI (flash) Controller pci:v00008086d0000A0A6* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Trace Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Trace Hub pci:v00008086d0000A0A8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #0 pci:v00008086d0000A0A9* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #1 + +pci:v00008086d0000A0AA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #0 pci:v00008086d0000A0AB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #1 pci:v00008086d0000A0B0* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #9 pci:v00008086d0000A0B1* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #10 + +pci:v00008086d0000A0B2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #11 pci:v00008086d0000A0B3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #12 pci:v00008086d0000A0B8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #1 + +pci:v00008086d0000A0B9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #2 + +pci:v00008086d0000A0BA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #3 pci:v00008086d0000A0BB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #4 pci:v00008086d0000A0BC* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #5 pci:v00008086d0000A0BD* - ID_MODEL_FROM_DATABASE=Tigerlake PCH-LP PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #6 pci:v00008086d0000A0BE* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #7 pci:v00008086d0000A0BF* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #8 pci:v00008086d0000A0C5* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #4 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #4 pci:v00008086d0000A0C6* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #5 + +pci:v00008086d0000A0C7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #2 pci:v00008086d0000A0C8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Smart Sound Technology Audio Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package High Definition Audio (HD Audio) + +pci:v00008086d0000A0D0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Touch Host Controller #0 + +pci:v00008086d0000A0D1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Touch Host Controller #1 pci:v00008086d0000A0D3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SATA Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (AHCI) + +pci:v00008086d0000A0D5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + +pci:v00008086d0000A0D7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + +pci:v00008086d0000A0DA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #3 pci:v00008086d0000A0E0* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Management Engine Interface + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #1 + +pci:v00008086d0000A0E1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #2 + +pci:v00008086d0000A0E2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME IDE Redirection (IDE-R) pci:v00008086d0000A0E3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Active Management Technology - SOL + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000A0E4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #3 + +pci:v00008086d0000A0E5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #4 pci:v00008086d0000A0E8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #0 pci:v00008086d0000A0E9* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #1 pci:v00008086d0000A0EA* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #2 pci:v00008086d0000A0EB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #3 pci:v00008086d0000A0ED* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + +pci:v00008086d0000A0EE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d0000A0EF* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Shared SRAM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Shared SRAM pci:v00008086d0000A0F0* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX201 @@ -116642,8 +118160,14 @@ pci:v00008086d0000A0F0* pci:v00008086d0000A0F0sv00008086sd00000244* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX201 (Wi-Fi 6 AX101NGW) +pci:v00008086d0000A0FB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #2 + pci:v00008086d0000A0FC* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Integrated Sensor Hub + +pci:v00008086d0000A0FD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #3 pci:v00008086d0000A102* ID_MODEL_FROM_DATABASE=Q170/Q150/B150/H170/H110/Z170/CM236 Chipset SATA Controller [AHCI Mode] @@ -116676,181 +118200,181 @@ pci:v00008086d0000A10F* ID_MODEL_FROM_DATABASE=Sunrise Point-H SATA Controller [RAID mode] pci:v00008086d0000A110* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #1 pci:v00008086d0000A111* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #2 pci:v00008086d0000A112* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #3 pci:v00008086d0000A113* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #4 pci:v00008086d0000A114* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #5 pci:v00008086d0000A114sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #5 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #5 (H110I-PLUS Motherboard) pci:v00008086d0000A115* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #6 pci:v00008086d0000A116* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #7 pci:v00008086d0000A117* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #8 pci:v00008086d0000A118* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #9 pci:v00008086d0000A118sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #9 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #9 (H110I-PLUS Motherboard) pci:v00008086d0000A119* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #10 pci:v00008086d0000A119sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #10 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #10 (H110I-PLUS Motherboard) pci:v00008086d0000A11A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #11 pci:v00008086d0000A11B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #12 pci:v00008086d0000A11C* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #13 pci:v00008086d0000A11D* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #14 pci:v00008086d0000A11E* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #15 pci:v00008086d0000A11F* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #16 pci:v00008086d0000A120* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family P2SB + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family P2SB pci:v00008086d0000A121* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC pci:v00008086d0000A121sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (XPS 15 9550) pci:v00008086d0000A121sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (OMEN-17-w001nv) pci:v00008086d0000A121sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (H110I-PLUS Motherboard) pci:v00008086d0000A121sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (H110M ECO/GAMING) pci:v00008086d0000A122* ID_MODEL_FROM_DATABASE=Sunrise Point-H cAVS pci:v00008086d0000A123* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus pci:v00008086d0000A123sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (XPS 15 9550) pci:v00008086d0000A123sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (OMEN-17-w001nv) pci:v00008086d0000A123sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (H110I-PLUS Motherboard) pci:v00008086d0000A123sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (H110M ECO/GAMING) pci:v00008086d0000A124* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SPI Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SPI Controller pci:v00008086d0000A125* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Gigabit Ethernet Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GbE Controller pci:v00008086d0000A126* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Trace Hub + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Trace Hub pci:v00008086d0000A127* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #0 pci:v00008086d0000A128* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #1 pci:v00008086d0000A129* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO GSPI #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GSPI #0 pci:v00008086d0000A12A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO GSPI #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GSPI #1 pci:v00008086d0000A12F* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller pci:v00008086d0000A12Fsv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (XPS 15 9550) pci:v00008086d0000A12Fsv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (OMEN-17-w001nv) pci:v00008086d0000A12Fsv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (H110I-PLUS Motherboard) pci:v00008086d0000A12Fsv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (H110M ECO/GAMING) pci:v00008086d0000A130* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB Device Controller (OTG) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB Device Controller (OTG) pci:v00008086d0000A131* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem pci:v00008086d0000A131sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (XPS 15 9550) pci:v00008086d0000A131sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (OMEN-17-w001nv) pci:v00008086d0000A131sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (H110M ECO/GAMING) pci:v00008086d0000A133* ID_MODEL_FROM_DATABASE=Sunrise Point-H Northpeak ACPI Function pci:v00008086d0000A135* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family ISH pci:v00008086d0000A13A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 pci:v00008086d0000A13Asv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (XPS 15 9550) pci:v00008086d0000A13Asv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (OMEN-17-w001nv) pci:v00008086d0000A13Asv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (H110I-PLUS Motherboard) pci:v00008086d0000A13Asv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (H110M ECO/GAMING) pci:v00008086d0000A13B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #2 pci:v00008086d0000A13C* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family IDE Redirection + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family IDE Redirection pci:v00008086d0000A13D* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family KT Redirection + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Keyboard and Text (KT) Redirection pci:v00008086d0000A13E* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #3 pci:v00008086d0000A140* ID_MODEL_FROM_DATABASE=Sunrise Point-H LPC Controller @@ -116961,55 +118485,55 @@ pci:v00008086d0000A15F* ID_MODEL_FROM_DATABASE=Sunrise Point-H LPC Controller pci:v00008086d0000A160* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 pci:v00008086d0000A160sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 (XPS 15 9550) pci:v00008086d0000A160sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 (OMEN-17-w001nv) pci:v00008086d0000A161* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #1 pci:v00008086d0000A161sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #1 (XPS 15 9550) pci:v00008086d0000A162* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #2 pci:v00008086d0000A163* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #3 pci:v00008086d0000A166* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #2 pci:v00008086d0000A167* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #17 pci:v00008086d0000A168* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #18 pci:v00008086d0000A169* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #19 pci:v00008086d0000A16A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #20 pci:v00008086d0000A170* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio pci:v00008086d0000A170sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (XPS 15 9550) pci:v00008086d0000A170sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (OMEN-17-w001nv) pci:v00008086d0000A170sv00001043sd000086C7* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (H110I-PLUS Motherboard) pci:v00008086d0000A170sv00001462sd0000F994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (H110M ECO/GAMING) pci:v00008086d0000A171* ID_MODEL_FROM_DATABASE=HM175/QM175/CM238 HD Audio Controller @@ -117285,106 +118809,109 @@ pci:v00008086d0000A26A* ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port pci:v00008086d0000A282* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [AHCI mode] + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (AHCI) pci:v00008086d0000A282sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [AHCI mode] (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (AHCI) (H270 PC MATE) pci:v00008086d0000A286* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [RAID mode] + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (RAID) Premium + +pci:v00008086d0000A28E* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (RST and Optane Technology) pci:v00008086d0000A290* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #1 pci:v00008086d0000A291* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #2 pci:v00008086d0000A292* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #3 pci:v00008086d0000A293* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #4 pci:v00008086d0000A294* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #5 pci:v00008086d0000A294sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #5 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #5 (H270 PC MATE) pci:v00008086d0000A295* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #6 pci:v00008086d0000A296* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #7 pci:v00008086d0000A296sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #7 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #7 (H270 PC MATE) pci:v00008086d0000A297* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #8 pci:v00008086d0000A298* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #9 pci:v00008086d0000A298sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #9 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #9 (H270 PC MATE) pci:v00008086d0000A299* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #10 pci:v00008086d0000A29A* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #11 pci:v00008086d0000A29B* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #12 pci:v00008086d0000A29C* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #13 pci:v00008086d0000A29D* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #14 pci:v00008086d0000A29E* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #15 pci:v00008086d0000A29F* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #16 pci:v00008086d0000A2A0* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family P2SB pci:v00008086d0000A2A1* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Power Management Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PMC pci:v00008086d0000A2A1sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Power Management Controller (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PMC (H270 PC MATE) pci:v00008086d0000A2A3* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus pci:v00008086d0000A2A3sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus Controller (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus (H270 PC MATE) pci:v00008086d0000A2A4* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SPI Controller pci:v00008086d0000A2A5* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Gigabit Ethernet Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GbE Controller pci:v00008086d0000A2A6* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Trace Hub pci:v00008086d0000A2A7* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #0 pci:v00008086d0000A2A8* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #1 pci:v00008086d0000A2A9* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO SPI Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GSPI #0 pci:v00008086d0000A2AA* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GSPI #1 pci:v00008086d0000A2AF* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB 3.0 xHCI Controller @@ -117392,41 +118919,53 @@ pci:v00008086d0000A2AF* pci:v00008086d0000A2AFsv00001462sd00007A72* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB 3.0 xHCI Controller (H270 PC MATE) +pci:v00008086d0000A2B0* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB Device Controller (OTG) + pci:v00008086d0000A2B1* - ID_MODEL_FROM_DATABASE=200 Series PCH Thermal Subsystem + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Thermal Subsystem pci:v00008086d0000A2B1sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH Thermal Subsystem (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Thermal Subsystem (H270 PC MATE) + +pci:v00008086d0000A2B5* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family ISH pci:v00008086d0000A2BA* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #1 pci:v00008086d0000A2BAsv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #1 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #1 (H270 PC MATE) pci:v00008086d0000A2BB* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #2 + +pci:v00008086d0000A2BC* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family IDE Redirection pci:v00008086d0000A2BD* - ID_MODEL_FROM_DATABASE=200 Series Chipset Family KT Redirection + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d0000A2BE* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #3 pci:v00008086d0000A2C4* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (H270) + ID_MODEL_FROM_DATABASE=H270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C4sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (H270) (H270 PC MATE) + ID_MODEL_FROM_DATABASE=H270 Chipset LPC/eSPI Controller (H270 PC MATE) pci:v00008086d0000A2C5* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Z270) + ID_MODEL_FROM_DATABASE=Z270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C6* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Q270) + ID_MODEL_FROM_DATABASE=Q270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C7* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Q250) + ID_MODEL_FROM_DATABASE=Q250 Chipset LPC/eSPI Controller pci:v00008086d0000A2C8* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (B250) + ID_MODEL_FROM_DATABASE=B250 Chipset LPC/eSPI Controller pci:v00008086d0000A2C9* ID_MODEL_FROM_DATABASE=Z370 Chipset LPC/eSPI Controller @@ -117438,52 +118977,52 @@ pci:v00008086d0000A2D3* ID_MODEL_FROM_DATABASE=C422 Chipset LPC/eSPI Controller pci:v00008086d0000A2E0* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #0 pci:v00008086d0000A2E1* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #1 pci:v00008086d0000A2E2* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #2 pci:v00008086d0000A2E3* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #3 pci:v00008086d0000A2E6* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #2 pci:v00008086d0000A2E7* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #17 pci:v00008086d0000A2E8* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #18 pci:v00008086d0000A2E9* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #19 pci:v00008086d0000A2EA* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #20 pci:v00008086d0000A2EB* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #21 pci:v00008086d0000A2EC* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #22 pci:v00008086d0000A2ED* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #23 pci:v00008086d0000A2EE* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #24 pci:v00008086d0000A2F0* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio pci:v00008086d0000A2F0sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio (H270 PC MATE) pci:v00008086d0000A2F0sv00001462sd0000FA72* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio (H270 PC MATE) pci:v00008086d0000A303* ID_MODEL_FROM_DATABASE=H310 Chipset LPC/eSPI Controller @@ -117524,95 +119063,113 @@ pci:v00008086d0000A30E* pci:v00008086d0000A313* ID_MODEL_FROM_DATABASE=Cannon Lake LPC/eSPI Controller +pci:v00008086d0000A320* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family P2SB + +pci:v00008086d0000A321* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PMC + pci:v00008086d0000A323* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SMBus Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SMBus pci:v00008086d0000A323sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SMBus Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SMBus (Vostro 3470) pci:v00008086d0000A324* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI (Flash) Controller pci:v00008086d0000A324sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI (Flash) Controller (Vostro 3470) + +pci:v00008086d0000A326* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Trace Hub pci:v00008086d0000A328* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO UART Host Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #0 + +pci:v00008086d0000A329* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #1 + +pci:v00008086d0000A32A* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family GSPI #0 pci:v00008086d0000A32B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Host Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family GSPI #1 pci:v00008086d0000A32C* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #21 pci:v00008086d0000A32D* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #22 pci:v00008086d0000A32E* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #23 pci:v00008086d0000A32F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #24 pci:v00008086d0000A330* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #9 pci:v00008086d0000A331* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #10 pci:v00008086d0000A332* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #11 pci:v00008086d0000A333* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #12 pci:v00008086d0000A334* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #13 pci:v00008086d0000A335* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #14 pci:v00008086d0000A336* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #15 pci:v00008086d0000A337* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #16 pci:v00008086d0000A338* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #1 pci:v00008086d0000A339* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #2 pci:v00008086d0000A33A* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #3 pci:v00008086d0000A33B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #4 pci:v00008086d0000A33C* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #5 pci:v00008086d0000A33D* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #6 pci:v00008086d0000A33E* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #7 pci:v00008086d0000A33F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #8 pci:v00008086d0000A340* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #17 pci:v00008086d0000A341* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #18 pci:v00008086d0000A342* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #19 pci:v00008086d0000A343* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d0000A347* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #2 pci:v00008086d0000A348* ID_MODEL_FROM_DATABASE=Cannon Lake PCH cAVS @@ -117621,37 +119178,58 @@ pci:v00008086d0000A348sv00001028sd00000869* ID_MODEL_FROM_DATABASE=Cannon Lake PCH cAVS (Vostro 3470) pci:v00008086d0000A352* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SATA AHCI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) pci:v00008086d0000A352sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SATA AHCI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) (Vostro 3470) pci:v00008086d0000A353* - ID_MODEL_FROM_DATABASE=Cannon Lake Mobile PCH SATA AHCI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) + +pci:v00008086d0000A355* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A356* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A357* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A35E* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller Optane Memory pci:v00008086d0000A360* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #1 pci:v00008086d0000A360sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #1 (Vostro 3470) + +pci:v00008086d0000A361* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #2 + +pci:v00008086d0000A362* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family IDE Redirection (IDER-R) pci:v00008086d0000A363* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Active Management Technology - SOL + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Keyboard and Text (KT) Redirection pci:v00008086d0000A364* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #3 + +pci:v00008086d0000A365* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #4 pci:v00008086d0000A368* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #0 pci:v00008086d0000A369* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #1 pci:v00008086d0000A36A* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #2 pci:v00008086d0000A36B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #3 pci:v00008086d0000A36D* ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller @@ -117659,8 +119237,11 @@ pci:v00008086d0000A36D* pci:v00008086d0000A36Dsv00001028sd00000869* ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller (Vostro 3470) +pci:v00008086d0000A36E* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family USB Device Controller (Dual Role) + pci:v00008086d0000A36F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Shared SRAM pci:v00008086d0000A370* ID_MODEL_FROM_DATABASE=Cannon Lake PCH CNVi WiFi @@ -117672,10 +119253,16 @@ pci:v00008086d0000A370sv00008086sd00000034* ID_MODEL_FROM_DATABASE=Cannon Lake PCH CNVi WiFi (Wireless-AC 9560) pci:v00008086d0000A379* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Thermal Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Thermal Subsystem pci:v00008086d0000A379sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Thermal Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Thermal Subsystem (Vostro 3470) + +pci:v00008086d0000A37B* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI #2 + +pci:v00008086d0000A37C* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Integrated Sensor Hub pci:v00008086d0000A382* ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA AHCI Controller @@ -117909,7 +119496,7 @@ pci:v00008086d0000A83D* ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #6 pci:v00008086d0000A840* - ID_MODEL_FROM_DATABASE=BE201 320MHz + ID_MODEL_FROM_DATABASE=BE200 Series Wi-Fi 7 pci:v00008086d0000A845* ID_MODEL_FROM_DATABASE=Lunar Lake-M Integrated Sensor Hub @@ -118299,10 +119886,10 @@ pci:v00008086d0000E221* ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] pci:v00008086d0000E222* - ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] + ID_MODEL_FROM_DATABASE=Battlemage G31 [Arc Pro B65] pci:v00008086d0000E223* - ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] + ID_MODEL_FROM_DATABASE=Battlemage G31 [Arc Pro B70] pci:v00008086d0000E302* ID_MODEL_FROM_DATABASE=Panther Lake LPC/eSPI Controller @@ -118442,6 +120029,27 @@ pci:v00008086d0000E37D* pci:v00008086d0000E37F* ID_MODEL_FROM_DATABASE=Panther Lake Shared SRAM +pci:v00008086d0000E431* + ID_MODEL_FROM_DATABASE=Panther Lake USB 3.2 xHCI Controller + +pci:v00008086d0000E434* + ID_MODEL_FROM_DATABASE=Panther Lake Thunderbolt 4 NHI #2 + +pci:v00008086d0000E440* + ID_MODEL_FROM_DATABASE=Panther Lake PCH CNVi WiFi + +pci:v00008086d0000E440sv00008086sd00000114* + ID_MODEL_FROM_DATABASE=Panther Lake PCH CNVi WiFi (Wi-Fi 7 BE211 320MHz) + +pci:v00008086d0000E476* + ID_MODEL_FROM_DATABASE=Panther Lake Bluetooth PCI Enumerator + +pci:v00008086d0000E47B* + ID_MODEL_FROM_DATABASE=Serial IO I2C Host Controller - E47B + +pci:v00008086d0000E47D* + ID_MODEL_FROM_DATABASE=Panther Lake USB 3.2 xHCI Controller + pci:v00008086d0000F1A5* ID_MODEL_FROM_DATABASE=SSD 600P Series @@ -118838,6 +120446,9 @@ pci:v00008510d00000201sv00008510sd00000014* pci:v00008510d00000201sv00008510sd00000201* ID_MODEL_FROM_DATABASE=GenBu02 Series GPU (GB2062-PUB-DDR) +pci:v00008510d00000301* + ID_MODEL_FROM_DATABASE=GenBu03 Series GPU + pci:v00008686* ID_VENDOR_FROM_DATABASE=SAP diff --git a/hwdb.d/acpi_id_registry.csv b/hwdb.d/acpi_id_registry.csv index df362829c59e3..95627ac05243a 100644 --- a/hwdb.d/acpi_id_registry.csv +++ b/hwdb.d/acpi_id_registry.csv @@ -1,152 +1,153 @@ Company,"ACPI ID","Approved On Date" -"Aava Mobile Oy",AAVA,04/02/2014 -AMD,AMDI,08/06/2014 -"Applied Micro Circuits Corporation",APMC,11/14/2013 -"Aptina Imaging Corporation",APTA,10/28/2013 -"ARM Ltd.",ARMH,08/13/2012 -"ARM Ltd.",ARML,02/24/2015 -ASUS,ASUS,09/13/2012 -Atmel,ATML,11/17/2011 -AuthenTec,AUTH,06/01/2012 -"Broadcom Corporation",BRCM,05/14/2015 -"Capella Microsystems Inc.",CPLM,05/09/2012 -"Dell, Inc.",DLLK,04/17/2012 -"Dell, Inc.",DELL,07/26/2012 -"ELAN MICROELECTRONICS CORPORATION",ELAN,11/14/2014 -"Everest Semiconductor Co., Ltd.",ESSX,11/17/2014 -"FocalTech Systems Co., Ltd.",FTSC,07/23/2013 -"Freescale, Inc",FRSC,01/01/2010 -"Fuzhou Rockchip Electronics Co., Ltd.",RKCP,07/20/2015 -"Google, Inc.",GOOG,12/05/2013 -"Hewlett-Packard Company",HPQC,11/26/2012 -"Hewlett Packard Enterprise",HWPE,01/15/2015 -"Himax Technologies, Inc.",HIMX,03/19/2014 -"HiSilicon Technologies Co., Ltd.",HISI,11/14/2014 -"HP Inc.",HPIC,01/15/2015 -"HTBLuVA Mödling",HTLM,02/18/2014 -IBM,IBMX,11/15/2012 -Impinj,IMPJ,08/14/2012 -"Inphi Corporation",IPHI,07/15/2014 -"Intel Corporation",ACPI,11/18/2011 -"Intel Corporation",INTC,01/01/2010 -"Intel Corporation",INTL,01/01/2010 -"Invensense, Inc",INVN,02/09/2012 -"IP3 Technology Ltd.",IP3T,11/11/2013 -"Kionix, Inc.",KIOX,12/23/2013 -"Lenovo Beijing Co. Ltd.",IDEA,05/22/2012 -"Linaro, Ltd.",LNRO,11/26/2013 -"Microsoft Corporation",MSAY,03/01/2012 -"Microsoft Corporation",MSFT,01/01/2010 -"Microsoft Corporation",MSHW,01/10/2011 -"MIPI Alliance",MIPI,04/17/2015 -"Nuvoton Technology Corporation",NVTN,11/14/2014 -Nvidia,NVDA,01/01/2010 -"OmniVision Technologies, Inc.",OVTI,02/26/2014 -"Pegatron Corporation",PEGA,08/27/2013 -"Qualcomm Inc",QCOM,01/01/2010 -"REALTEK Semiconductor Corp.",OBDA,11/07/2013 -"Red Hat, Inc.",QEMU,07/30/2015 -"Robert Bosch GmbH",BOSC,05/16/2014 -"Rozsnyó, s.r.o.",RZSN,03/24/2014 -"Sharp Corporation",SHRP,01/27/2015 -"Shenzhen DSO Microelectronics Co.,Ltd.",DSUO,10/10/2013 -"Shenzhen South-Top Computer Co., Ltd.",ST86,12/06/2013 -"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",3NOD,09/23/2013 -"Sierra Wireless",SWEM,01/22/2013 -"Sony Corporation",SONY,09/12/2012 -"Synaptics Inc",SYNA,11/17/2011 -"Teracue AG",TCAG,12/07/2012 -"Texas Instruments",TXNW,01/01/2010 -"The Linux Foundation",LNUX,04/04/2014 -"Dynabook Inc.",TOSB,07/07/2015 -"VAIO Corporation",VAIO,04/18/2014 -"Validity Sensors, Inc",VFSI,06/17/2013 -Wacom,WCOM,11/17/2011 -"Winsider Seminars & Solutions Inc.",WSDR,07/07/2015 -"Maxim Integrated",MXIM,10/16/2015 -"Xiaomi Inc.",XMCC,12/08/2015 -"u-blox AG",UBLX,12/08/2015 -"Raydium Semiconductor Corporation",RAYD,04/13/2016 -"Dialog Semiconductor PLC",DLGS,04/27/2016 -OmniPreSense,OMPS,05/25/2016 -"CoreOS, Inc",CORE,07/01/2016 -"Microchip Technology Inc",MCHP,08/03/2016 -"Green Hills Software",GHSW,08/11/2016 -"AAEON Technology Inc.",AANT,09/01/2016 -"VR Technology Holdings Limited",3GVR,01/19/2017 -"Exar Corporation",EXAR,02/28/2017 -"Coreboot Project",BOOT,02/28/2017 -"Marvell Technology Group Ltd.",MRVL,05/25/2017 -"IHSE GmbH",IHSE,06/22/2017 -"Insyde Software",INSY,11/10/2017 -"Nexstgo Company Limited",NXGO,11/13/2017 -"Ampere Computing",AMPC,03/29/2018 -IDEMIA,IDEM,06/26/2018 -"Vishay Intertechnology, Inc.",VSHY,07/09/2018 -"DMIST RESEARCH LTD",DMST,07/09/2018 -"COMHEAR, INC.",CMHR,08/02/2018 -"Sensel, Inc.",SNSL,08/20/2018 -"G2touch Co., LTD",GTCH,12/04/2018 -"Guizhou Huaxintong Semiconductor Technology Co., Ltd",HXTS,01/18/2019 -"Amazon Corporation",AMZN,02/06/2019 -"ASEM S.p.A.",ASEM,04/29/2019 -"Fujitsu Limited",FUJI,06/18/2019 -"Phytium Technology Co. Ltd.",PHYT,02/14/2020 -"CHENGDU HAIGUANG IC DESIGN CO., LTD",HYGO,07/15/2020 -"PixArt imaging inc.",PIXA,07/15/2020 -"Loongson Technology Corporation Limited",LOON,09/10/2020 -"Seiko Epson Corporation",SECC,02/16/2021 -"Alibaba Co., Ltd.",BABA,02/02/2021 -"Juniper Systems, Inc.",JSYS,03/18/2021 -"Framework Computer LLC",FRMW,03/22/2021 -"Pensando Systems, Inc.",PNSO,03/24/2021 -"Dynabook Inc.",DNBK,06/01/2021 -"Dioo Microcircuits Co., Ltd. Jiangsu",DIOO,06/04/2021 -"Purism SPC",PURI,06/10/2021 -"Lontium Semiconductor Corporation",LTSC,07/21/2021 -"Wacom Technology",WACF,09/21/2021 -"Shanghai Aiwei Electronic Technology Co., Ltd.",AWDZ,12/31/2021 -"Silicom Ltd. Connectivity Solutions",SILC,03/28/2022 -"NOLO Co., Ltd.",NOLO,03/28/2022 -"GoUp Co.,Ltd",GOUP,06/24/2022 -"Shenzhen Jaguar Microsystems Co.,Ltd.",JMIC,09/23/2022 -"Elliptic Laboratories AS",ELAS,10/20/2022 -"Micro Crystal AG",MCRY,11/10/2022 -"Cix Technology (Shanghai) Co., Ltd.",CIXH,11/16/2022 -"EyeTech Digital Systems",ETDS,11/29/2022 -"Raspberry Pi",RPIL,02/22/2023 -"Shenzhen Istaric Technology Co., Ltd.",ISIC,04/24/2023 -"Cirque Corporation",CIRQ,05/09/2023 -"Rivos Inc.",RVOS,06/07/2023 -"Beijer Electronics AB",BECS,06/07/2023 -"ILI Technology Corp",ILIT,06/20/2023 -"Hangzhou hj-micro Technology Co., Ltd",HJMC,07/31/2023 -"Vervent Audio Group",NAIM,01/04/2024 -"Das U-Boot",UBOO,02/14/2024 -3mdeb,DSHR,06/13/2024 -"SigmaSense, LLC",SGSN,06/13/2024 -"INIT - Innovative Informatikanwendungen GmbH",INIT,08/28/2024 -"RISC-V International",RSCV,10/23/2023 -"Ventana Micro Systems",VNTN,09/16/2024 -"DreamBig Semiconductor Inc.",DBSI,01/28/2025 -"Wuxi Institute of Advanced Technology",SUNW,01/28/2025 -"Sophgo Technologies Ltd.",SOPH,04/07/2025 -"SmartSens Technology (Shanghai) CO., Ltd.",SSLC,04/07/2025 -"Areus GmbH",AREU,04/07/2025 -"Rockwell Automation, Inc",ROKL,04/18/2025 -"JUMPtec GmbH",JUMP,04/22/2025 -"Fsas Technologies Inc.",FSAS,04/30/2025 -"JP Morgan Chase N.A.",JPMC,05/30/2025 -"Roku, Inc.",ROKU,07/15/2025 -"UltraRISC Technology (Shanghai) Co., Ltd",ULRV,09/15/2025 -"SYNCS / Aviot Systems Pte Ltd",SYNC,10/21/2025 -"Advantech Co., Ltd.",AHCL,10/23/2025 -"Picoheart (SG) Pte. Ltd.",PICO,10/30/2025 -"Kontron France",KOMF,12/09/2025 -"Ubiquiti Inc.",UBTI,12/10/2025 -"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 -"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 +"Aava Mobile Oy",AAVA,2014-04-02 +AMD,AMDI,2014-08-06 +"Applied Micro Circuits Corporation",APMC,2013-11-14 +"Aptina Imaging Corporation",APTA,2013-10-28 +"ARM Ltd.",ARMH,2012-08-13 +"ARM Ltd.",ARML,2015-02-24 +ASUS,ASUS,2012-09-13 +Atmel,ATML,2011-11-17 +AuthenTec,AUTH,2012-06-01 +"Broadcom Corporation",BRCM,2015-05-14 +"Capella Microsystems Inc.",CPLM,2012-05-09 +"Dell, Inc.",DLLK,2012-04-17 +"Dell, Inc.",DELL,2012-07-26 +"ELAN MICROELECTRONICS CORPORATION",ELAN,2014-11-14 +"Everest Semiconductor Co., Ltd.",ESSX,2014-11-17 +"FocalTech Systems Co., Ltd.",FTSC,2013-07-23 +"Freescale, Inc",FRSC,2010-01-01 +"Fuzhou Rockchip Electronics Co., Ltd.",RKCP,2015-07-20 +"Google, Inc.",GOOG,2013-12-05 +"Hewlett-Packard Company",HPQC,2012-11-26 +"Hewlett Packard Enterprise",HWPE,2015-01-15 +"Himax Technologies, Inc.",HIMX,2014-03-19 +"HiSilicon Technologies Co., Ltd.",HISI,2014-11-14 +"HP Inc.",HPIC,2015-01-15 +"HTBLuVA Mödling",HTLM,2014-02-18 +IBM,IBMX,2012-11-15 +Impinj,IMPJ,2012-08-14 +"Inphi Corporation",IPHI,2014-07-15 +"Intel Corporation",ACPI,2011-11-18 +"Intel Corporation",INTC,2010-01-01 +"Intel Corporation",INTL,2010-01-01 +"Invensense, Inc",INVN,2012-02-09 +"IP3 Technology Ltd.",IP3T,2013-11-11 +"Kionix, Inc.",KIOX,2013-12-23 +"Lenovo Beijing Co. Ltd.",IDEA,2012-05-22 +"Linaro, Ltd.",LNRO,2013-11-26 +"Microsoft Corporation",MSAY,2012-03-01 +"Microsoft Corporation",MSFT,2010-01-01 +"Microsoft Corporation",MSHW,2011-01-10 +"MIPI Alliance",MIPI,2015-04-17 +"Nuvoton Technology Corporation",NVTN,2014-11-14 +Nvidia,NVDA,2010-01-01 +"OmniVision Technologies, Inc.",OVTI,2014-02-26 +"Pegatron Corporation",PEGA,2013-08-27 +"Qualcomm Inc",QCOM,2010-01-01 +"REALTEK Semiconductor Corp.",OBDA,2013-11-07 +"Red Hat, Inc.",QEMU,2015-07-30 +"Robert Bosch GmbH",BOSC,2014-05-16 +"Rozsnyó, s.r.o.",RZSN,2014-03-24 +"Sharp Corporation",SHRP,2015-01-27 +"Shenzhen DSO Microelectronics Co.,Ltd.",DSUO,2013-10-10 +"Shenzhen South-Top Computer Co., Ltd.",ST86,2013-12-06 +"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",3NOD,2013-09-23 +"Sierra Wireless",SWEM,2013-01-22 +"Sony Corporation",SONY,2012-09-12 +"Synaptics Inc",SYNA,2011-11-17 +"Teracue AG",TCAG,2012-12-07 +"Texas Instruments",TXNW,2010-01-01 +"The Linux Foundation",LNUX,2014-04-04 +"Dynabook Inc.",TOSB,2015-07-07 +"VAIO Corporation",VAIO,2014-04-18 +"Validity Sensors, Inc",VFSI,2013-06-17 +Wacom,WCOM,2011-11-17 +"Winsider Seminars & Solutions Inc.",WSDR,2015-07-07 +"Maxim Integrated",MXIM,2015-10-16 +"Xiaomi Inc.",XMCC,2015-12-08 +"u-blox AG",UBLX,2015-12-08 +"Raydium Semiconductor Corporation",RAYD,2016-04-13 +"Dialog Semiconductor PLC",DLGS,2016-04-27 +OmniPreSense,OMPS,2016-05-25 +"CoreOS, Inc",CORE,2016-07-01 +"Microchip Technology Inc",MCHP,2016-08-03 +"Green Hills Software",GHSW,2016-08-11 +"AAEON Technology Inc.",AANT,2016-09-01 +"VR Technology Holdings Limited",3GVR,2017-01-19 +"Exar Corporation",EXAR,2017-02-28 +"Coreboot Project",BOOT,2017-02-28 +"Marvell Technology Group Ltd.",MRVL,2017-05-25 +"IHSE GmbH",IHSE,2017-06-22 +"Insyde Software",INSY,2017-11-10 +"Nexstgo Company Limited",NXGO,2017-11-13 +"Ampere Computing",AMPC,2018-03-29 +IDEMIA,IDEM,2018-06-26 +"Vishay Intertechnology, Inc.",VSHY,2018-07-09 +"DMIST RESEARCH LTD",DMST,2018-07-09 +"COMHEAR, INC.",CMHR,2018-08-02 +"Sensel, Inc.",SNSL,2018-08-20 +"G2touch Co., LTD",GTCH,2018-12-04 +"Guizhou Huaxintong Semiconductor Technology Co., Ltd",HXTS,2019-01-18 +"Amazon Corporation",AMZN,2019-02-06 +"ASEM S.p.A.",ASEM,2019-04-29 +"Fujitsu Limited",FUJI,2019-06-18 +"Phytium Technology Co. Ltd.",PHYT,2020-02-14 +"CHENGDU HAIGUANG IC DESIGN CO., LTD",HYGO,2020-07-15 +"PixArt imaging inc.",PIXA,2020-07-15 +"Loongson Technology Corporation Limited",LOON,2020-09-10 +"Seiko Epson Corporation",SECC,2021-02-16 +"Alibaba Co., Ltd.",BABA,2021-02-02 +"Juniper Systems, Inc.",JSYS,2021-03-18 +"Framework Computer LLC",FRMW,2021-03-22 +"Pensando Systems, Inc.",PNSO,2021-03-24 +"Dynabook Inc.",DNBK,2021-06-01 +"Dioo Microcircuits Co., Ltd. Jiangsu",DIOO,2021-06-04 +"Purism SPC",PURI,2021-06-10 +"Lontium Semiconductor Corporation",LTSC,2021-07-21 +"Wacom Technology",WACF,2021-09-21 +"Shanghai Aiwei Electronic Technology Co., Ltd.",AWDZ,2021-12-31 +"Silicom Ltd. Connectivity Solutions",SILC,2022-03-28 +"NOLO Co., Ltd.",NOLO,2022-03-28 +"GoUp Co.,Ltd",GOUP,2022-06-24 +"Shenzhen Jaguar Microsystems Co.,Ltd.",JMIC,2022-09-23 +"Elliptic Laboratories AS",ELAS,2022-10-20 +"Micro Crystal AG",MCRY,2022-11-10 +"Cix Technology (Shanghai) Co., Ltd.",CIXH,2022-11-16 +"EyeTech Digital Systems",ETDS,2022-11-29 +"Raspberry Pi",RPIL,2023-02-22 +"Shenzhen Istaric Technology Co., Ltd.",ISIC,2023-04-24 +"Cirque Corporation",CIRQ,2023-05-09 +"Rivos Inc.",RVOS,2023-06-07 +"Beijer Electronics AB",BECS,2023-06-07 +"ILI Technology Corp",ILIT,2023-06-20 +"Hangzhou hj-micro Technology Co., Ltd",HJMC,2023-07-31 +"Vervent Audio Group",NAIM,2024-01-04 +"Das U-Boot",UBOO,2024-02-14 +3mdeb,DSHR,2024-06-13 +"SigmaSense, LLC",SGSN,2024-06-13 +"INIT - Innovative Informatikanwendungen GmbH",INIT,2024-08-28 +"RISC-V International",RSCV,2023-10-23 +"Ventana Micro Systems",VNTN,2024-09-16 +"DreamBig Semiconductor Inc.",DBSI,2025-01-28 +"Wuxi Institute of Advanced Technology",SUNW,2025-01-28 +"Sophgo Technologies Ltd.",SOPH,2025-04-07 +"SmartSens Technology (Shanghai) CO., Ltd.",SSLC,2025-04-07 +"Areus GmbH",AREU,2025-04-07 +"Rockwell Automation, Inc",ROKL,2025-04-18 +"JUMPtec GmbH",JUMP,2025-04-22 +"Fsas Technologies Inc.",FSAS,2025-04-30 +"JP Morgan Chase N.A.",JPMC,2025-05-30 +"Roku, Inc.",ROKU,2025-07-15 +"UltraRISC Technology (Shanghai) Co., Ltd",ULRV,2025-09-15 +"SYNCS / Aviot Systems Pte Ltd",SYNC,2025-10-21 +"Advantech Co., Ltd.",AHCL,2025-10-23 +"Picoheart (SG) Pte. Ltd.",PICO,2025-10-30 +"Kontron France",KOMF,2025-12-09 +"Ubiquiti Inc.",UBTI,2025-12-10 +"KAYA N CO., LTD.",KAYA,2026-01-06 +Mesiontech,MITH,2026-01-30 +"Nexthop Systems Inc.",NXHP,2026-02-23 +"Megapolis-Telecom Region LLC",MPTR,2026-02-23 +"Nanjing Tianyihexin Electronics Ltd",TYHX,2026-02-27 +"Theo End (Shenzhen) Computing Technology Co., Ltd.",LECA,2026-02-27 +SIPEARL,SIPL,2026-04-08 \ No newline at end of file diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index fbc3faa600c75..6eaec69dc3202 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -2465,12 +2465,6 @@ C828E5 (base 16) Cisco Systems, Inc San Jose CA 94568 US -B0-FC-88 (hex) Sagemcom Broadband SAS -B0FC88 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 34-87-3D (hex) Quectel Wireless Solutions Co.,Ltd. 34873D (base 16) Quectel Wireless Solutions Co.,Ltd. RM501,Building 13,No.99 TianZhou Road,Xuhui District,Shanghai,China @@ -4451,12 +4445,6 @@ A45FB9 (base 16) DreamBig Semiconductor, Inc. San Jose CA 95134 US -34-5D-9E (hex) Sagemcom Broadband SAS -345D9E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-0F-32 (hex) Lootom Telcovideo Network (Wuxi) Co Ltd 000F32 (base 16) Lootom Telcovideo Network (Wuxi) Co Ltd 5F, 9Building, @@ -4613,12 +4601,6 @@ B03CDC (base 16) Intel Corporate Shenzhen Guangdong 518000 CN -7C-16-89 (hex) Sagemcom Broadband SAS -7C1689 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 5C-83-CD (hex) New platforms 5C83CD (base 16) New platforms Warshavskoe shosse, 35 bld 1 @@ -5399,12 +5381,6 @@ FC4265 (base 16) Zhejiang Tmall Technology Co., Ltd. London UK N7 9AH GB -44-D4-54 (hex) Sagemcom Broadband SAS -44D454 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-87-C6 (hex) Cisco Systems, Inc 6887C6 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -5609,12 +5585,6 @@ FC58DF (base 16) Interphone Service Anyang-si Gyeonggi-do 14057 KR -38-A6-59 (hex) Sagemcom Broadband SAS -38A659 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 78-13-05 (hex) IEEE Registration Authority 781305 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -5981,12 +5951,6 @@ E44E2D (base 16) Cisco Systems, Inc San Jose CA 94568 US -98-42-65 (hex) Sagemcom Broadband SAS -984265 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-21-5C (hex) Intel Corporate DC215C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -6323,12 +6287,6 @@ ECC5D2 (base 16) Huawei Device Co., Ltd. Beijing Haidian District 100085 CN -84-1E-A3 (hex) Sagemcom Broadband SAS -841EA3 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 8C-2A-8E (hex) DongGuan Ramaxel Memory Technology 8C2A8E (base 16) DongGuan Ramaxel Memory Technology No.32, Industrial East Road,Innovation Park, High-tech Industrial Development Zone, Songshan Lake, Dongguan City, Guangdong Province,China @@ -6749,12 +6707,6 @@ F4B301 (base 16) Intel Corporate Kulim Kedah 09000 MY -E8-D2-FF (hex) Sagemcom Broadband SAS -E8D2FF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 0C-96-CD (hex) MERCURY CORPORATION 0C96CD (base 16) MERCURY CORPORATION 90, Gajaeul-ro, Seo-gu @@ -6863,12 +6815,6 @@ C4D438 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Tokyo Japan 141-8610 JP -44-D4-53 (hex) Sagemcom Broadband SAS -44D453 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-A1-20 (hex) Nokia DCA120 (base 16) Nokia 600 March Road @@ -7043,12 +6989,6 @@ B456E3 (base 16) Apple, Inc. Hong Kong KOWLOON 999077 HK -B0-BB-E5 (hex) Sagemcom Broadband SAS -B0BBE5 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 1C-9F-4E (hex) COOSEA GROUP (HK) COMPANY LIMITED 1C9F4E (base 16) COOSEA GROUP (HK) COMPANY LIMITED UNIT 5-6 16/F MULTIFIELD PLAZA 3-7A PRAT AVENUE TSIMSHATSUI @@ -8837,12 +8777,6 @@ E826B6 (base 16) Companies House to GlucoRx Technologies Ltd. Suzhou Jiangsu 215412 CN -30-93-BC (hex) Sagemcom Broadband SAS -3093BC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F4-FE-FB (hex) Samsung Electronics Co.,Ltd F4FEFB (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -9239,12 +9173,6 @@ CCEF03 (base 16) Hunan Keyshare Communication Technology Co., Ltd. Changsha Hunan 410205 CN -EC-BE-DD (hex) Sagemcom Broadband SAS -ECBEDD (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 88-57-1D (hex) Seongji Industry Company 88571D (base 16) Seongji Industry Company 54-33, Dongtanhana 1-gil @@ -9515,12 +9443,6 @@ CCCD64 (base 16) SM-Electronic GmbH KYOTO KYOTO 601-8501 JP -F8-08-4F (hex) Sagemcom Broadband SAS -F8084F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 18-02-AE (hex) vivo Mobile Communication Co., Ltd. 1802AE (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road @@ -10037,12 +9959,6 @@ C82C2B (base 16) IEEE Registration Authority Piscataway NJ 08554 US -80-20-DA (hex) Sagemcom Broadband SAS -8020DA (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-20-B0 (hex) Mist Systems, Inc. D420B0 (base 16) Mist Systems, Inc. 1601 South De Anza Blvd, Suite 248 @@ -10568,12 +10484,6 @@ A8E2C1 (base 16) Texas Instruments DONG GUAN GUANG DONG 523860 CN -84-A0-6E (hex) Sagemcom Broadband SAS -84A06E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 88-4A-18 (hex) Opulinks 884A18 (base 16) Opulinks F 28, No.328, Huashan Rd @@ -11156,12 +11066,6 @@ E0795E (base 16) Wuxi Xiaohu Technology Co.,Ltd. Taipei Taiwan 112 TW -E8-AD-A6 (hex) Sagemcom Broadband SAS -E8ADA6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D0-B6-0A (hex) Xingluo Technology Company Limited D0B60A (base 16) Xingluo Technology Company Limited 28F, Building A, Aerospace Science And Technology Square, Nanshan District @@ -11372,12 +11276,6 @@ F8D9B8 (base 16) Open Mesh, Inc. Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -D8-7D-7F (hex) Sagemcom Broadband SAS -D87D7F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - EC-C4-0D (hex) Nintendo Co.,Ltd ECC40D (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU @@ -12194,12 +12092,6 @@ F85C4D (base 16) Nokia Westford MA 01886-4113 US -38-35-FB (hex) Sagemcom Broadband SAS -3835FB (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-01-AE (hex) Trex Enterprises 0001AE (base 16) Trex Enterprises 590 Lipoa Parkway @@ -40424,12 +40316,6 @@ D0460C (base 16) Dell Inc. Hezhou Guangxi 542800 CN -38-E1-F4 (hex) Sagemcom Broadband SAS -38E1F4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-53-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD 6453E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -40742,12 +40628,6 @@ DC54AD (base 16) Hangzhou RunZhou Fiber Technologies Co.,Ltd Hangzhou Zhejiang 310030 CN -B0-92-4A (hex) Sagemcom Broadband SAS -B0924A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-DE-24 (hex) Samsung Electronics Co.,Ltd 40DE24 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -44354,11 +44234,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 @@ -44372,11 +44258,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. @@ -44390,24 +44276,12 @@ 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. -F040EC (base 16) RainX PTE. LTD. - 83B Tanjong Pagar Road - Singapore 088504 - SG - C8-77-F3 (hex) VusionGroup C877F3 (base 16) VusionGroup Kalsdorfer Straße 12 @@ -44450,6 +44324,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 @@ -44468,56 +44354,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 @@ -44636,41 +44522,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. @@ -44690,6 +44552,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 @@ -44756,12 +44636,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 @@ -44810,11 +44684,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 @@ -44846,11 +44723,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 @@ -44858,9 +44741,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 @@ -44915,12 +44795,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 @@ -44933,36 +44807,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 @@ -44975,28 +44873,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 @@ -45011,6 +44897,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 @@ -45029,40 +44939,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 @@ -45083,29 +44969,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 @@ -45119,12 +44999,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 @@ -45161,11 +45035,11 @@ ECBB78 (base 16) Cisco Systems, Inc San Jose CA 95126 US -10-2B-AA (hex) Sagemcom Broadband SAS -102BAA (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - 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 @@ -45206,18 +45080,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 @@ -45230,11 +45092,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 @@ -45272,12 +45134,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 @@ -45296,34 +45152,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 @@ -45344,11 +45188,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. @@ -45356,12 +45206,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 @@ -45374,6 +45218,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 @@ -45392,6 +45254,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 @@ -45404,17 +45278,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 @@ -45428,12 +45296,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 @@ -45464,23 +45326,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. @@ -45494,24 +45350,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 @@ -45524,11 +45374,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 @@ -45536,30 +45386,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 @@ -45572,6 +45446,12 @@ 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 @@ -45584,22 +45464,16 @@ B0A604 (base 16) Espressif Inc. 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. @@ -45626,6 +45500,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. @@ -45644,23 +45530,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. @@ -45686,12 +45572,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 @@ -45704,18 +45584,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 @@ -45728,24 +45596,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 @@ -45770,12 +45638,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 @@ -45788,29 +45668,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 @@ -45824,17 +45698,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 @@ -45848,64 +45722,40 @@ D494A9 (base 16) Intel Corporate Kulim Kedah 09000 MY -E4-65-66 (hex) Maple IoT Solutions LLC -E46566 (base 16) Maple IoT Solutions LLC - N8408 MUIRFIELD WAY - MENASHA WI 54952 - US - -80-B2-69 (hex) Subtle Computing -80B269 (base 16) Subtle Computing - 855 El Camino Real, Suite 13A - 230 - 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 - 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 -20-CB-CC (hex) GridVisibility, inc. -20CBCC (base 16) GridVisibility, inc. - 1502 Meeker Dr - Longmont CO 80504 - US - -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 - 5453 Great America Parkway - Santa Clara CA 95054 +E4-65-66 (hex) Maple IoT Solutions LLC +E46566 (base 16) Maple IoT Solutions LLC + N8408 MUIRFIELD WAY + MENASHA WI 54952 US -74-33-36 (hex) IEEE Registration Authority -743336 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +80-B2-69 (hex) Subtle Computing +80B269 (base 16) Subtle Computing + 855 El Camino Real, Suite 13A - 230 + Palo Alto CA 94301 US 40-08-77 (hex) Xiaomi Communications Co Ltd @@ -45914,16 +45764,16 @@ E0FA5B (base 16) Arista Networks Beijing Haidian District 100085 CN -7C-D4-A8 (hex) Sagemcom Broadband SAS -7CD4A8 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +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 -90-31-96 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. -903196 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. - Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - SHENZHEN Guangdong Province 518052 +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 CC-2D-D2 (hex) Ruckus Wireless @@ -45938,29 +45788,11 @@ B0D7DE (base 16) Hangzhou Linovision Co., Ltd. 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 +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 @@ -45968,17 +45800,17 @@ B85384 (base 16) Xiaomi Communications Co Ltd 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 +E0-FA-5B (hex) Arista Networks +E0FA5B (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US -98-42-AB (hex) GN Hearing A/S -9842AB (base 16) GN Hearing A/S - Lautrupbjerg 7 - Ballerup 2750 - DK +74-33-36 (hex) IEEE Registration Authority +743336 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US 9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. 9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. @@ -45986,16 +45818,22 @@ B85384 (base 16) Xiaomi Communications Co Ltd 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 +90-31-96 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. +903196 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. + Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + SHENZHEN Guangdong Province 518052 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 +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 + +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 4C-3C-8F (hex) Shenzhen Jingxun Technology Co., Ltd. @@ -46016,17 +45854,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 @@ -46040,16 +45872,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 @@ -46070,30 +45902,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 @@ -46106,11 +45920,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 @@ -46142,12 +45962,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 @@ -46160,12 +46010,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 @@ -46178,11 +46022,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 @@ -46220,17 +46064,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 @@ -46244,17 +46088,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 @@ -46268,54 +46112,30 @@ 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 Santa Clara CA 95054 US -B0-43-5D (hex) MechoShade -B0435D (base 16) MechoShade - 1497 Poinsettia Ave. - 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., @@ -46334,34 +46154,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 @@ -46376,12 +46214,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 @@ -46400,6 +46232,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 @@ -46430,29 +46268,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 @@ -46484,6 +46304,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 @@ -46508,6 +46340,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 @@ -46520,24 +46388,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 @@ -46574,24 +46424,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 @@ -46604,17 +46436,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. @@ -46634,6 +46460,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 @@ -46646,47 +46502,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 @@ -46694,6 +46526,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 @@ -46712,10 +46550,46 @@ 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 +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 @@ -46730,6 +46604,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 @@ -46748,14 +46628,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 @@ -46790,48 +46676,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 @@ -46856,17 +46700,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 @@ -46898,10 +46742,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 @@ -46910,11 +46754,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 @@ -46925,71 +46787,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 -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 - -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 - 19F, Kowa Kawasaki West Exit Building66-2 Horikawa-cho, Saiwai-ku - 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 - -B0-2B-64 (hex) Cisco Systems, Inc -B02B64 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +44-9F-79 (hex) onsemi +449F79 (base 16) onsemi + 5701 N Pima Rd + Scottsdale AZ 85250 US -10-E6-76 (hex) Cisco Systems, Inc -10E676 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +2C-DE-F5 (hex) TVS REGZA Corporation +2CDEF5 (base 16) TVS REGZA Corporation + 19F, Kowa Kawasaki West Exit Building66-2 Horikawa-cho, Saiwai-ku + Kawasaki-shi Kanagawa 2120013 + JP -FC-50-0C (hex) Sitehop Ltd -FC500C (base 16) Sitehop Ltd - 9 South Street - Sheffield South Yorkshire S2 5QX - GB +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. @@ -47003,22 +46829,16 @@ FC500C (base 16) Sitehop Ltd 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 - 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 +50-EE-87 (hex) HPRO +50EE87 (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 + US -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 +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 @@ -47027,6 +46847,12 @@ AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd 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 @@ -47039,17 +46865,41 @@ AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd 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 + 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 +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 90-0E-84 (hex) eero inc. 900E84 (base 16) eero inc. @@ -47063,11 +46913,11 @@ 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 +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 E0-D3-8E (hex) Chipsea Technologies (Shenzhen) Crop. E0D38E (base 16) Chipsea Technologies (Shenzhen) Crop. @@ -47087,11 +46937,23 @@ 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 +A8-D3-F7 (hex) Arcadyan Corporation +A8D3F7 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City Hsinchu 30071 + TW + +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 + +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 78-0A-57 (hex) Shanghai Lightningsemi Technology Co.,Ltd. 780A57 (base 16) Shanghai Lightningsemi Technology Co.,Ltd. @@ -47099,10 +46961,10 @@ D44A85 (base 16) Silicon Laboratories 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 +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 5C-BA-75 (hex) Quectel Wireless Solutions Co., Ltd. @@ -47117,17 +46979,17 @@ D096EA (base 16) vivo Mobile Communication Co., Ltd. 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 +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 -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 +B8-38-65 (hex) Hewlett Packard Enterprise +B83865 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US 08-D0-1E (hex) Juniper Networks 08D01E (base 16) Juniper Networks @@ -47135,22 +46997,16 @@ D096EA (base 16) vivo Mobile Communication Co., Ltd. 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 +30-15-77 (hex) Zyxel Communications Corporation +301577 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 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 +50-5E-3A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +505E3A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN 8C-53-87 (hex) Huzhou Luxshare Precision Industry Co.LTD @@ -47165,6 +47021,690 @@ B83865 (base 16) Hewlett Packard Enterprise BESANCON 25000 FR +90-06-DB (hex) HUAWEI TECHNOLOGIES CO.,LTD +9006DB (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +78-78-26 (hex) HUAWEI TECHNOLOGIES CO.,LTD +787826 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +5C-61-17 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C6117 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +34-8A-3B (hex) Huawei Device Co., Ltd. +348A3B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +20-E5-25 (hex) Huawei Device Co., Ltd. +20E525 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +F8-BA-98 (hex) HUAQIN TECHNOLOGY CO., LTD +F8BA98 (base 16) HUAQIN TECHNOLOGY CO., LTD + No.699, Lvke Road + Pudong New Area Shanghai 200000 + CN + +AC-53-22 (hex) Samsung Electronics Co.,Ltd +AC5322 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +88-C3-44 (hex) Google, Inc. +88C344 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +B0-43-5D (hex) MechoShade +B0435D (base 16) MechoShade + 4203 35th Street + Long Island City NY 11101 + US + +AC-AD-EF (hex) Wanan Hongsheng Electronic Co.Ltd +ACADEF (base 16) Wanan Hongsheng Electronic Co.Ltd +  1st section of industrial pack,Wan'An County,Ji'An City + Ji'An City ,jiangxi province 343800 + CN + +D4-2B-F0 (hex) Tiinlab Corporation +D42BF0 (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 + +B8-DC-28 (hex) Extreme Networks Headquarters +B8DC28 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +1C-2D-60 (hex) Extreme Networks Headquarters +1C2D60 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +84-EB-0C (hex) Mellanox Technologies, Inc. +84EB0C (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +54-14-E9 (hex) AltoBeam Inc. +5414E9 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +F4-F9-1E (hex) INGRAM MICRO SERVICES +F4F91E (base 16) INGRAM MICRO SERVICES + 100 CHEMIN DE BAILLOT + MONTAUBAN 82000 + FR + +D8-60-C5 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +D860C5 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +5C-F9-2B (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +5CF92B (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 + +14-38-FA (hex) Motorola Mobility LLC, a Lenovo Company +1438FA (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +F0-40-EC (hex) LOOPDESIGNLAB PTE. LTD +F040EC (base 16) LOOPDESIGNLAB PTE. LTD + 83B Tanjong Pagar Road + Singapore 088504 + SG + +9C-9D-07 (hex) FN-LINK TECHNOLOGY Ltd. +9C9D07 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + +D8-BF-42 (hex) Intel Corporate +D8BF42 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +7C-A8-5D (hex) RONGCHEENG GOER TECHNOLOGY CO.,LTD. +7CA85D (base 16) RONGCHEENG GOER TECHNOLOGY CO.,LTD. + Rongcheng Goer Technology Co., Ltd., No. 699, Jiangjun South Road, Rongcheng City, Weihai City, Shandong Province + Weihai Shandong 264300 + CN + +C8-41-2E (hex) AM Telecom co., Ltd. +C8412E (base 16) AM Telecom co., Ltd. + 20, Gwacheon-daero 7ga-gil + Gwacheon-si Gyeonggi-do 13840 + KR + +4C-54-8B (hex) Cerebras System Inc. +4C548B (base 16) Cerebras System Inc. + 1237 E Arques Ave + Sunnyvale CA 94085-4701 + US + +80-A6-3C (hex) Amazon Technologies Inc. +80A63C (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +A4-0F-25 (hex) eero inc. +A40F25 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +78-31-C4 (hex) Panascais ehf. +7831C4 (base 16) Panascais ehf. + Suðurlandsbraut 4 + Reykjavík 108 + IS + +4C-6C-A1 (hex) Chipsea Technologies (Shenzhen) Crop. +4C6CA1 (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 + +80-45-6B (hex) Espressif Inc. +80456B (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +90-84-8E (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +90848E (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN + +8C-18-01 (hex) zte corporation +8C1801 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +D0-84-5D (hex) B&C Transit, Inc. +D0845D (base 16) B&C Transit, Inc. + 701 Seco Road + Monroeville PA 15146 + US + +40-ED-7B (hex) Zscaler +40ED7B (base 16) Zscaler + 120 Holger Way + San Jose CA 95134 + US + +5C-B2-DF (hex) Shenzhen Powerleader Storage Technology Co., Ltd. +5CB2DF (base 16) Shenzhen Powerleader Storage Technology Co., Ltd. + 2E, Unit 9, Mingjia Fuju, West of Yiyuan Road, Nantou Subdistrict, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 + CN + +44-45-20 (hex) EM Microelectronic +444520 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +88-DB-08 (hex) EM Microelectronic +88DB08 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +FC-FD-71 (hex) AltoBeam Inc. +FCFD71 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +38-2C-DB (hex) Arista Networks +382CDB (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +BC-93-2A (hex) Silicon Laboratories +BC932A (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +AC-A8-99 (hex) Texas Instruments +ACA899 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +38-4E-56 (hex) Texas Instruments +384E56 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +B8-DA-5E (hex) Texas Instruments +B8DA5E (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +F0-B1-63 (hex) Texas Instruments +F0B163 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +B4-DF-09 (hex) FLUX:: +B4DF09 (base 16) FLUX:: + 8500 Balboa Boulevard + Northridge CA 91325 + US + +5C-51-DF (hex) eero inc. +5C51DF (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +5C-63-B0 (hex) Fortinet, Inc. +5C63B0 (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + +88-F3-D5 (hex) Zyxel Communications Corporation +88F3D5 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +A0-1B-04 (hex) Hefei Huanxin Microelectronics Technology Co., Ltd. +A01B04 (base 16) Hefei Huanxin Microelectronics Technology Co., Ltd. + 22nd Floor, Building A1, China Vision, No. 99 Longchuan Road, Baohe District, Hefei City, Anhui Province, China + Hefei Anhui 230001 + CN + +74-A9-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD +74A981 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +40-A6-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +40A654 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +90-BA-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD +90BA09 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E8-EA-34 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E8EA34 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +78-34-B4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7834B4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +A0-C6-A5 (hex) Lierda Science & Technology Group Co., Ltd +A0C6A5 (base 16) Lierda Science & Technology Group Co., Ltd + Lierda Science Park,No.1326 WenyiWestRoad + Hangzhou ZheJiang 311121 + CN + +20-A3-66 (hex) vivo Mobile Communication Co., Ltd. +20A366 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +68-FD-E8 (hex) Ruckus Wireless +68FDE8 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +7C-0C-5F (hex) Espressif Inc. +7C0C5F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +7C-E8-B1 (hex) Espressif Inc. +7CE8B1 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +FC-59-7A (hex) Zebra Technologies Inc. +FC597A (base 16) Zebra Technologies Inc. + ONE ZEBRA PLAZA + HOLTSVILLE NY 11742 + US + +F4-00-A2 (hex) Samsung Electronics Co.,Ltd +F400A2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +3C-29-83 (hex) Samsung Electronics Co.,Ltd +3C2983 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +1C-42-C2 (hex) Huawei Device Co., Ltd. +1C42C2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +F4-01-CC (hex) Silicon Laboratories +F401CC (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +D0-D4-FB (hex) Home Control Singapore Pte Ltd +D0D4FB (base 16) Home Control Singapore Pte Ltd + 1 Paya Lebar Link, PLQ 1, #04-01(Office 448) + Singapore 408533 + SG + +48-EA-A9 (hex) ShenZhen C&D Electronics CO.Ltd. +48EAA9 (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 + +50-DE-92 (hex) shenzhen worldelite electronics co., LTD +50DE92 (base 16) shenzhen worldelite electronics co., LTD + Office 5 F, Xiang Yu Industrial Park, Longsheng Road, Longgang Dist + Shenzhen Guangdong 51800 + CN + +34-F0-84 (hex) Samsung Electronics Co.,Ltd +34F084 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +FC-6E-83 (hex) Samsung Electronics Co.,Ltd +FC6E83 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +BC-BF-2E (hex) ASUSTek COMPUTER INC. +BCBF2E (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW + +F8-C9-D6 (hex) IEEE Registration Authority +F8C9D6 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +0C-39-3D (hex) eero inc. +0C393D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B0-BB-E5 (hex) Sagemcom Broadband SAS +B0BBE5 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +44-D4-53 (hex) Sagemcom Broadband SAS +44D453 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +E8-D2-FF (hex) Sagemcom Broadband SAS +E8D2FF (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +84-1E-A3 (hex) Sagemcom Broadband SAS +841EA3 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +98-42-65 (hex) Sagemcom Broadband SAS +984265 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +38-A6-59 (hex) Sagemcom Broadband SAS +38A659 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +84-A0-6E (hex) Sagemcom Broadband SAS +84A06E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +80-20-DA (hex) Sagemcom Broadband SAS +8020DA (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +F8-08-4F (hex) Sagemcom Broadband SAS +F8084F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +EC-BE-DD (hex) Sagemcom Broadband SAS +ECBEDD (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +30-93-BC (hex) Sagemcom Broadband SAS +3093BC (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +B4-1C-AF (hex) UAB TELTONIKA NETWORKS +B41CAF (base 16) UAB TELTONIKA NETWORKS + K. Baršausko g. 66 + Kaunas Kauno m. sav. LT -51436 + LT + +8C-08-3C (hex) EM Microelectronic +8C083C (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +38-35-FB (hex) Sagemcom Broadband SAS +3835FB (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D8-7D-7F (hex) Sagemcom Broadband SAS +D87D7F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +E8-AD-A6 (hex) Sagemcom Broadband SAS +E8ADA6 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +44-D4-54 (hex) Sagemcom Broadband SAS +44D454 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +7C-16-89 (hex) Sagemcom Broadband SAS +7C1689 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +34-5D-9E (hex) Sagemcom Broadband SAS +345D9E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +34-28-44 (hex) Kyung In Electronics +342844 (base 16) Kyung In Electronics + #1411, Byucksan Digital Valley 2, 184, Gasan Digital2-ro, Geumcheon-gu + Seoul 08501 + KR + +44-49-C0 (hex) NVIDIA Corporation +4449C0 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + US + +D8-B1-DE (hex) Hewlett Packard Enterprise +D8B1DE (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +F8-75-28 (hex) IEEE Registration Authority +F87528 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +C0-8B-27 (hex) FN-LINK TECHNOLOGY Ltd. +C08B27 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + +DC-F1-44 (hex) Ocean Solution Technology +DCF144 (base 16) Ocean Solution Technology + 203-1 Arifukucho + Sasebo City NAGASAKI 859-3241 + JP + +F8-E7-3C (hex) Ufispace Co., LTD. +F8E73C (base 16) Ufispace Co., LTD. + 9F., No. 81 Jhongcheng Rd., Tucheng Dist., + New Taipei 23674 + TW + +7C-D4-A8 (hex) Sagemcom Broadband SAS +7CD4A8 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +48-AA-BB (hex) Sagemcom Broadband SAS +48AABB (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +24-4E-CD (hex) Sagemcom Broadband SAS +244ECD (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +38-E1-F4 (hex) Sagemcom Broadband SAS +38E1F4 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +B0-92-4A (hex) Sagemcom Broadband SAS +B0924A (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +10-2B-AA (hex) Sagemcom Broadband SAS +102BAA (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-FA-2B (hex) Sagemcom Broadband SAS +64FA2B (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +B4-3B-52 (hex) Sagemcom Broadband SAS +B43B52 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +B0-FC-88 (hex) Sagemcom Broadband SAS +B0FC88 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +F8-32-BA (hex) VusionGroup +F832BA (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +F8-D8-11 (hex) Quectel Wireless Solutions Co.,Ltd. +F8D811 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +38-39-04 (hex) ittim +383904 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN + +78-6B-A5 (hex) Changchun Jetty Automotive Technology Co., LTD +786BA5 (base 16) Changchun Jetty Automotive Technology Co., LTD + 1st Floor, No. 957 Shunda Road,High-tech Development Zone + Changchun Jilin 130012 + CN + +D0-13-C1 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +D013C1 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +10-B0-6E (hex) Shenzhen Phaten Tech. LTD +10B06E (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 + CN + +20-93-95 (hex) nVent +209395 (base 16) nVent + 1665 Utica Avenue, Suite 700 + St. Louis Park MN 55416 + US + 00-01-30 (hex) Extreme Networks Headquarters 000130 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -49601,12 +50141,6 @@ A46C24 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -08-7B-12 (hex) Sagemcom Broadband SAS -087B12 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 50-D4-5C (hex) Amazon Technologies Inc. 50D45C (base 16) Amazon Technologies Inc. P.O Box 8102 @@ -49643,12 +50177,6 @@ D46137 (base 16) IEEE Registration Authority North Point 00000 HK -D0-CF-0E (hex) Sagemcom Broadband SAS -D0CF0E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-DE-CC (hex) HYBE Co.,LTD 80DECC (base 16) HYBE Co.,LTD 42, Hangang-daero @@ -49661,12 +50189,6 @@ A0ED6D (base 16) Ubee Interactive Co., Limited North Point 00000 HK -C4-EB-42 (hex) Sagemcom Broadband SAS -C4EB42 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F8-34-5A (hex) Hitron Technologies. Inc F8345A (base 16) Hitron Technologies. Inc No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C @@ -49907,18 +50429,6 @@ A49DDD (base 16) Samsung Electronics Co.,Ltd shenzhen guangdong 518057 CN -88-0F-A2 (hex) Sagemcom Broadband SAS -880FA2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -DC-97-E6 (hex) Sagemcom Broadband SAS -DC97E6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-92-B9 (hex) ORION NOVA, S.L. D492B9 (base 16) ORION NOVA, S.L. CALLE LARRAMENDI 12C 6A @@ -50351,12 +50861,6 @@ B0DCEF (base 16) Intel Corporate Kulim Kedah 09000 MY -78-C2-13 (hex) Sagemcom Broadband SAS -78C213 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-22-D8 (hex) Espressif Inc. 4022D8 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -51239,12 +51743,6 @@ DC6B1B (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -D0-6D-C9 (hex) Sagemcom Broadband SAS -D06DC9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-CA-20 (hex) Shanghai SIMCOM Ltd. 98CA20 (base 16) Shanghai SIMCOM Ltd. Building A, SIM Technology Building, No.633, Jinzhong Road, Changning District @@ -51281,12 +51779,6 @@ D850A1 (base 16) Hunan Danuo Technology Co.,LTD Cupertino CA 95014 US -D8-33-B7 (hex) Sagemcom Broadband SAS -D833B7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-FB-8E (hex) Apple, Inc. D4FB8E (base 16) Apple, Inc. 1 Infinite Loop @@ -51599,12 +52091,6 @@ C889F3 (base 16) Apple, Inc. Austin TX 78701 US -4C-19-5D (hex) Sagemcom Broadband SAS -4C195D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 9C-74-03 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 9C7403 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, @@ -52109,12 +52595,6 @@ C4BDE5 (base 16) Intel Corporate Kulim Kedah 09000 MY -34-53-D2 (hex) Sagemcom Broadband SAS -3453D2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 34-29-EF (hex) Qingdao Haier Technology Co.,Ltd 3429EF (base 16) Qingdao Haier Technology Co.,Ltd A01, No.1, Haier Road, Laoshan District, @@ -52211,12 +52691,6 @@ E42805 (base 16) Pivotal Optics Toronto ON M3C 3E5 CA -68-15-D3 (hex) Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. -6815D3 (base 16) Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. - ul. Traugutta 7 - Mielec 39-300 - PL - 00-80-E7 (hex) Leonardo UK Ltd 0080E7 (base 16) Leonardo UK Ltd Christopher Martin Road @@ -53015,12 +53489,6 @@ D48DD9 (base 16) Meld Technology, Inc Sunnyvale CA 94085 US -58-2F-F7 (hex) Sagemcom Broadband SAS -582FF7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - AC-E7-7B (hex) Sichuan Tianyi Comheart Telecom Co.,LTD ACE77B (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, @@ -54629,12 +55097,6 @@ FC6DD1 (base 16) APRESIA Systems, Ltd. Wuhan Hubei 430074 CN -10-D7-B0 (hex) Sagemcom Broadband SAS -10D7B0 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 44-59-43 (hex) zte corporation 445943 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -55517,12 +55979,6 @@ C489ED (base 16) Solid Optics EU N.V. Riga Riga LV1009 LV -10-06-45 (hex) Sagemcom Broadband SAS -100645 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - A0-27-B6 (hex) Samsung Electronics Co.,Ltd A027B6 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -56597,12 +57053,6 @@ E83F67 (base 16) Huawei Device Co., Ltd. Chongqing Chongqing 401332 CN -34-49-5B (hex) Sagemcom Broadband SAS -34495B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-16-09 (hex) Sleep Number 801609 (base 16) Sleep Number 1001 Third Avenue South @@ -57125,12 +57575,6 @@ BCC31B (base 16) Kygo Life A Bayan Lepas Penang 11900 MY -78-65-59 (hex) Sagemcom Broadband SAS -786559 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 50-D2-F5 (hex) Beijing Xiaomi Mobile Software Co., Ltd 50D2F5 (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -57797,12 +58241,6 @@ DC7137 (base 16) zte corporation Moscow 115324 RU -34-DB-9C (hex) Sagemcom Broadband SAS -34DB9C (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 74-40-BE (hex) LG Innotek 7440BE (base 16) LG Innotek 26, Hanamsandan 5beon-ro @@ -58553,12 +58991,6 @@ B831B5 (base 16) Microsoft Corporation Suwon Gyeonggi-Do 16677 KR -38-B4-D3 (hex) BSH Hausgeraete GmbH -38B4D3 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - C8-47-82 (hex) Areson Technology Corp. C84782 (base 16) Areson Technology Corp. 11F., No. 646, Sec. 5, Chongxin Rd., Sanchong District @@ -59363,12 +59795,6 @@ D0D783 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -AC-3B-77 (hex) Sagemcom Broadband SAS -AC3B77 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - FC-E6-6A (hex) Industrial Software Co FCE66A (base 16) Industrial Software Co 85, Aleksandyr Malinov Blvd. Office 6 @@ -60371,12 +60797,6 @@ D88F76 (base 16) Apple, Inc. Chicago IL 60654 US -F4-49-EF (hex) EMSTONE -F449EF (base 16) EMSTONE - #310, Ace Techno Tower 3rd, 38 Digital-ro-29-gil - Guro-Gu Seoul 08381 - KR - 54-DF-24 (hex) Fiberhome Telecommunication Technologies Co.,LTD 54DF24 (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road @@ -82151,12 +82571,6 @@ CC0080 (base 16) BETTINI SRL Taichung Taiwan 403, R.O.C. TW -00-03-74 (hex) Control Microsystems -000374 (base 16) Control Microsystems - 48 Steacie Drive - Ottawa Ontario K2K 2A9 - CA - 00-03-76 (hex) Graphtec Technology, Inc. 000376 (base 16) Graphtec Technology, Inc. 45 Parker, Suite A @@ -82505,12 +82919,6 @@ CC0080 (base 16) BETTINI SRL Lexington MA 02421 US -00-02-31 (hex) Ingersoll-Rand -000231 (base 16) Ingersoll-Rand - 1467 Route 31 South - Annandale NJ 08801 - US - 00-02-34 (hex) Imperial Technology, Inc. 000234 (base 16) Imperial Technology, Inc. 2305 Utah Avenue @@ -87086,12 +87494,6 @@ A0E025 (base 16) Provision-ISR Kfar Saba 4464310 IL -9C-24-72 (hex) Sagemcom Broadband SAS -9C2472 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-85-44 (hex) Intelbras 808544 (base 16) Intelbras BR 101, km 210, S/N° @@ -87983,18 +88385,6 @@ B0B867 (base 16) Hewlett Packard Enterprise Roseville CA 95747 US -DC-92-72 (hex) Sagemcom Broadband SAS -DC9272 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -18-0C-7A (hex) Sagemcom Broadband SAS -180C7A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-FD-45 (hex) Hewlett Packard Enterprise 00FD45 (base 16) Hewlett Packard Enterprise 8000 Foothills Blvd. @@ -90227,18 +90617,6 @@ A0EEEE (base 16) CIG SHANGHAI CO LTD Marin-Epagnier Neuchatel 2074 CH -2C-FB-0F (hex) Sagemcom Broadband SAS -2CFB0F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -BC-D5-ED (hex) Sagemcom Broadband SAS -BCD5ED (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 94-E7-F3 (hex) HUAWEI TECHNOLOGIES CO.,LTD 94E7F3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -90605,12 +90983,6 @@ D4808B (base 16) Seiko Epson Corporation Matsumoto-shi Nagano-ken 399-8702 JP -8C-9A-8F (hex) Sagemcom Broadband SAS -8C9A8F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 10-66-6A (hex) Zabbly 10666A (base 16) Zabbly 24 Saxby S @@ -90980,6 +91352,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 @@ -90998,24 +91382,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 @@ -91046,78 +91418,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 @@ -91154,18 +91508,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 @@ -91226,6 +91574,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 @@ -91250,12 +91622,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 @@ -91298,12 +91664,6 @@ D83EEF (base 16) COOSEA GROUP (HK) COMPANY LIMITED 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 @@ -91322,11 +91682,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 @@ -91358,6 +91718,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 @@ -91370,42 +91736,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 @@ -91448,30 +91790,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 @@ -91502,22 +91844,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 @@ -91532,17 +91886,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 @@ -91586,6 +91940,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 @@ -91598,6 +91958,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 @@ -91616,10 +91982,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 @@ -91640,10 +92006,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 @@ -91658,6 +92024,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 @@ -91682,12 +92054,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, @@ -91706,6 +92072,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 @@ -91724,18 +92114,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 @@ -91748,24 +92126,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 @@ -91778,6 +92156,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 @@ -91808,23 +92192,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. @@ -91832,18 +92210,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. @@ -91856,24 +92228,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 @@ -91886,12 +92240,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 @@ -91910,18 +92288,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 @@ -91940,22 +92306,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 @@ -91970,12 +92330,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 @@ -91988,18 +92366,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° @@ -92018,35 +92396,23 @@ B02EBA (base 16) Earda Technologies co Ltd 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 @@ -92060,22 +92426,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. @@ -92102,32 +92480,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 @@ -92156,15 +92531,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 @@ -92183,14 +92549,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 @@ -92219,12 +92591,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 @@ -92249,23 +92615,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 @@ -92285,6 +92639,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 @@ -92303,12 +92669,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 @@ -92333,6 +92693,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 @@ -92357,36 +92723,24 @@ 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 Piscataway NJ 08554 US -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 @@ -92483,17 +92837,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 @@ -92507,18 +92873,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 @@ -92543,11 +92909,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 @@ -92597,22 +92963,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. @@ -92633,18 +92993,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 @@ -92657,42 +93005,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 @@ -92711,6 +93071,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 @@ -92747,18 +93125,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 @@ -92771,14 +93137,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 @@ -92789,14 +93155,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 @@ -92813,30 +93173,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, @@ -92861,14 +93197,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. @@ -92882,6 +93215,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. @@ -92894,17 +93251,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. @@ -92918,23 +93272,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. @@ -92942,47 +93290,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 @@ -93002,6 +93314,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 @@ -93020,6 +93338,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 @@ -93038,16 +93362,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. @@ -93056,11 +93410,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 @@ -93068,12 +93422,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 @@ -93086,17 +93434,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 @@ -93104,12 +93446,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 @@ -93122,12 +93494,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 @@ -93140,18 +93506,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 @@ -93182,18 +93536,18 @@ DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. Kulim Kedah 09000 MY +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 @@ -93218,18 +93572,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, @@ -93242,34 +93584,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 @@ -93278,6 +93614,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 @@ -93296,29 +93644,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. @@ -93332,22 +93680,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. @@ -93356,17 +93692,17 @@ 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 -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 +F8-1E-49 (hex) Apple, Inc. +F81E49 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US BC-74-EA (hex) Apple, Inc. BC74EA (base 16) Apple, Inc. @@ -93380,6 +93716,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 @@ -93392,18 +93734,48 @@ 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 -F8-1E-49 (hex) Apple, Inc. -F81E49 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - C8-26-91 (hex) Arista Networks, Inc. C82691 (base 16) Arista Networks, Inc. 5453 Great America Parkway @@ -93416,6 +93788,12 @@ C82691 (base 16) Arista Networks, Inc. Huntsville 35806 US +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 + 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 @@ -93428,6 +93806,12 @@ C82691 (base 16) Arista Networks, Inc. Shanghai Shanghai 201203 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 + FC-E4-21 (hex) zhejiang Dusun Electron Co.,Ltd FCE421 (base 16) zhejiang Dusun Electron Co.,Ltd NO.640 FengQing str., @@ -93440,42 +93824,12 @@ FCE421 (base 16) zhejiang Dusun Electron Co.,Ltd 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 - -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 - DC-73-FC (hex) Mellanox Technologies, Inc. DC73FC (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -93488,6 +93842,12 @@ CC3089 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +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 + 84-9C-A6 (hex) Arcadyan Corporation 849CA6 (base 16) Arcadyan Corporation 4F, No. 9, Park Avenue II , @@ -93530,11 +93890,23 @@ ECB5AF (base 16) RayService a.s. 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 +78-E0-C5 (hex) Samsung Electronics Co.,Ltd +78E0C5 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-EE-17 (hex) GYGES LABS PTE.LTD +8CEE17 (base 16) GYGES LABS PTE.LTD + 160 Robinson Road, #25-09, SBF Center, Singapore 068914 + SINGAPORE 068914 + SG + +2C-B4-71 (hex) Tuya Smart Inc. +2CB471 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US 00-76-B6 (hex) Ford Motor Company 0076B6 (base 16) Ford Motor Company @@ -93542,274 +93914,886 @@ ECB5AF (base 16) RayService a.s. Dearborn MI 48124 US +80-1D-0D (hex) IEEE Registration Authority +801D0D (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + 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 - Hangzhou Zhejiang 310052 +B0-3D-BF (hex) shenzhen ceita communications technology co.,ltd +B03DBF (base 16) shenzhen ceita communications technology co.,ltd + 4/F, 3/B, Shayi North Yongfa Science and Technology Park, Shajing Town, Bao'an District, Shenzhen. + shenzhen Select State 518104 CN -0C-8D-7A (hex) RADiflow -0C8D7A (base 16) RADiflow - HaBarzel St 38 - Tel-Aviv 6971054 - IL +C0-A3-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD +C0A36D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -FC-A9-F5 (hex) Xiaomi Communications Co Ltd -FCA9F5 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +34-10-D0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3410D0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -6C-03-70 (hex) Extreme Networks Headquarters -6C0370 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +CC-13-F3 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +CC13F3 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -78-96-A3 (hex) Extreme Networks Headquarters -7896A3 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +9C-7F-64 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +9C7F64 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -DC-E6-50 (hex) Extreme Networks Headquarters -DCE650 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +EC-A1-CC (hex) Cisco Systems, Inc +ECA1CC (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -24-1F-BD (hex) Extreme Networks Headquarters -241FBD (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +F4-B8-21 (hex) Cisco Systems, Inc +F4B821 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -E4-44-E5 (hex) Extreme Networks Headquarters -E444E5 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +C0-E5-79 (hex) Huawei Device Co., Ltd. +C0E579 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -40-88-2F (hex) Extreme Networks Headquarters -40882F (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +CC-81-30 (hex) Intelbras +CC8130 (base 16) Intelbras + BR 101, km 210, S/N° + São José Santa Catarina 88104800 + BR -40-18-B1 (hex) Extreme Networks Headquarters -4018B1 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +90-36-B2 (hex) TRATON AB +9036B2 (base 16) TRATON AB + Lärlingsvägen 3 + Södertälje 15165 + SE -20-6C-8A (hex) Extreme Networks Headquarters -206C8A (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +AC-30-19 (hex) Shenzhen Hailingwei Electronics Co., Ltd. +AC3019 (base 16) Shenzhen Hailingwei Electronics Co., Ltd. + 2nd Floor, Building 9, Longwangmiao Industrial Zone, East District, Baishixia Community, Fuyong Street, Bao'an District, Shenzhen + Shenzhen Guangdong 518000 + CN -88-5B-DD (hex) Extreme Networks Headquarters -885BDD (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +A0-F2-61 (hex) Palo Alto Networks +A0F261 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 US -34-85-84 (hex) Extreme Networks Headquarters -348584 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +74-F7-14 (hex) Lushare Precision Industry Co.,LTD +74F714 (base 16) Lushare Precision Industry Co.,LTD + No.313, Beihuan Road, Tiesong, Qingxi Town, + Dongguan Guangdong 523650 + CN -94-9B-2C (hex) Extreme Networks Headquarters -949B2C (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +A0-14-6D (hex) Suzhou NODKA Automation Technology Co.,Ltd +A0146D (base 16) Suzhou NODKA Automation Technology Co.,Ltd + NO.480, Yinzang Road, Linhu Town, Wuzhong District + Suzhou Jiangsu 215106 + CN -A4-EA-8E (hex) Extreme Networks Headquarters -A4EA8E (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +8C-49-CF (hex) Private +8C49CF (base 16) Private -B8-50-01 (hex) Extreme Networks Headquarters -B85001 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +90-F0-4C (hex) Nokia Solutions (Shanghai) Co.,Ltd. +90F04C (base 16) Nokia Solutions (Shanghai) Co.,Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai 201206,P.R.China + Shanghai Pudong New Area 201206 + CN -14-14-4B (hex) Ruijie Networks Co.,LTD -14144B (base 16) Ruijie Networks Co.,LTD - 19-22# Building,Star-net Science Plaza,Juyuanzhou, - FUZHOU FUJIAN 350002 +20-50-0D (hex) Espressif Inc. +20500D (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -00-04-A5 (hex) Barco NV -0004A5 (base 16) Barco NV - BeneluxPark 21 - Kortrijk 8500 - BE +68-15-D3 (hex) R&G PLUS Sp. z o.o. +6815D3 (base 16) R&G PLUS Sp. z o.o. + ul. Traugutta 7 + Mielec 39-300 + PL -AC-ED-32 (hex) Extreme Networks Headquarters -ACED32 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +74-8F-BF (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +748FBF (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -00-E6-0E (hex) Extreme Networks Headquarters -00E60E (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +3C-A2-39 (hex) DGSQ Co.,Ltd +3CA239 (base 16) DGSQ Co.,Ltd + Building A-B, Dongxing Industrial Park, Kengmei Village, Dongkeng Town, Dongguan City, Guangdong Province, China + Dongguan Guangdong 523455 + CN -44-D9-80 (hex) EVERYBOT INC. -44D980 (base 16) EVERYBOT INC. - 10th Floor of H Square B/D S, Pangyoyeok-ro 231, Bundang-gu - Seongnam-si Gyeonggi-do 13494 - KR +68-19-77 (hex) New H3C Technologies Co., Ltd +681977 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -00-16-E6 (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -0016E6 (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - Pin-Jen City, Taoyuan - 324 +C4-92-D9 (hex) zte corporation +C492D9 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +54-BB-8F (hex) ACCTON TECHNOLOGY CORPORATION +54BB8F (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 TW -D0-DC-2C (hex) Cisco Systems, Inc -D0DC2C (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +70-C2-88 (hex) Intel Corporate +70C288 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -F8-3C-44 (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED -F83C44 (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 +74-13-48 (hex) Blink by Amazon +741348 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 + US -20-15-DE (hex) Samsung Electronics Co.,Ltd -2015DE (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +80-3B-70 (hex) Private +803B70 (base 16) Private -18-52-3D (hex) Xiamen Jiwu Technology CO.,Ltd -18523D (base 16) Xiamen Jiwu Technology CO.,Ltd - 1st Floor,No.75 Hu'an Road, Huli District - Xiamen Fujian 361006 +78-8A-FB (hex) HANSHOW TECHNOLOGY CO.,LTD. +788AFB (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 -D8-43-EA (hex) SY Electronics Ltd -D843EA (base 16) SY Electronics Ltd - 7 Worrall Street - Manchester M5 4TH - GB +40-12-77 (hex) Microsoft Corporation +401277 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US -D0-91-68 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -D09168 (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 - SHENZHEN GUANGDONG 518057 +3C-A0-0E (hex) Shenzhen Skyworth Digital Technology CO., Ltd +3CA00E (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -D4-A3-8B (hex) ELE(GROUP)CO.,LTD -D4A38B (base 16) ELE(GROUP)CO.,LTD - No.158, Chuangyuan Road, SIP, Suzhou, Jiangsu, China - suzhou jiangsu 215000 +FC-24-22 (hex) Hangzhou Ezviz Software Co.,Ltd. +FC2422 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -54-13-CA (hex) ITEL MOBILE LIMITED -5413CA (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 +90-92-2C (hex) Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. +90922C (base 16) Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. + No. 108, Haisen Street, High-tech Industrial Development Zone + Changzhi Shanxi 046000 + CN -10-E8-3A (hex) FIBERX DISTRIBUIDORA DE PRODUTOS DE TELECOMUNICACAO LTDA -10E83A (base 16) FIBERX DISTRIBUIDORA DE PRODUTOS DE TELECOMUNICACAO LTDA - RUA JOSE NEOLI CRUZ, 5000 - PORTO BELO SANTA CATARINA 88210000 - BR +BC-FA-BA (hex) Mellanox Technologies, Inc. +BCFABA (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -E8-C8-29 (hex) Intel Corporate -E8C829 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +D4-94-77 (hex) FONEX Data Systems Inc. +D49477 (base 16) FONEX Data Systems Inc. + 5400 Saint-Francois + Saint-Laurent QC H4S 1P6 + CA -E0-4C-05 (hex) EverCharge -E04C05 (base 16) EverCharge - 548 Market Street, 31647 - San Francisco CA 94104 +DC-1B-48 (hex) Texas Instruments +DC1B48 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -58-68-7A (hex) Sagemcom Broadband SAS -58687A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -80-48-2C (hex) Wyze Labs Inc -80482C (base 16) Wyze Labs Inc - 4030 Lake Washington Boulevard NE - Kirkland WA 98033 +64-D3-63 (hex) Seyond +64D363 (base 16) Seyond + 160 San Gabriel Dr + Sunnyvale CA 94086 US -88-5E-BD (hex) NCKOREA Co.,Ltd. -885EBD (base 16) NCKOREA Co.,Ltd. - 121, Hyeoksinsandan 7-gil, Wanggok-myeon, Naju-si, Jeollanam-do, Republic of Korea - Naju-si Jeollanam-do 58296 +F4-49-EF (hex) EMSTONE +F449EF (base 16) EMSTONE + #1201, Byeoksan Digital Valley 3rd, 271, Digital-ro + Guro-Gu Seoul 08381 KR -1C-60-66 (hex) TEJAS NETWORKS LTD -1C6066 (base 16) TEJAS NETWORKS LTD - Plot 25 JP Software Park Electronics City Phase-1 - Bangalore Karnataka 560100 - IN - -D0-F4-05 (hex) Hon Hai Precision Industry Co., Ltd. -D0F405 (base 16) Hon Hai Precision Industry Co., Ltd. - GuangDongShenZhen - ShenZhen GuangDong 518109 +AC-87-46 (hex) Huizhou BYD Electronic Co., Ltd. +AC8746 (base 16) Huizhou BYD Electronic Co., Ltd. + Xiangshui River, Economic Development Zone, Daya Bay, Huizhou, Guangdong, China + Huizhou Guangdong 516000 CN -88-09-AF (hex) Masimo Corporation -8809AF (base 16) Masimo Corporation - 52 Discovery - Irvine CA 92618 +78-0C-48 (hex) Hong Kong Yihao Electronic Technology Co., Limited +780C48 (base 16) Hong Kong Yihao Electronic Technology Co., Limited + FLAT/RM 1618B 16/F PIONEER CTR 750 NATHAN RD MONG KOK + Hong Kong 999077 + HK + +68-70-9E (hex) Silicon Laboratories +68709E (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -B0-4A-B4 (hex) Motorola Mobility LLC, a Lenovo Company -B04AB4 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +74-DA-78 (hex) HP Inc. +74DA78 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US -C4-14-A2 (hex) Cisco Meraki -C414A2 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +80-5A-70 (hex) Fortinet, Inc. +805A70 (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 US -64-E7-38 (hex) Zhejiang SUPCON Technology Co., Ltd. -64E738 (base 16) Zhejiang SUPCON Technology Co., Ltd. - No.309 Liuhe Road, Binjiang District - Hangzhou Zhejiang 310053 +9C-1F-E6 (hex) Shenzhen Skyworth Display Technologies Co.,Ltd +9C1FE6 (base 16) Shenzhen Skyworth Display Technologies Co.,Ltd + 1st floor, Experimental Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District + Shenzhen Guangdong 518108 CN -5C-7D-F3 (hex) Fiberhome Telecommunication Technologies Co.,LTD -5C7DF3 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +B4-65-DC (hex) CHINA DRAGON TECHNOLOGY LIMITED +B465DC (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 + +F4-2F-97 (hex) Embrava USA, Inc +F42F97 (base 16) Embrava USA, Inc + 31 Hudson Yards, FL 11 + New York NY 10001 + US + +34-26-01 (hex) HUAWEI TECHNOLOGIES CO.,LTD +342601 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +AC-24-77 (hex) Shenzhen Tinno Mobile Technology Corp +AC2477 (base 16) Shenzhen Tinno Mobile Technology Corp + 3F.15F(N).24-28F, Tianlong Mobile Headquarters BuildingTongfa South Road, Xilli Community, Xili Street, Nanshan DistrictShenzhen City, Guangdong Province, P. R. China + Shenzhen Guangdong 518053 + CN + +F8-A5-E6 (hex) Magicyo Technology CO.,Ltd +F8A5E6 (base 16) Magicyo Technology CO.,Ltd + Room 518, Incubation Center Building, China Academy of Science and Technology Development, High tech Community, Yuehai Street, Nanshan District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518057 + CN + +E8-B8-53 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +E8B853 (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 + +C4-8A-CE (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD +C48ACE (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD + Qianwangang Road 218 + Qingdao Shandong 266510 + CN + +5C-A9-31 (hex) Ubee Interactive Co., Limited +5CA931 (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 + HK + +64-21-FD (hex) Guang zhou Xradio Technology Co., Ltd +6421FD (base 16) Guang zhou Xradio Technology Co., Ltd + Room 405 ,BuildingB, No. 18 Science Avenue, Guangzhou Science City + Guangzhou Guangdong 510700 + CN + +98-FE-54 (hex) Raspberry Pi (Trading) Ltd +98FE54 (base 16) Raspberry Pi (Trading) Ltd + Maurice Wilkes Building, St Johns Innovation Park + Cambridge Cambridgeshire CB4 0DS + GB + +DC-8E-6D (hex) Huawei Device Co., Ltd. +DC8E6D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +CC-A3-0C (hex) Silicon Laboratories +CCA30C (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +70-28-7D (hex) Google, Inc. +70287D (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +E8-BA-17 (hex) Beijing Xiaomi Mobile Software Co., Ltd +E8BA17 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +B0-64-E0 (hex) Samsung Electronics Co.,Ltd +B064E0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +94-64-42 (hex) CELESTICA INC. +946442 (base 16) CELESTICA INC. + 1900-5140 Yonge Street PO Box 42 + Toronto Ontario M2N 6L7 + CA + +50-85-7C (hex) eero inc. +50857C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +24-1A-F7 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +241AF7 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + +3C-35-58 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3C3558 (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 + +34-DB-9C (hex) Sagemcom Broadband SAS +34DB9C (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +78-65-59 (hex) Sagemcom Broadband SAS +786559 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +34-49-5B (hex) Sagemcom Broadband SAS +34495B (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +10-06-45 (hex) Sagemcom Broadband SAS +100645 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +10-D7-B0 (hex) Sagemcom Broadband SAS +10D7B0 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +58-2F-F7 (hex) Sagemcom Broadband SAS +582FF7 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +70-73-3A (hex) Jiangxi Remote lntelligence Technology Co.,Ltd +70733A (base 16) Jiangxi Remote lntelligence Technology Co.,Ltd + No. 1, Chemical Avenue, Guixi335400, Yingtan, Jiangxi + Yingtan Jiangxi 360600 + CN + +54-88-D5 (hex) zte corporation +5488D5 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +AC-3B-77 (hex) Sagemcom Broadband SAS +AC3B77 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +78-C2-13 (hex) Sagemcom Broadband SAS +78C213 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +88-0F-A2 (hex) Sagemcom Broadband SAS +880FA2 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +DC-97-E6 (hex) Sagemcom Broadband SAS +DC97E6 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +C4-EB-42 (hex) Sagemcom Broadband SAS +C4EB42 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D0-CF-0E (hex) Sagemcom Broadband SAS +D0CF0E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +08-7B-12 (hex) Sagemcom Broadband SAS +087B12 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +34-53-D2 (hex) Sagemcom Broadband SAS +3453D2 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +4C-19-5D (hex) Sagemcom Broadband SAS +4C195D (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D0-6D-C9 (hex) Sagemcom Broadband SAS +D06DC9 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D8-33-B7 (hex) Sagemcom Broadband SAS +D833B7 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +2C-FB-0F (hex) Sagemcom Broadband SAS +2CFB0F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +BC-D5-ED (hex) Sagemcom Broadband SAS +BCD5ED (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +8C-9A-8F (hex) Sagemcom Broadband SAS +8C9A8F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +70-7D-A1 (hex) Sagemcom Broadband SAS +707DA1 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +9C-24-72 (hex) Sagemcom Broadband SAS +9C2472 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +DC-92-72 (hex) Sagemcom Broadband SAS +DC9272 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +18-0C-7A (hex) Sagemcom Broadband SAS +180C7A (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +EC-50-A6 (hex) Sagemcom Broadband SAS +EC50A6 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +60-15-6F (hex) TP-Link Systems Inc. +60156F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +84-67-9A (hex) Arm Ltd +84679A (base 16) Arm Ltd + 110 Fulbourn Road + Cambridge Cambridgeshire CB19NJ + GB + +38-B4-D3 (hex) BSH Hausgeräte GmbH +38B4D3 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE + +BC-4F-2D (hex) Apple, Inc. +BC4F2D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +8C-82-83 (hex) Apple, Inc. +8C8283 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +5C-B8-B7 (hex) Apple, Inc. +5CB8B7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +10-9E-6B (hex) Apple, Inc. +109E6B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +98-9E-85 (hex) Honor Device Co., Ltd. +989E85 (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 + +0C-54-27 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +0C5427 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 + CN + +14-C7-C4 (hex) Zyxel Communications Corporation +14C7C4 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +EC-2C-0D (hex) Apple, Inc. +EC2C0D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +D8-46-CE (hex) Apple, Inc. +D846CE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +00-02-31 (hex) Ingersoll-Rand +000231 (base 16) Ingersoll-Rand + 53 Frontage Road Suite 250 + Hampton NJ 08827 + US + +00-03-74 (hex) Schneider Electric +000374 (base 16) Schneider Electric + 35 Rue Joseph Monier + Rueil-Malmaison 92500 + CA + +44-99-5B (hex) GX India Pvt Ltd +44995B (base 16) GX India Pvt Ltd + 595, SECTOR-8, IMT MANESAR + GURGAON Haryana 122051 + IN + +6C-87-20 (hex) New H3C Technologies Co., Ltd +6C8720 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +0C-8D-7A (hex) RADiflow +0C8D7A (base 16) RADiflow + HaBarzel St 38 + Tel-Aviv 6971054 + IL + +FC-A9-F5 (hex) Xiaomi Communications Co Ltd +FCA9F5 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +6C-03-70 (hex) Extreme Networks Headquarters +6C0370 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +78-96-A3 (hex) Extreme Networks Headquarters +7896A3 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +DC-E6-50 (hex) Extreme Networks Headquarters +DCE650 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +24-1F-BD (hex) Extreme Networks Headquarters +241FBD (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +E4-44-E5 (hex) Extreme Networks Headquarters +E444E5 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +40-88-2F (hex) Extreme Networks Headquarters +40882F (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +40-18-B1 (hex) Extreme Networks Headquarters +4018B1 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +20-6C-8A (hex) Extreme Networks Headquarters +206C8A (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +88-5B-DD (hex) Extreme Networks Headquarters +885BDD (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +34-85-84 (hex) Extreme Networks Headquarters +348584 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +94-9B-2C (hex) Extreme Networks Headquarters +949B2C (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +A4-EA-8E (hex) Extreme Networks Headquarters +A4EA8E (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +B8-50-01 (hex) Extreme Networks Headquarters +B85001 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +14-14-4B (hex) Ruijie Networks Co.,LTD +14144B (base 16) Ruijie Networks Co.,LTD + 19-22# Building,Star-net Science Plaza,Juyuanzhou, + FUZHOU FUJIAN 350002 + CN + +00-04-A5 (hex) Barco NV +0004A5 (base 16) Barco NV + BeneluxPark 21 + Kortrijk 8500 + BE + +AC-ED-32 (hex) Extreme Networks Headquarters +ACED32 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +00-E6-0E (hex) Extreme Networks Headquarters +00E60E (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +44-D9-80 (hex) EVERYBOT INC. +44D980 (base 16) EVERYBOT INC. + 10th Floor of H Square B/D S, Pangyoyeok-ro 231, Bundang-gu + Seongnam-si Gyeonggi-do 13494 + KR + +00-16-E6 (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +0016E6 (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + Pin-Jen City, Taoyuan + 324 + TW + +D0-DC-2C (hex) Cisco Systems, Inc +D0DC2C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +F8-3C-44 (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +F83C44 (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 + +20-15-DE (hex) Samsung Electronics Co.,Ltd +2015DE (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +18-52-3D (hex) Xiamen Jiwu Technology CO.,Ltd +18523D (base 16) Xiamen Jiwu Technology CO.,Ltd + 1st Floor,No.75 Hu'an Road, Huli District + Xiamen Fujian 361006 + CN + +D8-43-EA (hex) SY Electronics Ltd +D843EA (base 16) SY Electronics Ltd + 7 Worrall Street + Manchester M5 4TH + GB + +D0-91-68 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +D09168 (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 + SHENZHEN GUANGDONG 518057 + CN + +D4-A3-8B (hex) ELE(GROUP)CO.,LTD +D4A38B (base 16) ELE(GROUP)CO.,LTD + No.158, Chuangyuan Road, SIP, Suzhou, Jiangsu, China + suzhou jiangsu 215000 + CN + +54-13-CA (hex) ITEL MOBILE LIMITED +5413CA (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 + +10-E8-3A (hex) FIBERX DISTRIBUIDORA DE PRODUTOS DE TELECOMUNICACAO LTDA +10E83A (base 16) FIBERX DISTRIBUIDORA DE PRODUTOS DE TELECOMUNICACAO LTDA + RUA JOSE NEOLI CRUZ, 5000 + PORTO BELO SANTA CATARINA 88210000 + BR + +E8-C8-29 (hex) Intel Corporate +E8C829 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +E0-4C-05 (hex) EverCharge +E04C05 (base 16) EverCharge + 548 Market Street, 31647 + San Francisco CA 94104 + US + +58-68-7A (hex) Sagemcom Broadband SAS +58687A (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +80-48-2C (hex) Wyze Labs Inc +80482C (base 16) Wyze Labs Inc + 4030 Lake Washington Boulevard NE + Kirkland WA 98033 + US + +88-5E-BD (hex) NCKOREA Co.,Ltd. +885EBD (base 16) NCKOREA Co.,Ltd. + 121, Hyeoksinsandan 7-gil, Wanggok-myeon, Naju-si, Jeollanam-do, Republic of Korea + Naju-si Jeollanam-do 58296 + KR + +1C-60-66 (hex) TEJAS NETWORKS LTD +1C6066 (base 16) TEJAS NETWORKS LTD + Plot 25 JP Software Park Electronics City Phase-1 + Bangalore Karnataka 560100 + IN + +D0-F4-05 (hex) Hon Hai Precision Industry Co., Ltd. +D0F405 (base 16) Hon Hai Precision Industry Co., Ltd. + GuangDongShenZhen + ShenZhen GuangDong 518109 + CN + +88-09-AF (hex) Masimo Corporation +8809AF (base 16) Masimo Corporation + 52 Discovery + Irvine CA 92618 + US + +B0-4A-B4 (hex) Motorola Mobility LLC, a Lenovo Company +B04AB4 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +C4-14-A2 (hex) Cisco Meraki +C414A2 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +64-E7-38 (hex) Zhejiang SUPCON Technology Co., Ltd. +64E738 (base 16) Zhejiang SUPCON Technology Co., Ltd. + No.309 Liuhe Road, Binjiang District + Hangzhou Zhejiang 310053 + CN + +5C-7D-F3 (hex) Fiberhome Telecommunication Technologies Co.,LTD +5C7DF3 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN EC-04-82 (hex) STL Systems AG @@ -96080,18 +97064,6 @@ ACC4A9 (base 16) Fiberhome Telecommunication Technologies Co.,LTD HongKong HongKong 999077 HK -C4-EB-43 (hex) Sagemcom Broadband SAS -C4EB43 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -C4-EB-41 (hex) Sagemcom Broadband SAS -C4EB41 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 74-3E-39 (hex) YUSUR Technology Co., Ltd. 743E39 (base 16) YUSUR Technology Co., Ltd. Room 1401,building 4,yard 1, Beiqing Road No.81, Haidian District @@ -96500,12 +97472,6 @@ A41CB4 (base 16) DFI Inc Dongguan Guangdong 523808 CN -94-3C-96 (hex) Sagemcom Broadband SAS -943C96 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 70-B7-E4 (hex) Broadcom Limited 70B7E4 (base 16) Broadcom Limited 15191 Alton Parkway @@ -96722,12 +97688,6 @@ F0C1CE (base 16) GoodWe Technologies CO., Ltd REDMOND WA 98052 US -C4-EB-39 (hex) Sagemcom Broadband SAS -C4EB39 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-52-C8 (hex) Made Studio Design Ltd. 0052C8 (base 16) Made Studio Design Ltd. 10F., No. 169, Sec. 4, Zhongxiao E. Rd., Da-an Dist. @@ -97238,12 +98198,6 @@ C0A938 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Beijing 100089 CN -64-FD-96 (hex) Sagemcom Broadband SAS -64FD96 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-98-9E (hex) TRINNOV AUDIO 64989E (base 16) TRINNOV AUDIO 5 rue Edmond Michelet @@ -98468,12 +99422,6 @@ D86C5A (base 16) HUMAX Co., Ltd. Seongnam-si Gyeonggi-do 463-875 KR -E4-C0-E2 (hex) Sagemcom Broadband SAS -E4C0E2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 4C-3F-A7 (hex) uGrid Network Inc. 4C3FA7 (base 16) uGrid Network Inc. 602 Gabriola Way @@ -101024,12 +101972,6 @@ C8BC9C (base 16) Huawei Device Co., Ltd. Kulim Kedah 09000 MY -6C-BA-B8 (hex) Sagemcom Broadband SAS -6CBAB8 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E0-D4-64 (hex) Intel Corporate E0D464 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -101126,12 +102068,6 @@ F469D5 (base 16) IEEE Registration Authority Piscataway NJ 08554 US -8C-C5-B4 (hex) Sagemcom Broadband SAS -8CC5B4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F8-1B-04 (hex) Zhong Shan City Richsound Electronic Industrial Ltd F81B04 (base 16) Zhong Shan City Richsound Electronic Industrial Ltd Qunle Industrial Area,East ShaGang Road,GangKou ZhongShan,GuangDong,China @@ -101522,12 +102458,6 @@ EC2651 (base 16) Apple, Inc. Sunnyvale CA 94089 US -D4-F8-29 (hex) Sagemcom Broadband SAS -D4F829 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-50-3A (hex) Nanjing Ticom Tech Co., Ltd. DC503A (base 16) Nanjing Ticom Tech Co., Ltd. No.35 Fenghui Road, Yuhuatai District @@ -101792,12 +102722,6 @@ C88314 (base 16) Tempo Communications Palo Alto CA 94304 US -C4-42-68 (hex) CRESTRON ELECTRONICS, INC. -C44268 (base 16) CRESTRON ELECTRONICS, INC. - 15 Volvo Drive - Rockleigh NJ 07647 - US - 40-F5-20 (hex) Espressif Inc. 40F520 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -103274,12 +104198,6 @@ E419C1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -B8-66-85 (hex) Sagemcom Broadband SAS -B86685 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B4-52-A9 (hex) Texas Instruments B452A9 (base 16) Texas Instruments 12500 TI Blvd @@ -104462,12 +105380,6 @@ A89CA4 (base 16) Furrion Limited Dallas TX 75243 US -30-24-78 (hex) Sagemcom Broadband SAS -302478 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 18-4B-DF (hex) Caavo Inc 184BDF (base 16) Caavo Inc 1525 McCarthy Blvd., #1182 @@ -104600,12 +105512,6 @@ E0A509 (base 16) Bitmain Technologies Inc Reno NV 89507 US -D8-A7-56 (hex) Sagemcom Broadband SAS -D8A756 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 54-83-3A (hex) Zyxel Communications Corporation 54833A (base 16) Zyxel Communications Corporation No. 6 Innovation Road II, Science Park @@ -105068,12 +105974,6 @@ F09FFC (base 16) SHARP Corporation Wuhan Hubei 430074 CN -70-0B-01 (hex) Sagemcom Broadband SAS -700B01 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 5C-26-23 (hex) WaveLynx Technologies Corporation 5C2623 (base 16) WaveLynx Technologies Corporation 100 Technology Drive, Building B, Ste 150 @@ -116939,12 +117839,6 @@ A40BED (base 16) Carry Technology Co.,Ltd Jhonghe Dist. New Taipei City 23585 TW -70-23-93 (hex) fos4X GmbH -702393 (base 16) fos4X GmbH - Thalkirchner Str. 210, Geb. 6 - 81371 München - DE - F8-5F-2A (hex) Nokia Corporation F85F2A (base 16) Nokia Corporation Elektroniikkatie 10 @@ -119858,12 +120752,6 @@ A05DE7 (base 16) DIRECTV, Inc. El Segundo CA 90245 US -08-76-18 (hex) ViE Technologies Sdn. Bhd. -087618 (base 16) ViE Technologies Sdn. Bhd. - no. 85-A, Lintang Bayan Lepas 11, - Bayan Lepas Penang 11900 - MY - D0-E4-0B (hex) Wearable Inc. D0E40B (base 16) Wearable Inc. 3825 Charles Dr. @@ -128144,12 +129032,6 @@ B4B5AF (base 16) Minsung Electronics Rogersville TN 37857 US -00-05-21 (hex) Control Microsystems -000521 (base 16) Control Microsystems - 48 Steacie Drive - Ottawa Ontario K2K 2A9 - CA - 00-05-1F (hex) Taijin Media Co., Ltd. 00051F (base 16) Taijin Media Co., Ltd. 640-8 Tungchon-Dong @@ -134948,12 +135830,6 @@ B03226 (base 16) Keheng Information Industry Co., Ltd. Jinan Shandong 250098 CN -3C-58-36 (hex) Sagemcom Broadband SAS -3C5836 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-CB-85 (hex) EM Microelectronic 84CB85 (base 16) EM Microelectronic Rue des Sors 3 @@ -137186,9 +138062,6 @@ E4A430 (base 16) Samsung Electronics Co.,Ltd Marin-Epagnier Neuchatel 2074 CH -C0-BA-1F (hex) Private -C0BA1F (base 16) Private - 78-E1-67 (hex) Launch Tech Co., Ltd. 78E167 (base 16) Launch Tech Co., Ltd. Launch Industrial Park, No.4012, North of Wuhe Road, Bantian Street, Longgang District, @@ -137570,23 +138443,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 @@ -137606,17 +138479,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 @@ -137630,12 +138515,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 @@ -137696,11 +138575,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. @@ -137738,6 +138617,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 @@ -137756,12 +138665,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 @@ -137870,36 +138773,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 @@ -137936,42 +138809,6 @@ F02C59 (base 16) Chipsea Technologies (Shenzhen) Crop. Shenzhen 518108 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 - Putuo District Shanghai 200333 - CN - -04-B5-C1 (hex) ITEL MOBILE LIMITED -04B5C1 (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 - -28-C9-7A (hex) New H3C Technologies Co., Ltd -28C97A (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - 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 - Suzhou Jiangsu 215341 - CN - -AC-BD-F7 (hex) Cisco Meraki -ACBDF7 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - FC-39-5A (hex) SonicWall FC395A (base 16) SonicWall 1033 McCarthy Blvd @@ -137996,6 +138833,36 @@ B0D5FB (base 16) Google, Inc. 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 + Putuo District Shanghai 200333 + CN + +04-B5-C1 (hex) ITEL MOBILE LIMITED +04B5C1 (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 + +28-C9-7A (hex) New H3C Technologies Co., Ltd +28C97A (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +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 + Suzhou Jiangsu 215341 + CN + +AC-BD-F7 (hex) Cisco Meraki +ACBDF7 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + C8-17-F5 (hex) Nanjing Qinheng Microelectronics Co., Ltd. C817F5 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road @@ -138014,16 +138881,28 @@ 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 +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. @@ -138032,18 +138911,18 @@ CC22FE (base 16) Apple, Inc. 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 +34-DA-A1 (hex) Apple, Inc. +34DAA1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US +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 + 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 @@ -138056,30 +138935,12 @@ CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd 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 @@ -138098,24 +138959,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 @@ -138146,6 +138989,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 @@ -138164,24 +139013,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 @@ -138254,12 +139115,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 @@ -138272,6 +139127,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 @@ -138284,11 +139151,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. @@ -138302,22 +139175,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 @@ -138356,12 +139223,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 @@ -138380,6 +139241,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 @@ -138410,12 +139277,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 @@ -138446,54 +139307,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 @@ -138506,24 +139379,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 @@ -138536,6 +139391,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 @@ -138548,18 +139445,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 @@ -138572,23 +139475,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. @@ -138596,28 +139511,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. @@ -138638,12 +139571,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 @@ -138668,6 +139595,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 @@ -138680,71 +139613,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 @@ -138758,42 +139661,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 @@ -138830,6 +139697,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 @@ -138848,29 +139721,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. @@ -138878,11 +139751,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 @@ -138920,70 +139811,64 @@ 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 - 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 +D8-3A-36 (hex) AltoBeam Inc. +D83A36 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 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 +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 -68-AB-A9 (hex) Sagemcom Broadband SAS -68ABA9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - 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. 1600 Amphitheatre Parkway 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 @@ -139034,35 +139919,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 @@ -139070,17 +139961,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 @@ -139088,6 +139979,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 @@ -139106,30 +140009,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 @@ -139148,11 +140027,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. @@ -139160,60 +140045,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. @@ -139256,28 +140093,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 @@ -139298,12 +140129,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 @@ -139316,6 +140141,60 @@ 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 + +A4-C0-B0 (hex) Drivenets +A4C0B0 (base 16) Drivenets + 1st Zarhin St. + Raanana Israel 4366235 + IL + +90-C9-52 (hex) Durin, Inc +90C952 (base 16) Durin, Inc + 440 N Wolfe Rd + Sunnyvale CA 94085 + 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 + US + +98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd +98A942 (base 16) Tozed Kangwei Tech Co., Ltd + Room 1301, NO. 37 Jinlong , Nansha Street, Xiangjiang Financial Business Center, Nansha District + Guangzhou Guangdong 511458 + CN + +28-D6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD +28D6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +AC-E0-11 (hex) HUAWEI TECHNOLOGIES CO.,LTD +ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + 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 + 08-6A-0B (hex) Cisco Meraki 086A0B (base 16) Cisco Meraki 500 Terry A. Francois Blvd @@ -139328,18 +140207,42 @@ C86340 (base 16) Cisco Meraki San Francisco 94158 US -A4-C0-B0 (hex) Drivenets -A4C0B0 (base 16) Drivenets - 1st Zarhin St. - 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 +FC-26-8C (hex) Signify B.V. +FC268C (base 16) Signify B.V. + High Tech Campus 7 + Eindhoven 5656AE + NL + +E8-3D-C1 (hex) Espressif Inc. +E83DC1 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +24-71-21 (hex) Cisco Systems, Inc +247121 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +BC-AB-F5 (hex) Cisco Systems, Inc +BCABF5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +B8-C9-24 (hex) Cisco Systems, Inc +B8C924 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + 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 @@ -139358,24 +140261,6 @@ A05866 (base 16) Qorvo Utrecht B.V. REDMOND WA 98052 US -98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd -98A942 (base 16) Tozed Kangwei Tech Co., Ltd - Room 1301, NO. 37 Jinlong , Nansha Street, Xiangjiang Financial Business Center, Nansha District - Guangzhou Guangdong 511458 - CN - -28-D6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD -28D6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -AC-E0-11 (hex) HUAWEI TECHNOLOGIES CO.,LTD -ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - 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 @@ -139400,17 +140285,35 @@ CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD 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 -FC-26-8C (hex) Signify B.V. -FC268C (base 16) Signify B.V. - High Tech Campus 7 - Eindhoven 5656AE - NL +00-12-4F (hex) Chemelex LLC +00124F (base 16) Chemelex LLC + 1665 Utica Avenue, Suite 700 + St Louis Park MN 55416 + US + +F0-30-12 (hex) AUMOVIO Autonomous Mobility Germany GmbH +F03012 (base 16) AUMOVIO Autonomous Mobility Germany GmbH + Ringlerstr. 17 + Ingolstadt 85057 + DE + +B4-DD-D0 (hex) AUMOVIO Hungary Kft. +B4DDD0 (base 16) AUMOVIO Hungary Kft. + Napmátka u. 6. + Budapest Pest H-1106 + HU 30-F0-3A (hex) UEI Electronics Private Ltd. 30F03A (base 16) UEI Electronics Private Ltd. @@ -139418,54 +140321,18 @@ FC268C (base 16) Signify B.V. 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 - Shanghai Shanghai 201203 - CN - -24-71-21 (hex) Cisco Systems, Inc -247121 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -BC-AB-F5 (hex) Cisco Systems, Inc -BCABF5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -B8-C9-24 (hex) Cisco Systems, Inc -B8C924 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -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 +94-2D-3A (hex) PRIZOR VIZTECH LIMITED +942D3A (base 16) PRIZOR VIZTECH LIMITED + 514, Maple Trade Ctr Rd, near Surdhara Circle, Thaltej + Ahmedabad Gujarat 380052 + IN -00-12-4F (hex) Chemelex LLC -00124F (base 16) Chemelex LLC - 1665 Utica Avenue, Suite 700 - St Louis Park MN 55416 +08-18-14 (hex) Hewlett Packard Enterprise +081814 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -F0-30-12 (hex) AUMOVIO Autonomous Mobility Germany GmbH -F03012 (base 16) AUMOVIO Autonomous Mobility Germany GmbH - Ringlerstr. 17 - Ingolstadt 85057 - DE - -B4-DD-D0 (hex) AUMOVIO Hungary Kft. -B4DDD0 (base 16) AUMOVIO Hungary Kft. - Napmátka u. 6. - 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 @@ -139478,42 +140345,12 @@ B4DDD0 (base 16) AUMOVIO Hungary Kft. Dover DE 19904 US -94-2D-3A (hex) PRIZOR VIZTECH LIMITED -942D3A (base 16) PRIZOR VIZTECH LIMITED - 514, Maple Trade Ctr Rd, near Surdhara Circle, Thaltej - Ahmedabad Gujarat 380052 - IN - -08-18-14 (hex) Hewlett Packard Enterprise -081814 (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - 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 @@ -139532,23 +140369,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. @@ -139568,18 +140405,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 @@ -139592,18 +140417,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 +D8-3E-EB (hex) AltoBeam Inc. +D83EEB (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -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 +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 @@ -139622,23 +140453,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 @@ -139646,17 +140477,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 @@ -139664,10 +140501,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. @@ -139676,41 +140513,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 @@ -139718,6 +140537,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 @@ -139730,24 +140555,42 @@ 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 + E4-C8-01 (hex) BLU Products Inc E4C801 (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -139760,12 +140603,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 @@ -139778,47 +140651,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. @@ -139838,60 +140717,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 @@ -139916,30 +140765,12 @@ 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 @@ -139958,52 +140789,70 @@ 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 -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 +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 -C8-85-41 (hex) Espressif Inc. -C88541 (base 16) Espressif Inc. +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 -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 -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 +00-07-8B (hex) Wegener Communications, Inc. +00078B (base 16) Wegener Communications, Inc. + 930 Interstate Ridge Drive, Ste. A, + Gainesville GA 30501 + US + +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 + +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 -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 +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 18-83-BF (hex) Arcadyan Corporation @@ -140024,29 +140873,29 @@ B07AA4 (base 16) Guangzhou Punp Electronics Manufacturing Co., Ltd. Hsinchu 300 TW -00-1A-2A (hex) Arcadyan Corporation -001A2A (base 16) Arcadyan Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW +E4-0A-75 (hex) Silicon Laboratories +E40A75 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -5C-DC-96 (hex) Arcadyan Corporation -5CDC96 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd., - Hsinchu City 30071, 12345 - 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 -74-31-70 (hex) Arcadyan Corporation -743170 (base 16) Arcadyan Corporation - 4F. , No. 9 , Park Avenue II, - Hsinchu 300 - TW +D8-5C-11 (hex) Optiview USA +D85C11 (base 16) Optiview USA + 5211 Fairmont Street + Jacksonville FL 32207 + US -F8-E0-00 (hex) FUJI ELECTRIC CO., LTD. -F8E000 (base 16) FUJI ELECTRIC CO., LTD. - 1-27, Fuji-cho - Yokkaichi 510-0013 - JP +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 50-61-88 (hex) PLANET Technology Corporation 506188 (base 16) PLANET Technology Corporation @@ -140054,24 +140903,606 @@ F8E000 (base 16) FUJI ELECTRIC CO., LTD. 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 +F8-E0-00 (hex) FUJI ELECTRIC CO., LTD. +F8E000 (base 16) FUJI ELECTRIC CO., LTD. + 1-27, Fuji-cho + Yokkaichi 510-0013 + JP + +8C-98-85 (hex) Samsung Electronics Co.,Ltd +8C9885 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +F8-EC-FE (hex) Owl Home Inc. +F8ECFE (base 16) Owl Home Inc. + 1007n Orange St, 4th Floor Suite 5077 + Wilmington DE 19801 + US + +E0-E8-05 (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION +E0E805 (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION + NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China + Suzhou 215021 + CN + +44-CE-1D (hex) Nokia +44CE1D (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +D0-B6-46 (hex) NXP Semiconductors Taiwan Ltd. +D0B646 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +54-73-5A (hex) Huawei Device Co., Ltd. +54735A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +E8-C3-C5 (hex) Huawei Device Co., Ltd. +E8C3C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +C8-30-49 (hex) Huawei Device Co., Ltd. +C83049 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +D4-2B-6F (hex) Cisco Systems, Inc +D42B6F (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +AC-22-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +AC2241 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +D0-04-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D00477 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +D4-73-27 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D47327 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +34-22-CF (hex) AUMOVIO Systems, Inc. +3422CF (base 16) AUMOVIO Systems, Inc. + 21440 W. Lake Cook Rd. + Deer Park IL 60010 + US + D0-82-EB (hex) Tuya Smart Inc. D082EB (base 16) Tuya Smart Inc. 160 Greentree Drive, Suite 101 Dover DE 19904 US +3C-71-4B (hex) HUMAX NETWORKS +3C714B (base 16) HUMAX NETWORKS + HUMAX VILLAGE, 216Hwangsaeul-ro, Bundang gu + Seongnam-si Gyeonggi-do 13595 + KR + +08-64-80 (hex) Black Sesame Technologies Co., Ltd +086480 (base 16) Black Sesame Technologies Co., Ltd + 29th / 30th Floor, Building A, CHUNG HING TIMES DIGITAL TRADE PORT, No. 79, Xudong Street, Hongshan District, Wuhan City, Hubei Province + Wuhan Hubei 430000 + CN + +C0-BA-1F (hex) Xiamen Milesight IoT Co., Ltd. +C0BA1F (base 16) Xiamen Milesight IoT Co., Ltd. + + + + +44-BD-8D (hex) Espressif Inc. +44BD8D (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +E0-95-59 (hex) Arcadyan Corporation +E09559 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +28-04-7A (hex) WNC Corporation +28047A (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +B8-D0-8F (hex) Quectel Wireless Solutions Co.,Ltd. +B8D08F (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +F8-40-68 (hex) SZ DJI Ronin Technology Co., Ltd. +F84068 (base 16) SZ DJI Ronin Technology Co., Ltd. + Skyworth Semiconductor Design Building18 Gaoxin South 4th Avenue, Nanshan District + shenzhen Guangdong 518057 + CN + +24-BA-79 (hex) New H3C Technologies Co., Ltd +24BA79 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +30-53-5B (hex) Shenzhen Comnect Technology Co.,LTD +30535B (base 16) Shenzhen Comnect Technology Co.,LTD + 5F, Building 8A, Shenzhen International Innovation Valley, Dashi 1st Road, Nanshan District + Shenzhen Guangdong 518055 + CN + +0C-02-5B (hex) Microchip Technology Inc. +0C025B (base 16) Microchip Technology Inc. + 2355 W. Chandler Blvd. + Chandler AZ 85224 + US + +A8-75-4E (hex) Nexlawn Intelligent Technology (Suzhou) Co., Ltd. +A8754E (base 16) Nexlawn Intelligent Technology (Suzhou) Co., Ltd. + 10/F, Building B, No. 7 Qian Zhu Road, Taihu Street, + Suzhou Jiangsu Province 215100 + CN + +58-92-04 (hex) zte corporation +589204 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +48-09-51 (hex) Guangzhou Trustmo Information System Co.,LTD +480951 (base 16) Guangzhou Trustmo Information System Co.,LTD + 19th Floor, No.123 Tiyu West Road, Tianhe District, Guangzhou City,Guangdong P.R. China + Guangzhou Guangdong 510000 + CN + +74-E6-65 (hex) Dynabook Technology (Hangzhou) Inc. +74E665 (base 16) Dynabook Technology (Hangzhou) Inc. + 2/F,Building2,NO.3,East Gate,Comprehensive bonded Zone,Qiantang District,Hangzhou,Zhejiang + Hangzhou 310018 + CN + +EC-B2-93 (hex) Hewlett Packard Enterprise +ECB293 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +A4-3C-D4 (hex) JBL Professional +A43CD4 (base 16) JBL Professional + 8500 Balboa Boulevard + Northridge CA 91325 + US + +FC-37-6D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +FC376D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +50-DA-9E (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +50DA9E (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +3C-8B-6E (hex) Mellanox Technologies, Inc. +3C8B6E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +7C-4F-AD (hex) Espressif Inc. +7C4FAD (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +90-D7-33 (hex) HUAWEI TECHNOLOGIES CO.,LTD +90D733 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +1C-5B-A2 (hex) HP GLOBALES MEXICO +1C5BA2 (base 16) HP GLOBALES MEXICO + JAVIER BARROS SIERRA + Mexico ALVARO OBREGON 01376 + MX + +C4-96-9F (hex) Amazon Technologies Inc. +C4969F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +34-4E-E2 (hex) Huawei Device Co., Ltd. +344EE2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +7C-85-2F (hex) Huawei Device Co., Ltd. +7C852F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +78-96-6E (hex) Huawei Device Co., Ltd. +78966E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +3C-38-1F (hex) Huawei Device Co., Ltd. +3C381F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +DC-87-F8 (hex) Samsung Electronics Co.,Ltd +DC87F8 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +4C-EC-EE (hex) Samsung Electronics Co.,Ltd +4CECEE (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +28-72-C6 (hex) Samsung Electronics Co.,Ltd +2872C6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +84-19-85 (hex) Samsung Electronics Co.,Ltd +841985 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +5C-17-15 (hex) ODrive Robotics +5C1715 (base 16) ODrive Robotics + 2041 East St PMB 309 + Concord CA 94520 + US + +D8-CF-B1 (hex) BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED +D8CFB1 (base 16) BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED + PLOT 42/4, BLOCK E, OKHLA INDUSTRIAL ESTATE PHASE 2, NEW DELHI + NEW DELHI DELHI 110020 + IN + +0C-26-43 (hex) Cisco Systems, Inc +0C2643 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +08-63-8A (hex) Cisco Systems, Inc +08638A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +C0-A4-CF (hex) Nintendo Co.,Ltd +C0A4CF (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +2C-4B-14 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +2C4B14 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + +08-76-18 (hex) ViTrox Technologies Sdn. Bhd +087618 (base 16) ViTrox Technologies Sdn. Bhd + 746, Persiaran Cassia Selatan 3 Batu Kawan Industrial Park + Bandar Cassia Penang 14110 + MY + +A0-5D-0E (hex) ALPSALPINE CO.,LTD. +A05D0E (base 16) ALPSALPINE CO.,LTD. + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP + +70-0B-01 (hex) Sagemcom Broadband SAS +700B01 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D8-A7-56 (hex) Sagemcom Broadband SAS +D8A756 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +30-24-78 (hex) Sagemcom Broadband SAS +302478 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +60-F7-23 (hex) Beijing Xiaomi Mobile Software Co., Ltd +60F723 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +6C-A6-13 (hex) AltoBeam Inc. +6CA613 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +58-18-B4 (hex) Chengdu Quanjing Intelligent Technology Co.,Ltd +5818B4 (base 16) Chengdu Quanjing Intelligent Technology Co.,Ltd + Building A2, Chi Yuen Technology Park, 1001 College Avenue, Nanshan District, Shenzhen,P.R.C. + Shenzhen Guangdong 518000 + CN + +70-23-93 (hex) Polytech A/S +702393 (base 16) Polytech A/S + Thalkirchner Str. 210, Geb. 6 + 81371 München 0 + DE + +E4-C0-E2 (hex) Sagemcom Broadband SAS +E4C0E2 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-FD-96 (hex) Sagemcom Broadband SAS +64FD96 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +C4-EB-39 (hex) Sagemcom Broadband SAS +C4EB39 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D4-F8-29 (hex) Sagemcom Broadband SAS +D4F829 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +8C-C5-B4 (hex) Sagemcom Broadband SAS +8CC5B4 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +6C-BA-B8 (hex) Sagemcom Broadband SAS +6CBAB8 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +3C-58-36 (hex) Sagemcom Broadband SAS +3C5836 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +A0-3C-20 (hex) Sagemcom Broadband SAS +A03C20 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +CC-FA-F1 (hex) Sagemcom Broadband SAS +CCFAF1 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +68-AB-A9 (hex) Sagemcom Broadband SAS +68ABA9 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +FC-66-37 (hex) Sagemcom Broadband SAS +FC6637 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-79-99 (hex) Sagemcom Broadband SAS +647999 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +94-3C-96 (hex) Sagemcom Broadband SAS +943C96 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +C4-EB-41 (hex) Sagemcom Broadband SAS +C4EB41 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +C4-EB-43 (hex) Sagemcom Broadband SAS +C4EB43 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +B8-66-85 (hex) Sagemcom Broadband SAS +B86685 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +7C-90-E9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +7C90E9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +38-14-A1 (hex) LG Innotek +3814A1 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +30-89-EC (hex) Nintendo Co.,Ltd +3089EC (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +30-09-16 (hex) Apple, Inc. +300916 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +60-2E-D5 (hex) Apple, Inc. +602ED5 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +7C-4F-CD (hex) Apple, Inc. +7C4FCD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +F4-A1-A6 (hex) Apple, Inc. +F4A1A6 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +14-D5-37 (hex) All Inspire Health Inc. +14D537 (base 16) All Inspire Health Inc. + 19 Morris Avenue, Building 128, Cumberland Gate + Brooklyn NY 11205 + US + +B8-1F-3F (hex) Espressif Inc. +B81F3F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +90-DA-72 (hex) Espressif Inc. +90DA72 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D0-0C-5E (hex) Nanjing Qinheng Microelectronics Co., Ltd. +D00C5E (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN + +A0-87-BE (hex) Apple, Inc. +A087BE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +34-F8-DD (hex) Apple, Inc. +34F8DD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +7C-D6-2C (hex) Apple, Inc. +7CD62C (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +6C-68-8A (hex) Amazon Technologies Inc. +6C688A (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +00-05-21 (hex) Schneider Electric +000521 (base 16) Schneider Electric + 35 Rue Joseph Monier + Rueil-Malmaison 92500 + CA + +C4-42-68 (hex) CRESTRON ELECTRONICS, INC. +C44268 (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 + US + +FC-B9-48 (hex) McScience Inc. +FCB948 (base 16) McScience Inc. + B-1102, Digital Empire Bldg. 1556-16 Deogyeong Blvd., Yeongtong + Suwon 16690 + KR + +44-1D-E5 (hex) XCENA Inc. +441DE5 (base 16) XCENA Inc. + 8F, 20, Pangyoyeok-ro 241 beon-gil, Bundang-gu + Seongnam-si Gyeonggi-do 13494 + KR + +58-F6-58 (hex) Edifier International +58F658 (base 16) Edifier International + Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway + Hong Kong 070 + CN + +E0-D2-39 (hex) TECHNOLID, LLC +E0D239 (base 16) TECHNOLID, LLC + Nobelya, 7 + Moscow 121205 + RU + 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 @@ -141065,12 +142496,6 @@ B440DC (base 16) Samsung Electronics Co.,Ltd Taipei City 114 TW -DC-2D-DE (hex) Ledworks SRL -DC2DDE (base 16) Ledworks SRL - Via Tortona 37 - Milano Milano 20144 - IT - 64-17-CD (hex) Samsung Electronics Co.,Ltd 6417CD (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -143882,12 +145307,6 @@ AC2929 (base 16) Infinix mobility limited Vancouver WA 98661 US -F0-4D-D4 (hex) Sagemcom Broadband SAS -F04DD4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-4E-05 (hex) HUNAN FN-LINK TECHNOLOGY LIMITED 684E05 (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED No.8, Litong Road, Liuyan Economic & Tec @@ -144470,12 +145889,6 @@ A439B3 (base 16) Beijing Xiaomi Mobile Software Co., Ltd Beijing Beijing 100085 CN -6C-FF-CE (hex) Sagemcom Broadband SAS -6CFFCE (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C8-99-B2 (hex) Arcadyan Corporation C899B2 (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -147284,18 +148697,6 @@ F02F74 (base 16) ASUSTek COMPUTER INC. Shenzhen Guangdong 518057 CN -D0-6E-DE (hex) Sagemcom Broadband SAS -D06EDE (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -08-CB-E5 (hex) R3 Solutions GmbH -08CBE5 (base 16) R3 Solutions GmbH - Kurfürstendamm 21 - Berlin 10719 - DE - 48-25-67 (hex) Poly 482567 (base 16) Poly 6001 America Center Drive @@ -148202,12 +149603,6 @@ A09B17 (base 16) Taicang T&W Electronics Suzhou Jiangsu 215412 CN -E0-6C-A6 (hex) Creotech Instruments S.A. -E06CA6 (base 16) Creotech Instruments S.A. - ul. Gen. L. Okulickiego 7/9 - Piaseczno Mazovia 05-500 - PL - A0-D8-3D (hex) Fiberhome Telecommunication Technologies Co.,LTD A0D83D (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road @@ -148250,12 +149645,6 @@ C086B3 (base 16) Shenzhen Voxtech Co., Ltd. Shenzhen 518000 CN -44-AD-B1 (hex) Sagemcom Broadband SAS -44ADB1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-3A-EA (hex) Cisco Systems, Inc 643AEA (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -148940,12 +150329,6 @@ CC418E (base 16) MSA Innovation Incheon 21999 KR -C8-D7-78 (hex) BSH Hausgeraete GmbH -C8D778 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - C0-95-DA (hex) NXP India Private Limited C095DA (base 16) NXP India Private Limited 1st Floor, Muttha Towers, Don Bosco Marg, Off Airport Road, Yerwada @@ -149192,12 +150575,6 @@ E0BB9E (base 16) Seiko Epson Corporation Matsumoto-shi Nagano-ken 399-8702 JP -48-D2-4F (hex) Sagemcom Broadband SAS -48D24F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E4-AA-EC (hex) Tianjin Hualai Technology Co., Ltd E4AAEC (base 16) Tianjin Hualai Technology Co., Ltd Overseas Chinese business building No. 10, Jinping Road, Nankai District, Tianjin @@ -150482,12 +151859,6 @@ AC00D0 (base 16) zte corporation shenzhen guangdong 518057 CN -98-1E-19 (hex) Sagemcom Broadband SAS -981E19 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-B8-66 (hex) Beijing XiaoLu technology co. LTD 84B866 (base 16) Beijing XiaoLu technology co. LTD Room 002, floor 2, building 1, yard 4, BeiTuCheng East Road, ChaoYang district, Beijing @@ -151766,12 +153137,6 @@ E00EE1 (base 16) We Corporation Inc. Anyang-si Gyeonggi-do 14088 KR -A8-9A-93 (hex) Sagemcom Broadband SAS -A89A93 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 8C-92-46 (hex) Oerlikon Textile Gmbh&Co.KG 8C9246 (base 16) Oerlikon Textile Gmbh&Co.KG NO.9 Changyang Street @@ -152036,12 +153401,6 @@ B0027E (base 16) MULLER SERVICES Shanghai 200438 CN -28-9E-FC (hex) Sagemcom Broadband SAS -289EFC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-C0-55 (hex) MODULAR COMPUTING TECHNOLOGIES 00C055 (base 16) MODULAR COMPUTING TECHNOLOGIES 2352 MAIN STREET @@ -162188,12 +163547,6 @@ D073D5 (base 16) LIFI LABS MANAGEMENT PTY LTD LUXEMBOURG L-1260 US -48-F9-25 (hex) Maestronic -48F925 (base 16) Maestronic - Futura plaza 2103 - Kwun Tong 88 - HK - 68-83-1A (hex) Pandora Mobility Corporation 68831A (base 16) Pandora Mobility Corporation 1F., No.33, Fude St @@ -174119,12 +175472,6 @@ A07332 (base 16) Cashmaster International Limited Campbell CA 95008 US -00-07-A7 (hex) A-Z Inc. -0007A7 (base 16) A-Z Inc. - 5-5-17 Kamikoushien - - JP - 00-07-A6 (hex) Leviton Manufacturing Co., Inc. 0007A6 (base 16) Leviton Manufacturing Co., Inc. 4330 Michoud Blvd @@ -180080,12 +181427,6 @@ E4E66C (base 16) Tiandy Technologies Co.,LTD Tianjin Tianjin 300384 CN -84-3E-03 (hex) Sagemcom Broadband SAS -843E03 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 54-9A-8F (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. 549A8F (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -181565,12 +182906,6 @@ F45B29 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Mianyang Sichuan 622650 CN -64-7B-1E (hex) Sagemcom Broadband SAS -647B1E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-FA-2E (hex) Sony Interactive Entertainment Inc. 98FA2E (base 16) Sony Interactive Entertainment Inc. 1-7-1 Konan @@ -184136,12 +185471,6 @@ C0DA5E (base 16) Huawei Device Co., Ltd. Paris Paris 75007 FR -44-15-24 (hex) Sagemcom Broadband SAS -441524 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C8-A1-DC (hex) Motorola Mobility LLC, a Lenovo Company C8A1DC (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -184580,23 +185909,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 @@ -184616,23 +185939,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 @@ -184652,6 +185987,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 @@ -184670,36 +186011,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 @@ -184712,18 +186041,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 @@ -184736,6 +186071,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 @@ -184754,12 +186095,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 @@ -184784,54 +186119,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 @@ -184892,20 +186227,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 @@ -184940,16 +186281,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 @@ -184958,10 +186293,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. @@ -184976,22 +186311,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 @@ -185006,17 +186329,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. @@ -185024,12 +186359,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 @@ -185048,12 +186377,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 @@ -185084,23 +186407,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 @@ -185120,11 +186431,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. @@ -185150,23 +186473,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. @@ -185174,29 +186491,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. @@ -185210,12 +186545,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. @@ -185228,42 +186575,36 @@ 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 -D427FF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-49-7C (hex) eero inc. 40497C (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 +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 @@ -185288,12 +186629,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 @@ -185306,10 +186641,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. @@ -185336,36 +186695,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 @@ -185414,24 +186761,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 @@ -185444,11 +186773,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. @@ -185462,6 +186791,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 @@ -185492,12 +186839,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 @@ -185516,46 +186857,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 @@ -185564,16 +186875,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 @@ -185582,22 +186911,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. @@ -185624,30 +186953,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 @@ -185660,6 +186965,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 @@ -185672,17 +186983,35 @@ 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 AC-EB-E6 (hex) Espressif Inc. ACEBE6 (base 16) Espressif Inc. @@ -185690,17 +187019,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. @@ -185750,6 +187091,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, @@ -185768,16 +187121,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. @@ -185810,29 +187157,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 @@ -185846,12 +187187,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 @@ -185870,35 +187205,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 @@ -185912,24 +187241,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 @@ -185948,17 +187295,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 @@ -185978,29 +187331,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 @@ -186014,11 +187355,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. @@ -186044,46 +187391,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. @@ -186092,22 +187403,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 @@ -186122,6 +187421,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 @@ -186140,28 +187445,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 @@ -186170,11 +187469,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 @@ -186218,24 +187523,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 @@ -186248,16 +187535,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 @@ -186266,6 +187571,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 @@ -186296,6 +187613,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 @@ -186320,17 +187643,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. @@ -186338,12 +187667,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 @@ -186368,22 +187691,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. @@ -186392,12 +187736,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 @@ -186416,22 +187754,10 @@ 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 +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 8C-5D-54 (hex) Kisi @@ -186440,39 +187766,12 @@ AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd 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 - 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 @@ -186497,17 +187796,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 @@ -186515,18 +187814,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 @@ -186545,17 +187844,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 @@ -186563,10 +187862,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 @@ -186599,11 +187898,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. @@ -186611,23 +187922,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, @@ -186635,6 +187934,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 @@ -186653,22 +187964,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 @@ -186683,29 +187982,23 @@ 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 - -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. @@ -186725,16 +188018,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. @@ -186749,29 +188036,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 @@ -186785,11 +188072,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 @@ -186797,34 +188084,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. @@ -186839,18 +188144,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 @@ -186863,24 +188186,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 @@ -186893,6 +188204,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 @@ -186905,6 +188222,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 @@ -186917,18 +188240,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 @@ -186953,12 +188264,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 @@ -186971,11 +188276,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 @@ -186983,11 +188288,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. @@ -187001,11 +188306,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. @@ -187019,18 +188324,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 @@ -187049,6 +188342,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 @@ -187061,18 +188360,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 @@ -187103,30 +188432,6 @@ 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 @@ -187139,12 +188444,6 @@ 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 @@ -187169,28 +188468,22 @@ F0D018 (base 16) Hewlett Packard Enterprise Bocholt NRW 46397 DE -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 - 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 +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 -E4-FB-1E (hex) Microsoft Corporation -E4FB1E (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +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. @@ -187211,18 +188504,12 @@ E4FB1E (base 16) Microsoft Corporation 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 +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 -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 @@ -187247,10 +188534,22 @@ 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 +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 + +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 + +E4-FB-1E (hex) Microsoft Corporation +E4FB1E (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US B8-D5-AD (hex) Nokia @@ -187271,28 +188570,70 @@ 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 +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 + +28-87-5F (hex) Annapurna labs +28875F (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +38-6D-ED (hex) Juniper Networks +386DED (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +10-3A-5D (hex) Emerson +103A5D (base 16) Emerson + 6021 Innovation Blvd + Shakopee MN 55379 + US + +30-08-4D (hex) Trumpf Hüttinger +30084D (base 16) Trumpf Hüttinger + Bötzingerstraße 80 + Freiburg 79111 + DE + 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 +DC-9D-ED (hex) Samsung Electronics Co.,Ltd +DC9DED (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +AC-62-FF (hex) Vantiva USA LLC +AC62FF (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 + US + +AC-46-A7 (hex) SERCOMM PHILIPPINES INC +AC46A7 (base 16) SERCOMM PHILIPPINES INC + Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City + Calamba Lot 1 + PH + +30-C8-A2 (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +30C8A2 (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 E0-83-0D (hex) NOTTA PTE. LTD. @@ -187307,18 +188648,6 @@ E0830D (base 16) NOTTA PTE. LTD. 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 @@ -187331,6 +188660,534 @@ FCCA10 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. Shenzhen Guangdong 518057 CN +34-C2-32 (hex) Samsung Electronics Co.,Ltd +34C232 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +9C-F2-7E (hex) Samsung Electronics Co.,Ltd +9CF27E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D0-EA-30 (hex) NXP Semiconductors Taiwan Ltd. +D0EA30 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +F0-A4-EA (hex) Huawei Device Co., Ltd. +F0A4EA (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +A8-F0-7C (hex) Huawei Device Co., Ltd. +A8F07C (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +98-E3-01 (hex) Shenzhen Sundray Technologies company Limited +98E301 (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 + Shenzhen GuangDong 518057 + CN + +08-7D-60 (hex) SAMJIN Co.ltd +087D60 (base 16) SAMJIN Co.ltd + 81, Anyangcheonseo-ro, Manan-gu + Anyang-si Gyeonggi-do 14087 + KR + +BC-B3-0E (hex) Cisco Systems, Inc +BCB30E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-07-A7 (hex) Glory Technical Solutions Co., Ltd. +0007A7 (base 16) Glory Technical Solutions Co., Ltd. + 1-3-1 Shimoteno + Himeji Hyogo 00000 + JP + +3C-15-5A (hex) Nokia +3C155A (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +24-36-72 (hex) AMPAK Technology Inc. +243672 (base 16) AMPAK Technology Inc. + 6F., No23, Huanke 1st Rd. + Zhubei City Hsinchu County 302047 + TW + +30-07-A3 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +3007A3 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +EC-8F-72 (hex) Barrot Technology Co.,Ltd. +EC8F72 (base 16) Barrot Technology Co.,Ltd. + A1009,Block A,Jia Hua Building,No.9 Shangdi 3rd Street,Haidian District,Beijing + beijing beijing 100000 + CN + +08-AD-0A (hex) Espressif Inc. +08AD0A (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +EC-B9-31 (hex) TP-Link Systems Inc. +ECB931 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +D4-D6-DF (hex) TP-Link Systems Inc. +D4D6DF (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +00-3C-B7 (hex) AzureWave Technology Inc. +003CB7 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +EC-71-5E (hex) Freefly Systems Inc +EC715E (base 16) Freefly Systems Inc + 16650 Woodinville-Redmond RD NE, Bldg D + Woodinville WA 98072 + US + +44-7B-45 (hex) Amazon Technologies Inc. +447B45 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +90-FC-55 (hex) Hyve Solutions +90FC55 (base 16) Hyve Solutions + 44201 Nobel Dr + Fremont CA 94538 + US + +CC-E7-DE (hex) IEEE Registration Authority +CCE7DE (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +48-F9-25 (hex) Maestronic +48F925 (base 16) Maestronic + #205-3689 1st Avenue East + Vancouver 88BC V5M 1C2 + CA + +4C-A0-3D (hex) Bouffalo Lab (Nanjing) Co., Ltd. +4CA03D (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +E4-FA-DE (hex) Microsoft Corporation +E4FADE (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +B8-7B-4D (hex) Espressif Inc. +B87B4D (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D4-05-92 (hex) Espressif Inc. +D40592 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +CC-DD-28 (hex) ACCTON TECHNOLOGY CORPORATION +CCDD28 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW + +80-99-CF (hex) Texas Instruments +8099CF (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +38-FF-59 (hex) Dell Inc. +38FF59 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +28-69-26 (hex) OPTOKON, a.s. +286926 (base 16) OPTOKON, a.s. + Červený Kříž 250 + Jihlava 586 01 + CZ + +B0-9C-18 (hex) Shenzhen Taichi Technology Limited +B09C18 (base 16) Shenzhen Taichi Technology Limited + A1710, Nanshan Cloud Technology Building, Vanke Cloud City, Liuxin Third Street, Xili Community, Xili Street, Nanshan District, + Shenzhen Guangdong Province 518000 + CN + +8C-D1-A6 (hex) eero inc. +8CD1A6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +08-CB-E5 (hex) R3 Solutions GmbH +08CBE5 (base 16) R3 Solutions GmbH + Kurfürstendamm 194 + Berlin 10707 + DE + +74-3C-DE (hex) Hewlett Packard Enterprise +743CDE (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +E4-8F-B7 (hex) Arista Networks +E48FB7 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +0C-FE-E5 (hex) Texas Instruments +0CFEE5 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +D8-A4-69 (hex) Sonova AG +D8A469 (base 16) Sonova AG + Laubisruetistrasse 28 + Staefa 8712 + CH + +70-2D-81 (hex) Infinix mobility limited +702D81 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +94-59-15 (hex) Amazon Technologies Inc. +945915 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +DC-2D-DE (hex) Illucere Srl +DC2DDE (base 16) Illucere Srl + Via Tortona 37 + Milano Milano 20144 + IT + +A4-4F-3E (hex) IEEE Registration Authority +A44F3E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +CC-7A-8B (hex) SHENZHEN TECNO TECHNOLOGY +CC7A8B (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 + +40-0E-B9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +400EB9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +08-DD-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08DD82 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +1C-BA-B8 (hex) vivo Mobile Communication Co., Ltd. +1CBAB8 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +6C-93-13 (hex) Mellanox Technologies, Inc. +6C9313 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +B0-9C-B2 (hex) Google, Inc. +B09CB2 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +68-08-0D (hex) Shenzhen Yingsheng Technology Co., LTD +68080D (base 16) Shenzhen Yingsheng Technology Co., LTD + Room 2701, Aerospace Innovation Building, No.7013 Liuxian Avenue, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 + CN + +E0-6C-A6 (hex) Creotech Quantum SA +E06CA6 (base 16) Creotech Quantum SA + Migdalowa 4 + Warsaw Mazovia 02-796 + PL + +78-52-49 (hex) Loxone Electronics GmbH +785249 (base 16) Loxone Electronics GmbH + Smart Home 1 + Kollerschlag 4154 + AT + +9C-09-CA (hex) Huawei Device Co., Ltd. +9C09CA (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +5C-85-05 (hex) Huawei Device Co., Ltd. +5C8505 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +BC-A6-E7 (hex) Sichuan Odot Automation System Co., Ltd. +BCA6E7 (base 16) Sichuan Odot Automation System Co., Ltd. + Plant Building No. 204, Mianyang High-tech Zone,No.261 Eastern Section of Feiyun Avenue,Mianyang city Sichuan Province,China. + Mian Yang 621000 + CN + +2C-E6-4D (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +2CE64D (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +1C-D2-1E (hex) Juniper Networks +1CD21E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +28-9E-FC (hex) Sagemcom Broadband SAS +289EFC (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +A8-9A-93 (hex) Sagemcom Broadband SAS +A89A93 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +98-1E-19 (hex) Sagemcom Broadband SAS +981E19 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +48-D2-4F (hex) Sagemcom Broadband SAS +48D24F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +DC-33-0E (hex) Qingdao Haier Technology Co.Ltd +DC330E (base 16) Qingdao Haier Technology Co.Ltd + Building C01,Haier Information Park,No.1 Haier Road + Qingdao 266101 + CN + +44-CC-6E (hex) Rockwell Automation +44CC6E (base 16) Rockwell Automation + 1 Allen-Bradley Dr. + Mayfield Heights OH 44124-6118 + US + +6C-55-F6 (hex) eero inc. +6C55F6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +A8-29-DC (hex) TP-Link Systems Inc. +A829DC (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +28-91-04 (hex) TP-Link Systems Inc. +289104 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +2C-06-13 (hex) China Mobile Group Device Co.,Ltd. +2C0613 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +B0-3F-D3 (hex) Espressif Inc. +B03FD3 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +C8-D7-78 (hex) BSH Hausgeräte GmbH +C8D778 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE + +B0-1F-F4 (hex) Sagemcom Broadband SAS +B01FF4 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-18-DF (hex) Sagemcom Broadband SAS +6418DF (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D4-50-39 (hex) Sagemcom Broadband SAS +D45039 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-7B-1E (hex) Sagemcom Broadband SAS +647B1E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +44-15-24 (hex) Sagemcom Broadband SAS +441524 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D4-27-FF (hex) Sagemcom Broadband SAS +D427FF (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +44-AD-B1 (hex) Sagemcom Broadband SAS +44ADB1 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D0-6E-DE (hex) Sagemcom Broadband SAS +D06EDE (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +6C-FF-CE (hex) Sagemcom Broadband SAS +6CFFCE (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +F0-4D-D4 (hex) Sagemcom Broadband SAS +F04DD4 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +84-3E-03 (hex) Sagemcom Broadband SAS +843E03 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +54-73-70 (hex) The LEGO Group +547370 (base 16) The LEGO Group + Åstvej 17190 BillundDenmark + Billund Jylland 7190 + DK + +58-0F-A5 (hex) Apple, Inc. +580FA5 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +40-B5-70 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +40B570 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +B8-0E-1D (hex) PAX Computer Technology(Shenzhen) Ltd. +B80E1D (base 16) PAX Computer Technology(Shenzhen) Ltd. + Room 701, PAX Technology Building, Shanxia Community, Pinghu Sub-district, Longgang District, Shenzhen, China + shenzhen GuangDong 518057 + CN + +F4-16-E7 (hex) Skyverse Limited +F416E7 (base 16) Skyverse Limited + Room 101、102 No.1301-14,Guanguang Road,Xinlan community,Guanlan street,Longhua district, Shenzhen,PRC + Shenzhen Guangdong 518100 + CN + +B4-9A-7D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B49A7D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +48-0A-28 (hex) Apple, Inc. +480A28 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +BC-89-C1 (hex) Apple, Inc. +BC89C1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +70-62-CB (hex) Apple, Inc. +7062CB (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +08-7B-0F (hex) Amazon Technologies Inc. +087B0F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -189470,18 +191327,6 @@ F4B52F (base 16) Juniper Networks Beijing Beijing 100085 CN -B8-6A-F1 (hex) Sagemcom Broadband SAS -B86AF1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -CC-58-30 (hex) Sagemcom Broadband SAS -CC5830 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 44-DF-65 (hex) Beijing Xiaomi Mobile Software Co., Ltd 44DF65 (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -189992,12 +191837,6 @@ FCD5D9 (base 16) Shenzhen SDMC Technology CO.,Ltd. Shanghai 200233 CN -3C-58-5D (hex) Sagemcom Broadband SAS -3C585D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 38-95-92 (hex) Tendyron Corporation 389592 (base 16) Tendyron Corporation 1810,Tower B,Jin-ma,Building,17 East Qing Hua Road @@ -190544,12 +192383,6 @@ F87907 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -7C-E8-7F (hex) Sagemcom Broadband SAS -7CE87F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-87-1C (hex) Motorola Mobility LLC, a Lenovo Company 68871C (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -193028,12 +194861,6 @@ A01842 (base 16) Comtrend Corporation New Taipei City, Taiwan 24159 TW -50-6F-0C (hex) Sagemcom Broadband SAS -506F0C (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-86-60 (hex) Arcadyan Corporation D48660 (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -193118,12 +194945,6 @@ D86D17 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Reno NV 89507 US -74-5D-43 (hex) BSH Hausgeraete GmbH -745D43 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - 00-0B-A5 (hex) Quasar Cipta Mandiri, PT 000BA5 (base 16) Quasar Cipta Mandiri, PT Jl. Palasari 9A @@ -194786,12 +196607,6 @@ A0D0DC (base 16) Amazon Technologies Inc. Charlotte NC 28273 US -20-9A-7D (hex) Sagemcom Broadband SAS -209A7D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 50-C6-8E (hex) Biwin Semiconductor (HK) Company Limted 50C68E (base 16) Biwin Semiconductor (HK) Company Limted 5th/F., Block 4, Tongfuyu Industrial Park, Tanglang, Xili, Nanshan @@ -195620,12 +197435,6 @@ C80739 (base 16) NAKAYO Inc Santa Clara CA 95054 US -A8-6A-BB (hex) Sagemcom Broadband SAS -A86ABB (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 24-43-E2 (hex) DASAN Network Solutions 2443E2 (base 16) DASAN Network Solutions DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu @@ -195842,12 +197651,6 @@ BCFF21 (base 16) Smart Code(shenzhen)Technology Co.,Ltd Dallas TX 75243 US -F0-81-75 (hex) Sagemcom Broadband SAS -F08175 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 1C-63-BF (hex) SHENZHEN BROADTEL TELECOM CO.,LTD 1C63BF (base 16) SHENZHEN BROADTEL TELECOM CO.,LTD No.14-1, Tongqing Road, Baolong street, Longgang District @@ -196562,12 +198365,6 @@ A0946A (base 16) Shenzhen XGTEC Technology Co,.Ltd. Brentwood Essex 08854 GB -5C-B1-3E (hex) Sagemcom Broadband SAS -5CB13E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E4-AA-EA (hex) Liteon Technology Corporation E4AAEA (base 16) Liteon Technology Corporation 4F, 90, Chien 1 Road @@ -197252,12 +199049,6 @@ C4F7D5 (base 16) Cisco Systems, Inc New Taipei City, Taiwan 24159 TW -D0-57-94 (hex) Sagemcom Broadband SAS -D05794 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 04-D9-F5 (hex) ASUSTek COMPUTER INC. 04D9F5 (base 16) ASUSTek COMPUTER INC. 15,Li-Te Rd., Peitou, Taipei 112, Taiwan @@ -197840,12 +199631,6 @@ D45800 (base 16) Fiberhome Telecommunication Technologies Co.,LTD Basking Ridge 07030 US -34-6B-46 (hex) Sagemcom Broadband SAS -346B46 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-85-E6 (hex) Guangdong Asano Technology CO.,Ltd. 8485E6 (base 16) Guangdong Asano Technology CO.,Ltd. Changsheng Road, Songxia Industrial Park, Songgang, Shishan Town, Nanhai @@ -224015,12 +225800,6 @@ E80B13 (base 16) Akib Systems Taiwan, INC 64625 BENSHEIM DE -00-10-7F (hex) CRESTRON ELECTRONICS, INC. -00107F (base 16) CRESTRON ELECTRONICS, INC. - 15 Volvo Drive - Rockleigh NJ 07647 - US - 00-10-D4 (hex) STORAGE COMPUTER CORPORATION 0010D4 (base 16) STORAGE COMPUTER CORPORATION 11 RIVERSIDE STREET @@ -227528,12 +229307,6 @@ D825DF (base 16) CAME UK Castle Donington West Midlands DE74 2US GB -D4-B5-CD (hex) Sagemcom Broadband SAS -D4B5CD (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-49-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD 684983 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -227852,12 +229625,6 @@ C4BD8D (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Shenzhen Guangdong 518000 CN -30-F6-00 (hex) Sagemcom Broadband SAS -30F600 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-5F-41 (hex) Intel Corporate 985F41 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -229247,12 +231014,6 @@ BCB4FD (base 16) NXP Semiconductor (Tianjin) LTD. SHANGHAI 201114 CN -7C-51-84 (hex) Unis Flash Memory Technology(Chengdu)Co.,Ltd. -7C5184 (base 16) Unis Flash Memory Technology(Chengdu)Co.,Ltd. - No. 2110, 21st Floor, Unit 1, Building 1, No. 505, West Section of Fucheng Avenue, High Tech. Zone, Chengdu City, Sichuan Province, China. - Chengdu Sichuan 610000 - CN - E8-06-EB (hex) ShieldSOS LLC E806EB (base 16) ShieldSOS LLC 3700 Kyle Crossing, Suite 500 @@ -231128,9 +232889,6 @@ DC6279 (base 16) TP-Link Systems Inc Irvine CA 92606 US -EC-34-E2 (hex) Private -EC34E2 (base 16) Private - D0-32-C3 (hex) D-Link Corporation D032C3 (base 16) D-Link Corporation No.289, Sinhu 3rd Rd., Neihu District, @@ -231407,18 +233165,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 @@ -231437,17 +233183,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 @@ -231503,6 +233255,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 @@ -231533,6 +233303,30 @@ A08E24 (base 16) eero inc. San Francisco CA 94107 US +B4-20-46 (hex) eero inc. +B42046 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +C0-36-53 (hex) eero inc. +C03653 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +D4-05-DE (hex) eero inc. +D405DE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +9C-0B-05 (hex) eero inc. +9C0B05 (base 16) eero inc. + 660 3rd Street + 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 @@ -231545,36 +233339,132 @@ A08E24 (base 16) eero inc. 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 +5C-A5-BC (hex) eero inc. +5CA5BC (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -B4-20-46 (hex) eero inc. -B42046 (base 16) eero inc. +A8-B0-88 (hex) eero inc. +A8B088 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C0-36-53 (hex) eero inc. -C03653 (base 16) eero inc. +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 San Francisco CA 94107 US -D4-05-DE (hex) eero inc. -D405DE (base 16) eero inc. +24-F3-E3 (hex) eero inc. +24F3E3 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -9C-0B-05 (hex) eero inc. -9C0B05 (base 16) eero inc. +E4-19-7F (hex) eero inc. +E4197F (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US +FC-3D-73 (hex) eero inc. +FC3D73 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +14-79-F3 (hex) China Mobile Group Device Co.,Ltd. +1479F3 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +3C-57-4F (hex) China Mobile Group Device Co.,Ltd. +3C574F (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 + Beijing 100053 + CN + +40-62-EA (hex) China Mobile Group Device Co.,Ltd. +4062EA (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E0-45-6D (hex) China Mobile Group Device Co.,Ltd. +E0456D (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +50-8C-F5 (hex) China Mobile Group Device Co.,Ltd. +508CF5 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + 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 + +94-6D-AE (hex) Mellanox Technologies, Inc. +946DAE (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +FC-6A-1C (hex) Mellanox Technologies, Inc. +FC6A1C (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +A0-88-C2 (hex) Mellanox Technologies, Inc. +A088C2 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 7C-49-CF (hex) eero inc. 7C49CF (base 16) eero inc. 660 3rd Street @@ -231599,120 +233489,6 @@ DC69B5 (base 16) eero inc. 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 - -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 - -5C-A5-BC (hex) eero inc. -5CA5BC (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -A8-B0-88 (hex) eero inc. -A8B088 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -88-67-46 (hex) eero inc. -886746 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -24-F3-E3 (hex) eero inc. -24F3E3 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -E4-19-7F (hex) eero inc. -E4197F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -FC-3D-73 (hex) eero inc. -FC3D73 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -14-79-F3 (hex) China Mobile Group Device Co.,Ltd. -1479F3 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -3C-57-4F (hex) China Mobile Group Device Co.,Ltd. -3C574F (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - 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 - Beijing 100053 - CN - -40-62-EA (hex) China Mobile Group Device Co.,Ltd. -4062EA (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -E0-45-6D (hex) China Mobile Group Device Co.,Ltd. -E0456D (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -50-8C-F5 (hex) China Mobile Group Device Co.,Ltd. -508CF5 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 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 @@ -231731,30 +233507,6 @@ E0456D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -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 - -94-6D-AE (hex) Mellanox Technologies, Inc. -946DAE (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -FC-6A-1C (hex) Mellanox Technologies, Inc. -FC6A1C (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -A0-88-C2 (hex) Mellanox Technologies, Inc. -A088C2 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - E0-9D-73 (hex) Mellanox Technologies, Inc. E09D73 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -231767,6 +233519,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 @@ -231779,11 +233537,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 @@ -231791,18 +233549,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 @@ -231845,18 +233603,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 @@ -231869,28 +233615,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 @@ -231905,12 +233663,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 @@ -231935,16 +233687,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. @@ -231977,10 +233723,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. @@ -232001,24 +233747,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 @@ -232037,11 +233777,11 @@ D0C67F (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -A0-39-F9 (hex) Sagemcom Broadband SAS -A039F9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +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 B4-89-31 (hex) Silicon Laboratories B48931 (base 16) Silicon Laboratories @@ -232049,12 +233789,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 @@ -232073,11 +233807,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 @@ -232085,11 +233825,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 @@ -232097,35 +233837,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. @@ -232157,6 +233903,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 @@ -232175,18 +233939,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 @@ -232211,12 +233963,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 @@ -232247,36 +233993,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 @@ -232289,12 +234005,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 @@ -232313,17 +234041,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 @@ -232331,6 +234071,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 @@ -232355,12 +234101,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 @@ -232379,18 +234137,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 @@ -232409,11 +234155,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. @@ -232433,17 +234185,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 @@ -232457,12 +234209,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 @@ -232523,10 +234269,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 @@ -232547,6 +234293,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 @@ -232601,6 +234353,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 @@ -232613,12 +234371,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 @@ -232637,12 +234389,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 @@ -232697,6 +234443,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 @@ -232886,10 +234638,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. @@ -232898,6 +234650,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 @@ -232934,12 +234698,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 @@ -232952,24 +234710,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 @@ -232982,6 +234722,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 @@ -233000,20 +234752,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 @@ -233030,11 +234788,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 @@ -233054,24 +234818,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 @@ -233090,17 +234860,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. @@ -233126,12 +234890,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 @@ -233141,42 +234899,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 @@ -233213,28 +234935,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. @@ -233255,28 +234983,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 @@ -233291,23 +235013,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. @@ -233315,6 +235037,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 @@ -233351,6 +235091,48 @@ D89999 (base 16) TECNO MOBILE LIMITED 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 @@ -233363,12 +235145,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 @@ -233381,10 +235157,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. @@ -233399,40 +235175,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. @@ -233447,18 +235217,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, @@ -233471,17 +235229,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 @@ -233495,35 +235247,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 @@ -233543,12 +235331,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. @@ -233567,42 +235349,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. @@ -233615,17 +235367,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 @@ -233645,10 +235391,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 @@ -233657,6 +235409,30 @@ CCC530 (base 16) AzureWave Technology Inc. Piscataway NJ 08554 US +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 + Taipei 114 + TW + +A4-04-50 (hex) nFore Technology Co., Ltd. +A40450 (base 16) nFore Technology Co., Ltd. + 5F, NO 31, Ln 258, Rulguang Rd + Taipei Neihu District 11491 + TW + +00-17-53 (hex) nFore Technology Co., Ltd. +001753 (base 16) nFore Technology Co., Ltd. + 5F, NO 31, Ln 258, Rulguang Rd + Taipei Neihu District 11491 + TW + +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 @@ -233669,41 +235445,35 @@ CCC530 (base 16) AzureWave Technology Inc. ESSEX England CO3 8PH GB +58-8C-CF (hex) Silicon Laboratories +588CCF (base 16) Silicon Laboratories + 400 West Cesar Chavez + 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 -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 - Taipei 114 - TW - -A4-04-50 (hex) nFore Technology Co., Ltd. -A40450 (base 16) nFore Technology Co., Ltd. - 5F, NO 31, Ln 258, Rulguang Rd - Taipei Neihu District 11491 - TW +60-A1-FE (hex) HPRO +60A1FE (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 + US -00-17-53 (hex) nFore Technology Co., Ltd. -001753 (base 16) nFore Technology Co., Ltd. - 5F, NO 31, Ln 258, Rulguang Rd - Taipei Neihu District 11491 - TW +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 @@ -233711,41 +235481,23 @@ B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD 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 - 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 - 400 West Cesar Chavez - Austin TX 78701 +18-A0-84 (hex) Apple, Inc. +18A084 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -A4-18-94 (hex) IQSIGHT B.V. -A41894 (base 16) IQSIGHT B.V. - Achtseweg Zuid 173 - Eindhoven 5651 GW - NL +58-2A-93 (hex) Apple, Inc. +582A93 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US 64-C9-05 (hex) Apple, Inc. 64C905 (base 16) Apple, Inc. @@ -233753,6 +235505,36 @@ A41894 (base 16) IQSIGHT B.V. 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 @@ -233771,36 +235553,18 @@ A41894 (base 16) IQSIGHT B.V. 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 - -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 +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 -50-63-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -506382 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +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 -E8-FC-5F (hex) Ruckus Wireless -E8FC5F (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US - E8-68-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD E868B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, @@ -233813,28 +235577,28 @@ B0F079 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD DONG GUAN GUANG DONG 523860 CN +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 + +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 + 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 - -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. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +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. @@ -233849,10 +235613,22 @@ 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 +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 00-1D-19 (hex) Arcadyan Corporation @@ -233861,24 +235637,18 @@ A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD Hsinchu 300 TW -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 - -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 - C0-6B-C7 (hex) Gallagher Group Limited C06BC7 (base 16) Gallagher Group Limited 181 Kahikatea Drive Hamilton Waikato 3204 NZ +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 + BC-C4-36 (hex) Nokia BCC436 (base 16) Nokia 600 March Road @@ -233891,6 +235661,12 @@ DCB87D (base 16) Hewlett Packard Enterprise San Jose CA 95002 US +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 + 24-7E-7F (hex) D-Fend Solutions A.D Ltd 247E7F (base 16) D-Fend Solutions A.D Ltd 13 Zarhin st @@ -233915,36 +235691,6 @@ DCB87D (base 16) Hewlett Packard Enterprise 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 @@ -233957,12 +235703,6 @@ CC1EAB (base 16) LEDATEL sp. z o.o. i Wspólnicy sp.k 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 @@ -233980,3 +235720,585 @@ B89734 (base 16) Silicon Laboratories Yongan Community, the south of Lanling Road, Danyang Development Distinct zhenjiang jiangsu 212300 CN + +80-6D-DE (hex) Beken Corporation +806DDE (base 16) Beken Corporation + Building 41, Capital of Tech Leaders, 1387 Zhangdong Road, Zhangjiang High-Tech Park, Pudong New District + Shanghai 201203 + CN + +68-1D-4C (hex) Kontron eSystems GmbH +681D4C (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +24-68-00 (hex) Samsung Electronics Co.,Ltd +246800 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +F0-67-B1 (hex) Samsung Electronics Co.,Ltd +F067B1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +90-0A-48 (hex) Samsung Electronics Co.,Ltd +900A48 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +EC-B9-A5 (hex) Huawei Device Co., Ltd. +ECB9A5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +04-80-1A (hex) Huawei Device Co., Ltd. +04801A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +4C-D7-3A (hex) ShenZhen XinZhongXin Technology Co., Ltd +4CD73A (base 16) ShenZhen XinZhongXin Technology Co., Ltd + Building A2,Donghuan Industrial Zone,No.293,Nanpu Road,Xinqiao Sub-district,Bao'an District,Shenzhen City,Guangdong Province + ShenZhen 518104 + CN + +9C-51-87 (hex) SUNITEC TECHNOLOGY CO.,LIMITED +9C5187 (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 + +FC-EB-7B (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCEB7B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +00-CC-05 (hex) HUAWEI TECHNOLOGIES CO.,LTD +00CC05 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E4-CA-5F (hex) Murata Manufacturing Co., Ltd. +E4CA5F (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP + +B4-15-84 (hex) Samsung Electronics Co.,Ltd +B41584 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +34-DD-CC (hex) Google, Inc. +34DDCC (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +CC-61-46 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +CC6146 (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 + +38-87-9C (hex) Ei Electronics +38879C (base 16) Ei Electronics + Unit 40-47 Shannon Industrial Estate + Shannon Co. Clare V14 H020 + IE + +F4-20-4D (hex) Mellanox Technologies, Inc. +F4204D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +E8-F6-74 (hex) Jiang Su Fulian Communication Technology Co.,Ltd +E8F674 (base 16) Jiang Su Fulian Communication Technology Co.,Ltd + Yongan Community, the south of Lanling Road, Danyang Development Distinct + zhenjiang jiangsu 212300 + CN + +7C-51-84 (hex) Unis Flash Memory Technology(Chengdu)Co.,Ltd. +7C5184 (base 16) Unis Flash Memory Technology(Chengdu)Co.,Ltd. + Room 302, Block B, Ziguang Xinyun, No. 2555 Yizhou Avenue, High-tech Zone, + Chengdu Sichuan 610000 + CN + +04-1C-6C (hex) Intel Corporate +041C6C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +10-9A-BA (hex) Intel Corporate +109ABA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +AC-05-C7 (hex) Intel Corporate +AC05C7 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +78-64-F0 (hex) Beijing Soynetic Co., Ltd +7864F0 (base 16) Beijing Soynetic Co., Ltd + Room 108-60, 1st Floor, Building 4, Yard 1, Kechuang 10th Street Beijing Economic and Technological Development Zone, Beijing + Beijing Beijing 100176 + CN + +68-22-9F (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +68229F (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN + +78-EC-B5 (hex) Ruijie Networks Co.,LTD +78ECB5 (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN + +44-EF-26 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +44EF26 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 + CN + +68-92-68 (hex) Motorola Mobility LLC, a Lenovo Company +689268 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +EC-34-E2 (hex) Yasmina Labs Trading FZE +EC34E2 (base 16) Yasmina Labs Trading FZE + LB182702WS08, Jebel Ali Freezone + Dubai Dubai LB182702WS08 + AE + +14-3E-C2 (hex) Intel Corporate +143EC2 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +F4-25-3C (hex) eero inc. +F4253C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +1C-23-A2 (hex) FRITZ! Technology GmbH +1C23A2 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +9C-86-2B (hex) MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. +9C862B (base 16) MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. + INNOPLEX, NO. 2A, MEDAN BAYAN LEPAS, BAYAN LEPAS TECHNOPLEX + BAYAN LEPAS PENANG 11900 + MY + +E0-D3-F0 (hex) AltoBeam Inc. +E0D3F0 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +10-F1-C7 (hex) Tachyon Networks Inc +10F1C7 (base 16) Tachyon Networks Inc + 4783 W Stoney Brook Ln + Highland UT 84003 + US + +E4-FF-69 (hex) Holiday Robotics +E4FF69 (base 16) Holiday Robotics + 4F, 70, Nonhyeon-ro 85-gil, Gangnam-gu + Seoul 06234 + KR + +4C-6E-44 (hex) IEEE Registration Authority +4C6E44 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +30-6D-34 (hex) Wu Qi Technologies,Inc. +306D34 (base 16) Wu Qi Technologies,Inc. + 14/F, 107 Middle Road, Xiantao Big Data Valley, Yubei District + Chongqing Chongqing 401120 + CN + +B8-9D-E5 (hex) ASIX Electronics Corporation +B89DE5 (base 16) ASIX Electronics Corporation + 4F, No. 8, Hsin Ann Road, Hsinchu Science Park + Hsinchu 30078 + TW + +E4-98-E0 (hex) Tonly Technology Co. Ltd +E498E0 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 + CN + +A8-21-C8 (hex) shenzhen phoenix telecom technology Co.,Ltd. +A821C8 (base 16) shenzhen phoenix telecom technology Co.,Ltd. + Bldg A,Dedi Industrial Park,Jian'an Road,High-tech Development Zone,Fuhai Street,Bao'an District,Shenzhen,Guangdong,China + shenzhen 518103 + CN + +78-BB-5C (hex) Nokia Solutions and Networks India Private Limited +78BB5C (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN + +48-13-89 (hex) Mellanox Technologies, Inc. +481389 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +18-8A-F1 (hex) LEDVANCE, LLC +188AF1 (base 16) LEDVANCE, LLC + 181 Ballardvale StreetSte 203 + Wilmington, MA MA 01887 + US + +84-98-A7 (hex) Texas Instruments +8498A7 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +80-A2-FC (hex) AzureWave Technology Inc. +80A2FC (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +90-30-D6 (hex) Quectel Wireless Solutions Co.,Ltd. +9030D6 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +44-32-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD +44321D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +3C-C8-01 (hex) Shenzhen Sundray Technologies company Limited +3CC801 (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 + Shenzhen GuangDong 518057 + CN + +A8-DD-EC (hex) Hangzhou BroadLink Technology Co., Ltd +A8DDEC (base 16) Hangzhou BroadLink Technology Co., Ltd + Room 101,1/F,Unit C,Building 1,No.57 Jiang'er Road,Changhe Street,Binjiang District + Hangzhou ZheJiang 310052 + CN + +48-98-AB (hex) Wistron InfoComm(Chongqing)Co.,Ltd. +4898AB (base 16) Wistron InfoComm(Chongqing)Co.,Ltd. + No.18-9 Baohong Avenue, Wangjia Street, Yubei District, Chongqing + Chongqing Yubei 4001120 + CN + +28-87-AF (hex) Advantech Technology (CHINA) Co., Ltd. +2887AF (base 16) Advantech Technology (CHINA) Co., Ltd. + No.666, Han-Pu Rd. Yu-Shan + Kun-Shan Jiang Su 215316 + CN + +50-E4-67 (hex) Ring LLC +50E467 (base 16) Ring LLC + 1523 26th St + Santa Monica CA 90404 + US + +4C-4A-B4 (hex) Juniper Networks +4C4AB4 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +94-46-67 (hex) Cisco Systems, Inc +944667 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +8C-14-2A (hex) Cisco Systems, Inc +8C142A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +6C-DF-D9 (hex) Concept2, Inc. +6CDFD9 (base 16) Concept2, Inc. + 105 Industrial Park Drive + Morrisville VT 05661 + US + +38-F4-06 (hex) Jinan USR IOT Technology Limited +38F406 (base 16) Jinan USR IOT Technology Limited + Floor F1 & Part of Floor F2, Building No. 9,Diya shuang chuang Industrial Zone, No.2566,Century Main Road,Gaoxin District Jinan,Shandong China + Shandong Jinan 250014 + CN + +20-AE-B6 (hex) Huawei Device Co., Ltd. +20AEB6 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +7C-16-2A (hex) Huawei Device Co., Ltd. +7C162A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +E0-A4-47 (hex) zte corporation +E0A447 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +B0-DE-31 (hex) Samsung Electronics Co.,Ltd +B0DE31 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +34-6B-46 (hex) Sagemcom Broadband SAS +346B46 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D0-57-94 (hex) Sagemcom Broadband SAS +D05794 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +5C-B1-3E (hex) Sagemcom Broadband SAS +5CB13E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +F0-81-75 (hex) Sagemcom Broadband SAS +F08175 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +A8-6A-BB (hex) Sagemcom Broadband SAS +A86ABB (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +7C-E8-7F (hex) Sagemcom Broadband SAS +7CE87F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +3C-58-5D (hex) Sagemcom Broadband SAS +3C585D (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +B8-6A-F1 (hex) Sagemcom Broadband SAS +B86AF1 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +CC-58-30 (hex) Sagemcom Broadband SAS +CC5830 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D4-B5-CD (hex) Sagemcom Broadband SAS +D4B5CD (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +30-F6-00 (hex) Sagemcom Broadband SAS +30F600 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +80-E8-A4 (hex) zte corporation +80E8A4 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +20-9A-7D (hex) Sagemcom Broadband SAS +209A7D (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +50-6F-0C (hex) Sagemcom Broadband SAS +506F0C (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +DC-CB-35 (hex) EM Microelectronic +DCCB35 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +5C-5A-35 (hex) eero inc. +5C5A35 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +00-10-7F (hex) CRESTRON ELECTRONICS, INC. +00107F (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 + US + +94-88-35 (hex) CRESTRON ELECTRONICS, INC. +948835 (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 + US + +10-7A-2A (hex) Microsoft Corporation +107A2A (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +74-D8-09 (hex) Microsoft Corporation +74D809 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +E0-4F-95 (hex) Sagemcom Broadband SAS +E04F95 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +A0-39-F9 (hex) Sagemcom Broadband SAS +A039F9 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +54-B2-7E (hex) Sagemcom Broadband SAS +54B27E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +50-22-C9 (hex) Bel Power Solutions, Inc. +5022C9 (base 16) Bel Power Solutions, Inc. + 1326 South Wolf Road + Wheeling IL 60090 + US + +74-5D-43 (hex) BSH Hausgeräte GmbH +745D43 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE + +F8-63-47 (hex) Sichuan AI-Link Technology Co., Ltd. +F86347 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +24-64-04 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +246404 (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 + +84-01-6E (hex) Honor Device Co., Ltd. +84016E (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 + +B4-90-E5 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +B490E5 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +B8-DC-7D (hex) VusionGroup +B8DC7D (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +20-1F-55 (hex) DJI Osmo Technology Co., Ltd. +201F55 (base 16) DJI Osmo Technology Co., Ltd. + Tower 1, DJI Sky City, No. 55 Xianyuan Road, Xili Community, Xili Subdistrict, Nanshan District + shenzhen 518057 + CN + +90-4D-E2 (hex) Apple, Inc. +904DE2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +EC-DC-AA (hex) Apple, Inc. +ECDCAA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +08-24-0B (hex) Apple, Inc. +08240B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +0C-0E-C1 (hex) IEEE Registration Authority +0C0EC1 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +8C-65-EC (hex) TUBITAK MAM +8C65EC (base 16) TUBITAK MAM + Gebze Yerleskesi Marmara Arastirma Merkezi + KOCAELI TR 41470 + TR diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 72b11878d4d5b..381396688f0d9 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -95,12 +95,6 @@ C00000-CFFFFF (base 16) DanuTech Europe Kft Meguro-ku Tokyo-to 152-0035 JP -D0-15-BB (hex) Bluewaves Mobility Innovation Inc -D00000-DFFFFF (base 16) Bluewaves Mobility Innovation Inc - Unit 402-105 Gordon Baker Rd - Toronto Ontario M2H3P8 - CA - 88-A6-EF (hex) ShenZhen KZIot Technology LLC. 600000-6FFFFF (base 16) ShenZhen KZIot Technology LLC. 12th Floor, Block A, Kelu Building, Baoshen Road, Songpingshan Community, Xili Street, Nanshan District, @@ -1250,12 +1244,6 @@ E00000-EFFFFF (base 16) OUTFORM Miami FL 33137 US -28-36-13 (hex) shenzhen technology limited -700000-7FFFFF (base 16) shenzhen technology limited - 903,No. 1 Shifeng Building, Xinzhuang Community Villa Road, Matian Street, Guangming District, Shenzhen City - Shenzhen Guangdong 518000 - CN - 78-5E-E8 (hex) Guangdong COROS Sports Technology Co., Ltd 600000-6FFFFF (base 16) Guangdong COROS Sports Technology Co., Ltd ROOM 601 ROOM 701,BLD.2,NO.2,SCIENCE AND TECHNOLOGY 9 RD,SONGSHAN LAKE HI-TECH ZONE @@ -1862,12 +1850,6 @@ C00000-CFFFFF (base 16) Ark Vision Systems GmbH & Co. KG Merenberg Hessen 35799 DE -18-FD-CB (hex) Staclar, Inc. -300000-3FFFFF (base 16) Staclar, Inc. - 2093 Philadelphia Pike - Claymont DE 19703 - US - 18-FD-CB (hex) KWANG YANG MOTOR CO.,LTD E00000-EFFFFF (base 16) KWANG YANG MOTOR CO.,LTD NO. 35, WAN HSING ST., SAN MIN DIST., KAOHSIUNG, TAIWAN, R.O.C @@ -2036,12 +2018,6 @@ C00000-CFFFFF (base 16) Anacove LLC LA JOLLA CA 92037 US -30-49-50 (hex) Ledworks SRL -A00000-AFFFFF (base 16) Ledworks SRL - Via Tortona 37 - Milano Milano 20144 - IT - 30-49-50 (hex) ATLI WORLD LIMITED 100000-1FFFFF (base 16) ATLI WORLD LIMITED 306 Beverley Commercial Center, 87-105 Chatham Road, TST, @@ -7661,6 +7637,132 @@ C00000-CFFFFF (base 16) EGSTON Power Electronics GmbH Eggenburg 3730 AT +20-B3-7F (hex) Shenzhen Hengbang Xinchuang Technology Co.,Ltd +B00000-BFFFFF (base 16) Shenzhen Hengbang Xinchuang Technology Co.,Ltd + 2nd Floor East, Workshop 3A, No. 268 Baoshi East Road, Shiyan Sub-district, Bao'an District, Shenzhen + Shenzhen Guangdong 518108 + CN + +80-1D-0D (hex) WARNER ELECTRONICS (I) PVT. LTD. +900000-9FFFFF (base 16) WARNER ELECTRONICS (I) PVT. LTD. + Plot No. A114/6, Five Star Industrial Area, Mumbai – Nagpur Highway, Shendra + Aurangabad maharastra 431007 + IN + +CC-E7-DE (hex) Chengdu Vantron Technology Co., Ltd. +500000-5FFFFF (base 16) Chengdu Vantron Technology Co., Ltd. + 5th Floor, 1st Building, No.9 3rd WuKe East Street, WuHou District + Chengdu Sichuan 610045 + CN + +CC-E7-DE (hex) Shenzhen Xingyi Intelligent Technology Co.,Ltd +E00000-EFFFFF (base 16) Shenzhen Xingyi Intelligent Technology Co.,Ltd + 905,Bldg 1,Xinyi Lingyu R&D Center No.30,Honglang Beier Rd,Zone 69,Xingdong Community,Xin'an Sub-district,Bao'an Dist,Shenzhen,Guangdong,China + Shenzhen 518000 + CN + +CC-E7-DE (hex) Kaze.AI Technology Co.,Ltd. +100000-1FFFFF (base 16) Kaze.AI Technology Co.,Ltd. + Romm 5D, 5th Floor, Block A, Central Avenue, Xixiang Subdistrict, Bao'an District, Shenzhen + Romm 5D, 5th Floor, Block A, Central Avenue, Xixiang Subdistrict, Bao'an District, Shenzhen 518100 + CN + +4C-6E-44 (hex) Shenzhen iTayga Technology Co.,Ltd +800000-8FFFFF (base 16) Shenzhen iTayga Technology Co.,Ltd + Room 516, Building A, Huafeng Huayuan Technology Innovation Park, Xixiang Street, Bao'an District, Shenzhen + Shenzhen 粤 518100 + CN + +4C-6E-44 (hex) Shenzhen Langji Guangnian Technology Co., Ltd. +500000-5FFFFF (base 16) Shenzhen Langji Guangnian Technology Co., Ltd. + Unit 519, Building A, Jiada R&D Building, No. 5 Songpingshan Road, Songpingshan Community, Xili Street, Nanshan District, Shenzhen, Guangdong, China + Shenzhen 518000 + CN + +18-FD-CB (hex) Blahaj Studio +300000-3FFFFF (base 16) Blahaj Studio + Kurt-Schumacher-Str. 13 + Germering 82110 + DE + +4C-6E-44 (hex) NovaFly LLC +B00000-BFFFFF (base 16) NovaFly LLC + 2108 N ST STE N, SACRAMENTO, CA 95816 + SACRAMENTO CA 95816 + US + +A4-4F-3E (hex) RINVENT INDUSTRIES PRIVATE LIMITED +200000-2FFFFF (base 16) RINVENT INDUSTRIES PRIVATE LIMITED + 9-272/1A Angalakuduru + Tenali Andhra Pradesh 522211 + IN + +A4-4F-3E (hex) JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED +C00000-CFFFFF (base 16) JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED + FLAT/RM 1610, 16/F, SEAPOWER TOWER CONCORDIA PLAZA,1 SCIENCE MUSEUM ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG + HONG KONG 999077 + HK + +30-49-50 (hex) Illucere Srl +A00000-AFFFFF (base 16) Illucere Srl + Via Tortona 37 + Milano Milano 20144 + IT + +F8-C9-D6 (hex) VT100 SRL +B00000-BFFFFF (base 16) VT100 SRL + Viale Dell'Artigianato 4 + Caldiero ITALY 37042 + IT + +A4-4F-3E (hex) ShenZhen Chainway Information Technology Co., Ltd. +E00000-EFFFFF (base 16) ShenZhen Chainway Information Technology Co., Ltd. + 9F Building2, Phase2, Gaoxinqi Industrial Park , Bao'an District + ShenZhen GuangDong 518102 + CN + +F8-C9-D6 (hex) Shanghai Innovatech Information Technology Co., Ltd. +600000-6FFFFF (base 16) Shanghai Innovatech Information Technology Co., Ltd. + Yijing Technology, 17th Floor, Building G, Weijing Center, Gu Dai Lu, Xinzhuang Town, Minhang District + Shanghai Shanghai 201199 + CN + +F8-75-28 (hex) SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD +B00000-BFFFFF (base 16) SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD + No.826,Zone 1,Block B,Famous industrial product display purchasing center,Baoyuan Road,Xixiang,Bao'an Dis., Shenzhen,P.R.China + shenzhen China 518102 + CN + +D0-15-BB (hex) Bluewaves Mobility Innovation Inc +D00000-DFFFFF (base 16) Bluewaves Mobility Innovation Inc + Suite 702, 2450 Victoria Park Ave + North York Ontario M2J4A2 + CA + +28-36-13 (hex) SHENZHEN OFEIXIN TECHNOLOGY LIMITED +700000-7FFFFF (base 16) SHENZHEN OFEIXIN TECHNOLOGY LIMITED + 902, Building 4, Guangming Fenghuang Plaza, Dongkeng Community, Fenghuang Street, Guangming District + Shenzhen 518000 + CN + +F8-75-28 (hex) SGSG Science&Technology Co., Ltd. Zhuhai +300000-3FFFFF (base 16) SGSG Science&Technology Co., Ltd. Zhuhai + Floor 9-15, Building 1, 199 Dingxing Road, High-tech Zone, Zhuhai City + Zhuhai Guangdong 519000 + CN + +F8-75-28 (hex) Ingersoll-Rand +E00000-EFFFFF (base 16) Ingersoll-Rand + 53 Frontage Road Suite 250 + Hampton NJ 08827 + US + +0C-0E-C1 (hex) Spintronica LLC +000000-0FFFFF (base 16) Spintronica LLC + Lubyanka M. Street + Moscow 101000 + RU + 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 @@ -8417,12 +8519,6 @@ C00000-CFFFFF (base 16) shenzhen newbridge communication equipment CO.,LTD Kunshan Jiangsu 215300 CN -6C-15-24 (hex) Magicyo Technology CO., LTD. -400000-4FFFFF (base 16) Magicyo Technology CO., LTD. - 7F, Tower A, YuZhou Building, No.78 North Keyuan - Shenzhen Nanshan District 518057 - CN - 6C-15-24 (hex) D-HOME SMAART 900000-9FFFFF (base 16) D-HOME SMAART 8, rue Edouard Herriot @@ -8669,12 +8765,6 @@ B00000-BFFFFF (base 16) F-Plus Mobile LLC shenzhen guangdong 518057 CN -DC-36-43 (hex) Shenzhen smart-core technology co.,ltd. -600000-6FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - DC-36-43 (hex) Wuhan Linptech Co. ,Ltd. 200000-2FFFFF (base 16) Wuhan Linptech Co. ,Ltd. 3F,Bldg 1A,Lianxiang Enterprise Center @@ -10895,12 +10985,6 @@ D00000-DFFFFF (base 16) Amiosec Ltd Tewkesbury Gloucestershire GL20 8DN GB -04-C3-E6 (hex) Innovusion Inc. -400000-4FFFFF (base 16) Innovusion Inc. - 4920 El Camino Real - Los Altos CA 94022 - US - 04-C3-E6 (hex) SLOC GmbH 800000-8FFFFF (base 16) SLOC GmbH Nikolaiplatz 4 @@ -13880,12 +13964,6 @@ E00000-EFFFFF (base 16) JP Morgan Chase Bank, N.A. boston 02210 US -74-25-84 (hex) Shenzhen smart-core technology co.,ltd. -D00000-DFFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - 74-25-84 (hex) EZECONET 600000-6FFFFF (base 16) EZECONET 268-26. Jidong-gil, Apo-eup @@ -14792,12 +14870,6 @@ B00000-BFFFFF (base 16) MyPlace Australia Pty Ltd 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 @@ -14810,6 +14882,153 @@ A00000-AFFFFF (base 16) ShenZhen C&D Electronics CO.Ltd. ShenZhen GuangDong 518000 CN +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 + +80-1D-0D (hex) GTL Tecnologia e Sistemas Ltda +400000-4FFFFF (base 16) GTL Tecnologia e Sistemas Ltda + Av. Marcos Penteado de Ulhôa Rodrigues 1119, 16 andar, sala 1611 + Barueri São Paulo 06460040 + BR + +80-1D-0D (hex) Drowsy Digital Inc +600000-6FFFFF (base 16) Drowsy Digital Inc + 1 COOK ST + WESTBOROUGH MA 01581 + US + +80-1D-0D (hex) SZ Spinning Power Top Boundary Technology Co.Ltd. +200000-2FFFFF (base 16) SZ Spinning Power Top Boundary Technology Co.Ltd. + 1261B Yingfeng Center, No.19 Haitian 2nd Road, Binhai Community, Yuehai Street, Nanshan District, Shenzhen + SHENZHEN GUANGDONG 518100 + CN + +CC-E7-DE (hex) Guangdong Sirivision Communication Technology Co.,LTD. +C00000-CFFFFF (base 16) Guangdong Sirivision Communication Technology Co.,LTD. + Building 6, No. 16 Nanmian Road, Humen Town + Dongguan City Guangdong Province 523899 + CN + +CC-E7-DE (hex) Octopus Energy Ltd +200000-2FFFFF (base 16) Octopus Energy Ltd + UK House, 5th Floor, United Kingdom House, 164-182 Oxford St + London W1D 1NN + GB + +CC-E7-DE (hex) Juke Audio +D00000-DFFFFF (base 16) Juke Audio + 112 20th street + Manhattan Beach CA 90266 + US + +CC-E7-DE (hex) Shenzhen Jooan Technology Co., Ltd +000000-0FFFFF (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 + +04-C3-E6 (hex) Seyond +400000-4FFFFF (base 16) Seyond + 160 San Gabriel Dr + Sunnyvale CA 94086 + US + +4C-6E-44 (hex) Windar Photonics A/S +C00000-CFFFFF (base 16) Windar Photonics A/S + Baldersbækvej 24 C, 2635 Ishøj + Ishøj Capital Region of Denmark 2635 + DK + +4C-6E-44 (hex) Private +400000-4FFFFF (base 16) Private + +6C-15-24 (hex) Magicyo Technology CO.,Ltd +400000-4FFFFF (base 16) Magicyo Technology CO.,Ltd + 7F, Tower A, YuZhou Building, No.78 North Keyuan + Shenzhen Nanshan District 518057 + CN + +A4-4F-3E (hex) Netshield Europe Srl +100000-1FFFFF (base 16) Netshield Europe Srl + Viale Bianca Maria 24 + Milano Italy 20129 + IT + +A4-4F-3E (hex) NTT sonority, Inc. +D00000-DFFFFF (base 16) NTT sonority, Inc. + 3-20-2, Nishishinjuku + Shinjuku-ku Tokyo 163-1432 + JP + +A4-4F-3E (hex) United Automotive Electronic Systems Co.,Ltd +000000-0FFFFF (base 16) United Automotive Electronic Systems Co.,Ltd + No.555 Rongqiao Road,Pudong district, Shanghai,China + Shanghai 201206 + CN + +A4-4F-3E (hex) Maven Pet Inc +600000-6FFFFF (base 16) Maven Pet Inc + 800 N King Street Suite 304 2873 Wilmington + Wilmington DE 19801 + US + +F8-C9-D6 (hex) Annapurna labs +500000-5FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +F8-C9-D6 (hex) DIAS Infrared GmbH +A00000-AFFFFF (base 16) DIAS Infrared GmbH + Pforzheimer Str. 21 + Dresden Saxony 01189 + DE + +F8-C9-D6 (hex) Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD +C00000-CFFFFF (base 16) Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD + Zhongzhen Building, No. 68 Luofang Road, Luohu District + Shenzhen Guangdong Province 518003 + CN + +F8-C9-D6 (hex) Ningbo Ming Sing Optical R&D Co.,Ltd +000000-0FFFFF (base 16) Ningbo Ming Sing Optical R&D Co.,Ltd + No. 365 Middle Jingu Road (west), Panhuo Street,Yinzhou District, Ningbo,Zhejiang Province,China + Ningbo Zhejiang Province 315100 + CN + +74-25-84 (hex) Shenzhen smart-core technology co.,ltd. +D00000-DFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +DC-36-43 (hex) Shenzhen smart-core technology co.,ltd. +600000-6FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-75-28 (hex) RLS d.o.o. +900000-9FFFFF (base 16) RLS d.o.o. + Pod vrbami 2 + Komenda 1218 + SI + +F8-75-28 (hex) KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. +600000-6FFFFF (base 16) KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. + No.369 Kangzhuang Road Zhoushi Town Kunshan City Jiangsu Province China 215300 + KUNSHAN Jiangsu 215300 + CN + +F8-75-28 (hex) Origin Acoustics, LLC +C00000-CFFFFF (base 16) Origin Acoustics, LLC + 6975 South Decatur Blvd, Suite 140 + Las Vegas NV 89118 + US + B8-4C-87 (hex) Altronix , Corp A00000-AFFFFF (base 16) Altronix , Corp 140 58th St. Bldg A, Ste 2N @@ -14966,12 +15185,6 @@ E00000-EFFFFF (base 16) Waterkotte GmbH Herne 44628 DE -C8-6B-BC (hex) Shenzhen smart-core technology co.,ltd. -700000-7FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - C8-6B-BC (hex) ZEUS C00000-CFFFFF (base 16) ZEUS 132, Annyeongnam-ro @@ -20837,12 +21050,6 @@ C00000-CFFFFF (base 16) Sichuan Zhongguang Lightning Protection Technologie REGULY 05-816 PL -10-06-48 (hex) Shenzhen smart-core technology co.,ltd. -B00000-BFFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - 10-06-48 (hex) Hong Kong BOZZ Co., Limited. 600000-6FFFFF (base 16) Hong Kong BOZZ Co., Limited. NO. 33 MONG KOK ROAD, KOWLOON @@ -22574,30 +22781,105 @@ E00000-EFFFFF (base 16) Smart Radar System, Inc 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 - 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 +80-1D-0D (hex) LOGICOM SA +C00000-CFFFFF (base 16) LOGICOM SA + 55 Rue de Lisbonne + PARIS 75008 + FR + +20-B3-7F (hex) Kawasaki Thermal Engineering Co.,Ltd. +E00000-EFFFFF (base 16) Kawasaki Thermal Engineering Co.,Ltd. + 1000 Aoji-cho + Kusatsu-shi Shiga 525-8558 + JP + +80-1D-0D (hex) LONGI METER CO.,LTD. +500000-5FFFFF (base 16) LONGI METER CO.,LTD. + No. 25 Guangming Road, Yinchuan (National Level) Economic and Technological Development Zone + Yinchuan Ningxia 750021 + CN + +CC-E7-DE (hex) Private +300000-3FFFFF (base 16) Private + +4C-6E-44 (hex) Swistec GmbH +A00000-AFFFFF (base 16) Swistec GmbH + Keldenicher Str. 18 + Bornheim 53332 + DE + +4C-6E-44 (hex) Quasonix +000000-0FFFFF (base 16) Quasonix + 6025 Schumacher Park Drive + West Chester OH 45069 + US + +CC-E7-DE (hex) Opal Camera Inc. +A00000-AFFFFF (base 16) Opal Camera Inc. + 150 POST STREET, SUITE 700 + SAN FRANCISCO CA 94108 + US + +CC-E7-DE (hex) Skylight +600000-6FFFFF (base 16) Skylight + 101a Clay Street #144 + San Francisco CA 94111 + US + +4C-6E-44 (hex) Accutrol LLC +200000-2FFFFF (base 16) Accutrol LLC + 21 Commerce Dr + Danbury CT 06810 + US + +A4-4F-3E (hex) ShenZhen hionetech Co,.ltd +400000-4FFFFF (base 16) ShenZhen hionetech Co,.ltd + 2112 Baoshan Times Building ,Minzhi Street + LonghuaDistrictShenzhen guangdong 518110 + CN + +10-06-48 (hex) Shenzhen smart-core technology co.,ltd. +B00000-BFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +C8-6B-BC (hex) Shenzhen smart-core technology co.,ltd. +700000-7FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-75-28 (hex) Myers Emergency Power Systems +800000-8FFFFF (base 16) Myers Emergency Power Systems + 44 S Commerce Way + Bethlehem PA 18017 + US + +F8-75-28 (hex) After Technologies +D00000-DFFFFF (base 16) After Technologies + Gaustadalleen 21 + Oslo 0349 + NO + +F8-75-28 (hex) Wuhan Xingtuxinke ELectronic Co.,Ltd +100000-1FFFFF (base 16) Wuhan Xingtuxinke ELectronic Co.,Ltd + NO.C3-8F,Software Park,Optics Valley,East Lake Development Zone,Wuhan,Hubei,China + Wuhan Hubei 430074 + CN + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -23195,12 +23477,6 @@ E00000-EFFFFF (base 16) UAB Brolis sensor technology Vilnius 14259 LT -D4-61-37 (hex) Shenzhen smart-core technology co.,ltd. -100000-1FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - D4-61-37 (hex) Robert Bosch Elektronikai Kft. 200000-2FFFFF (base 16) Robert Bosch Elektronikai Kft. Robert Bosch út 1. @@ -30026,6 +30302,165 @@ E00000-EFFFFF (base 16) Knit Sound Company Almaty Almaty 050022 KZ +80-1D-0D (hex) HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD +700000-7FFFFF (base 16) HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD + 99#, 8th Kenhui Road, Xinjie street, Xiaoshan District, Hangzhou, Zhejiang CN 311232 + HANGZHOU ZHEJIANG 311232 + CN + +80-1D-0D (hex) KbDevice,Inc. +000000-0FFFFF (base 16) KbDevice,Inc. + 22-2, Hontorocho, Shimogyo-ku, Kyoto-shi + Kyoto 600-8086 + JP + +80-1D-0D (hex) CRESTCHIC (UK) LIMITED +800000-8FFFFF (base 16) CRESTCHIC (UK) LIMITED + Second AvenueCentrum 100 + Burton upon Trent DE14 2WF + GB + +CC-E7-DE (hex) Shenzhen Qichang Intelligent Technology Co., Ltd. +900000-9FFFFF (base 16) Shenzhen Qichang Intelligent Technology Co., Ltd. + 13th F, Building 1, West Area, Hongrongyuan HonghuTechnology Park, No.22 Ping'an Road, ZhangxiCommunity, Guanhu Street + Shenzhen Guang dong 518000 + CN + +CC-E7-DE (hex) Shanghai Dabuziduo Information and Technology Co., Ltd. +700000-7FFFFF (base 16) Shanghai Dabuziduo Information and Technology Co., Ltd. + #818, #298 Guoxia Rd, Yangpu Dist + Shanghai Shanghai 200000 + CN + +CC-E7-DE (hex) Private +800000-8FFFFF (base 16) Private + +C4-82-72 (hex) NextSilicon +300000-3FFFFF (base 16) NextSilicon + Derekh Begin 33 + Gibatayim 5348303 + IL + +4C-6E-44 (hex) Panache DigiLife Limited +600000-6FFFFF (base 16) Panache DigiLife Limited + B-507, Raheja Plaza Premises CSL, LBS Marg, Ghatkopar West , Maharashtra 400086 + Mumbai Maharashtra 400086 + IN + +4C-6E-44 (hex) Qingting Intelligent Technology(Suzhou)Co.,Ltd. +300000-3FFFFF (base 16) Qingting Intelligent Technology(Suzhou)Co.,Ltd. + Room 302, 3rd Floor, R&D Building 9, No. 9, 1999 Songjia Road, Guoxiang Street, Wuzhong Economic Development Zone + Suzhou JiangSu 215000 + CN + +4C-6E-44 (hex) 1Home Solutions GmbH +D00000-DFFFFF (base 16) 1Home Solutions GmbH + Friedrichstrasse 155 + Berlin 10117 + DE + +4C-6E-44 (hex) Shenzhen Jooan Technology Co., Ltd +E00000-EFFFFF (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 + +4C-6E-44 (hex) Luxshare Electronic Technology (KunShan) Ltd +700000-7FFFFF (base 16) Luxshare Electronic Technology (KunShan) Ltd + No. 699 Jinshang Road, Jinxi Town, Kunshan City, Jiangsu Province + Kunshan Jiangsu 215300 + CN + +4C-6E-44 (hex) Shenzhen Xmitech Electronic Co.,Ltd +100000-1FFFFF (base 16) Shenzhen Xmitech Electronic Co.,Ltd + Room 8B1888, Block AB, New Energy Building, No.2239, Nanhai Avenue, Nanguang Community, Nanshan Street, Nanshan District, Shenzhen + Shenzhen 518054 + CN + +A4-4F-3E (hex) Annapurna labs +300000-3FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +A4-4F-3E (hex) Suzhou AIDomex Intelligent Technology Co., Ltd. +B00000-BFFFFF (base 16) Suzhou AIDomex Intelligent Technology Co., Ltd. + B422,18th.Zhanye RD. SIP. Suzhou China + Suzhou Jiangsu 215122 + CN + +F8-C9-D6 (hex) Annapurna labs +200000-2FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +A4-4F-3E (hex) CMCNI Co., Ltd +A00000-AFFFFF (base 16) CMCNI Co., Ltd + B-601 Hangang Xi TowerYangcheonro 401Gangseogu + Seoul 07528 + KR + +D4-61-37 (hex) Shenzhen smart-core technology co.,ltd. +100000-1FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-C9-D6 (hex) Beijing Mlink Technology Inc. +100000-1FFFFF (base 16) Beijing Mlink Technology Inc. + 5th Floor, North Lobby, Building B, East Side Science and Innovation Center, Phase III, Zhongguancun Dongsheng Science and Technology Park, Haidian District, Beijing + Beijing 100080 + CN + +F8-C9-D6 (hex) CPflight_srl +300000-3FFFFF (base 16) CPflight_srl + Via_Antica_Regina_24 + Tremezzina Como 22016 + IT + +F8-C9-D6 (hex) Dimetix AG +900000-9FFFFF (base 16) Dimetix AG + Degersheimerstrasse 14 + Herisau 9100 + CH + +F8-C9-D6 (hex) Lecip Arcontia AB +700000-7FFFFF (base 16) Lecip Arcontia AB + Mässans Gata 10 + Gothenburg 40224 + SE + +F8-75-28 (hex) Qube Cinema Technologies Pvt Ltd +000000-0FFFFF (base 16) Qube Cinema Technologies Pvt Ltd + 42 Dr Ranga Road + Chennai Tamil Nadu 600004 + IN + +F8-C9-D6 (hex) Shenzhen smart-core technology co.,ltd. +E00000-EFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +0C-0E-C1 (hex) tecget GmbH +B00000-BFFFFF (base 16) tecget GmbH + Schafjueckenweg 1 + Rastede 26180 + DE + +F8-75-28 (hex) PANASONIC AUTOMOTIVE SYSTEM MALAYSIA +700000-7FFFFF (base 16) PANASONIC AUTOMOTIVE SYSTEM MALAYSIA + PLOT 10, PHASE 4PRAI INDUSTRAIL ESTATE + PRAI PENANG 13600 + MY + +0C-0E-C1 (hex) DELTACAST.TV +100000-1FFFFF (base 16) DELTACAST.TV + Rue Gilles Magnee 92/6 + ANS 4430 + BE + C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG Basadingerstrasse 18 @@ -37630,3 +38065,141 @@ F4-A4-54 (hex) TRI WORKS Kingsley Avenue Ilfracombe Devon EX348ES GB + +80-1D-0D (hex) 802 Secure +D00000-DFFFFF (base 16) 802 Secure + 1285 66th Street + Emeryville CA 94608 + US + +80-1D-0D (hex) Luxshare Electronic Technology (KunShan) Ltd +A00000-AFFFFF (base 16) Luxshare Electronic Technology (KunShan) Ltd + No. 699 Jinshang Road, Jinxi Town, Kunshan City, Jiangsu Province + Kunshan Jiangsu 215300 + CN + +80-1D-0D (hex) Syrma SGS Technology +B00000-BFFFFF (base 16) Syrma SGS Technology + MEPTZ , TAMBARAM + Chennai Tamil Nadu 600045 + IN + +80-1D-0D (hex) Lecoo Technology Co.,Ltd. +300000-3FFFFF (base 16) Lecoo Technology Co.,Ltd. + Room 103, No. 1, Second Avenue, Tianjin Airport International Logistics Park, Tianjin Pilot Free TradeZone + Tianjin Tianjin 300000 + CN + +80-1D-0D (hex) Shanghai ReveISpark Technologies Co.,Ltd. +E00000-EFFFFF (base 16) Shanghai ReveISpark Technologies Co.,Ltd. + 2nd Floor,No.25-1 Hongcao Road,Xuhui District,Shanghai,China + Shanghai 200233 + CN + +20-B3-7F (hex) B810 SPA +000000-0FFFFF (base 16) B810 SPA + Via Enzo Lazzaretti, 2/1 + REGGIO EMILIA Reggio Emilia 42122 + IT + +CC-E7-DE (hex) 3D Computing +B00000-BFFFFF (base 16) 3D Computing + D102, 2nd floor, street no 5, laxmi nagar, delhi 110092 + Delhi Delhi 110092 + IN + +80-1D-0D (hex) Hörmann Warnsysteme GmbH +100000-1FFFFF (base 16) Hörmann Warnsysteme GmbH + Hauptstr. 45-47 + Kirchseeon Bavaria 85614 + DE + +CC-E7-DE (hex) Fareco +400000-4FFFFF (base 16) Fareco + Arteparc de Meyreuil Bat C Route de la cote AzurLe Canet + MEYREUIL 13590 + FR + +4C-6E-44 (hex) Chengdu Ruibitechuang Technology Co.,Ltd +900000-9FFFFF (base 16) Chengdu Ruibitechuang Technology Co.,Ltd + Room 34, 7th Floor, Unit 2, Building 5, No. 68 Yangzi Mountain Road, Chenghua District + Chengdu Sichuan 610000 + CN + +A4-4F-3E (hex) Vinfast Trading and Production JSC +700000-7FFFFF (base 16) Vinfast Trading and Production JSC + Dinh Vu - Cat Hai Economic Zone, Cai Hai Island, Cat Hai Sepcial Administrative Zone + Hai Phong Hai Phong 180000 + VN + +A4-4F-3E (hex) Neurable +800000-8FFFFF (base 16) Neurable + 45 Bromfield St + Boston 02108 + US + +A4-4F-3E (hex) LINK Group Inc. +900000-9FFFFF (base 16) LINK Group Inc. + 43855 Plymouth Oaks Blvd + Plymouth MI 48170 + US + +A4-4F-3E (hex) Mobilint +500000-5FFFFF (base 16) Mobilint + 35, Seolleung-ro 93-gil + Seoul Gangnam-gu 06151 + KR + +F8-C9-D6 (hex) Fortis Medical Devices LTD +D00000-DFFFFF (base 16) Fortis Medical Devices LTD + Barna Road + Galway Galway H91 HP83 + IE + +F8-C9-D6 (hex) Miri Technologies, Inc +800000-8FFFFF (base 16) Miri Technologies, Inc + 156 Madison Ave + Reading PA 19605 + US + +F8-C9-D6 (hex) Active Research Limited +400000-4FFFFF (base 16) Active Research Limited + 21 Harwell Road + Poole Dorset BH17 0GE + GB + +F8-75-28 (hex) NORBIT ASA +500000-5FFFFF (base 16) NORBIT ASA + Stiklestadveien 1 + Trondheim 7041 + NO + +F8-75-28 (hex) Annapurna labs +400000-4FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +F8-75-28 (hex) Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. +A00000-AFFFFF (base 16) Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. + 6th Floor, Building 6, Courtyard 5, West Laiguangying Road, Chaoyang District, Beijing + Beijing Beijing 100024 + CN + +0C-0E-C1 (hex) COGITO TECH COMPANY LIMITED +600000-6FFFFF (base 16) COGITO TECH COMPANY LIMITED + 21/F TAI YAU BLDG 181 JOHNSON RD WANCHAI HONG KONG + HONG KONG 999077 + CN + +0C-0E-C1 (hex) Lupa Tecnologia e Sistemas Ltda +300000-3FFFFF (base 16) Lupa Tecnologia e Sistemas Ltda + Rua Viscondessa de Cavalcanti, 50 - Poço Rico + Juiz de Fora Minas Gerais 36020-070 + BR + +F8-75-28 (hex) Lyte AI +200000-2FFFFF (base 16) Lyte AI + 185 N Wolfe Rd. + Sunnyvale CA 94583 + US diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index 324f86bb3e100..e61283851a2ae 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -170,12 +170,6 @@ AD8000-AD8FFF (base 16) Novanta IMS Marlborough CT 06447 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -362000-362FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Mecos AG 48F000-48FFFF (base 16) Mecos AG Hardstrasse 319 @@ -383,12 +377,6 @@ A33000-A33FFF (base 16) TIAMA New Territories 852 HK -8C-1F-64 (hex) VT100 SRL -66D000-66DFFF (base 16) VT100 SRL - Via A. Meucci 11 - Caldiero ITALY 37042 - IT - 8C-1F-64 (hex) Flextronics International Kft 154000-154FFF (base 16) Flextronics International Kft 38. Zrinyi Str. @@ -479,12 +467,6 @@ BF1000-BF1FFF (base 16) Soha Jin Chandler 85286 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -DA6000-DA6FFF (base 16) Power Electronics Espana, S.L. - Pol Industrial Carrases Ronda del Camp d’Aviació,4 - Lliria Valencia 46160 - ES - 8C-1F-64 (hex) Scarlet Tech Co., Ltd. 37F000-37FFFF (base 16) Scarlet Tech Co., Ltd. 4F-3, 347 HePing E Rd 2nd Sec, Daan Dist @@ -644,12 +626,6 @@ B08000-B08FFF (base 16) Cronus Electronics Koesching 85092 DE -8C-1F-64 (hex) Power Electronics Espana, S.L. -B9E000-B9EFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) GVA Lighting, Inc. 44E000-44EFFF (base 16) GVA Lighting, Inc. 2771 Bristol Circle @@ -1148,12 +1124,6 @@ EB2000-EB2FFF (base 16) Aqua Broadcast Ltd Windsor NJ 07762 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -FE3000-FE3FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Aspen Spectra Sdn Bhd 41D000-41DFFF (base 16) Aspen Spectra Sdn Bhd 51-10, The Boulevard, Mid Valley City @@ -2282,12 +2252,6 @@ E46000-E46FFF (base 16) 7thSense Design Limited HONFLEUR 14600 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -632000-632FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Hermann Sewerin GmbH 484000-484FFF (base 16) Hermann Sewerin GmbH Robert-Bosch-Str. 3 @@ -2618,12 +2582,6 @@ B95000-B95FFF (base 16) EPIImaging Los Altos CA 94024 US -70-B3-D5 (hex) Power Electronics Espana, S.L. -56E000-56EFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Clecell 565000-565FFF (base 16) Clecell 26, Beobwon-ro 9-gil @@ -4418,12 +4376,6 @@ A73000-A73FFF (base 16) MobiPromo Champigny sur Marne France 94500 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -B56000-B56FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Seraphim Optronics Ltd ADF000-ADFFFF (base 16) Seraphim Optronics Ltd 2 hacarmel st @@ -7463,12 +7415,6 @@ B17000-B17FFF (base 16) DAT Informatics Pvt Ltd NOF HAGLIL 1789062 IL -8C-1F-64 (hex) Power Electronics Espana, S.L. -82C000-82CFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Canfield Scientific Inc 470000-470FFF (base 16) Canfield Scientific Inc 4 Wood Hollow Road @@ -8255,6 +8201,150 @@ D6F000-D6FFFF (base 16) ARKTRON ELECTRONICS FARIDABAD HARYANA 121004 IN +8C-1F-64 (hex) NEOX Networks +9A0000-9A0FFF (base 16) NEOX Networks + Monzastraße 4 + Langen Hesse 63225 + DE + +8C-1F-64 (hex) Potter Electric Signal Company +A25000-A25FFF (base 16) Potter Electric Signal Company + 1609 Park 370 Place + Hazelwood MO 63042 + US + +8C-1F-64 (hex) AT-Automation Technology GmbH +2DA000-2DAFFF (base 16) AT-Automation Technology GmbH + Hermann-Boessow-Str. 6-8 + Bad Oldesloe D-23843 + DE + +8C-1F-64 (hex) Right Time Sports LLC +6F0000-6F0FFF (base 16) Right Time Sports LLC + PO Box 684 + Supply NC 28462 + US + +8C-1F-64 (hex) Abbott Diagnostics Technologies AS +7EA000-7EAFFF (base 16) Abbott Diagnostics Technologies AS + P. O. Box 6863 Rodeløkka + Oslo Oslo 0504 + NO + +8C-1F-64 (hex) Xi'an Biangu Information Technology Co., Ltd. +D35000-D35FFF (base 16) Xi'an Biangu Information Technology Co., Ltd. + Room 1601, Building A, Innovation and Entrepreneurship Center, No. 8989 Shangji Road, Economic and Technological Development Zone, Xi'an, Shaanxi Province, China. + Xi'an Shanxi 710048 + CN + +8C-1F-64 (hex) Headwave +C65000-C65FFF (base 16) Headwave + Marie-Elisab.-V.-Humboldt 35a + Berlin 13057 + DE + +8C-1F-64 (hex) Portrait Displays, Inc. +893000-893FFF (base 16) Portrait Displays, Inc. + 4637 Chabot Drive, Suite 115 + Pleasanton CA 94588 + US + +8C-1F-64 (hex) Cognicom, Inc. +FE6000-FE6FFF (base 16) Cognicom, Inc. + 5095 Murphy Canyon Rd, Ste 240 + San Diego CA 92123 + US + +8C-1F-64 (hex) TEX COMPUTER SRL +1CC000-1CCFFF (base 16) TEX COMPUTER SRL + Via O. Respighi 13 + CATTOLICA RIMINI 47841 + IT + +8C-1F-64 (hex) JS Tech Co., Ltd. +51C000-51CFFF (base 16) JS Tech Co., Ltd. + Room 807, Building A, 190 Soha-ro + Gwangmyeong-si Gyeonggi-do 14322 + KR + +8C-1F-64 (hex) VT100 SRL +66D000-66DFFF (base 16) VT100 SRL + Viale Dell'Artigianato 4 + Caldiero ITALY 37042 + IT + +8C-1F-64 (hex) MyDefence A/S +E0D000-E0DFFF (base 16) MyDefence A/S + Bouet Mollevej 5 + Norresundby NJ 9400 + DK + +8C-1F-64 (hex) Talleres de Escoriaza SAU +955000-955FFF (base 16) Talleres de Escoriaza SAU + Barrio Ventas 35, Irun + Irun Gipuzkoa 20305 + ES + +8C-1F-64 (hex) Osec +8EF000-8EFFFF (base 16) Osec + netwerk 120 + Purmerend 1446XA + NL + +8C-1F-64 (hex) FLUGCOM GmbH +ABA000-ABAFFF (base 16) FLUGCOM GmbH + Lyoner str. 15 + Frankfurt am Main Hessen 60528 + DE + +8C-1F-64 (hex) Power Electronics Espana, S.L. +82C000-82CFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +362000-362FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +DA6000-DA6FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +B9E000-B9EFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +FE3000-FE3FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +632000-632FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +56E000-56EFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +B56000-B56FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -8279,12 +8369,6 @@ B5A000-B5AFFF (base 16) YUYAMA MFG Co.,Ltd MEISHINGUCHI,TOYONAKA OSAKA 561-0841 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -E80000-E80FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) RCH SPA DA9000-DA9FFF (base 16) RCH SPA Via Cendon 39 @@ -8615,12 +8699,6 @@ DD4000-DD4FFF (base 16) Midlands Technical Co., Ltd. shinjyuku-ku Tokyo 169-0075 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -0D8000-0D8FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) 3D perception AS A81000-A81FFF (base 16) 3D perception AS Nye Vakas vei 14 @@ -11387,12 +11465,6 @@ EA1000-EA1FFF (base 16) Qntra Technology Shenzhen Guangdong 518102 CN -70-B3-D5 (hex) Power Electronics Espana, S.L. -AB2000-AB2FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) BIGHOUSE.,INC. B80000-B80FFF (base 16) BIGHOUSE.,INC. 72-11, Pyeongchangmunwha-ro @@ -14033,12 +14105,6 @@ FE7000-FE7FFF (base 16) VEILUX INC. Trigg Western Australia 6029 AU -70-B3-D5 (hex) Power Electronics Espana, S.L. -431000-431FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) JK DEVICE CORPORATION 4DC000-4DCFFF (base 16) JK DEVICE CORPORATION RYOGOKU 4-35-1-304 @@ -15050,12 +15116,6 @@ BB1000-BB1FFF (base 16) Transit Solutions, LLC. Yuseong-gu Daejeon 34044 KR -8C-1F-64 (hex) Power Electronics Espana, S.L. -C83000-C83FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Flextronics International Kft 2DD000-2DDFFF (base 16) Flextronics International Kft 38. Zrinyi Str. @@ -16343,12 +16403,6 @@ A28000-A28FFF (base 16) Monnit Corporation Warriewood NSW 2102 AU -8C-1F-64 (hex) Power Electronics Espana, S.L. -EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. - Ctra. CV-35, Salida 30 Parcela M-13. Pla de Carrases B - LIRIA, Valencia Valencia 46160 - ES - 70-B3-D5 (hex) BAE Systems 1D7000-1D7FFF (base 16) BAE Systems Waterside House, 170 Priestley Road, Surrey Research Park @@ -16634,12 +16688,120 @@ FF2000-FF2FFF (base 16) MITSUBISHI ELECTRIC INDIA PVT. LTD. 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 +8C-1F-64 (hex) Shanghai Jarue Microsystem.CO.,Ltd. +D82000-D82FFF (base 16) Shanghai Jarue Microsystem.CO.,Ltd. + No. G5-118, Lane 3188, XiuPu Road, Pudong New Area + Shanghai Shanghai 200000 + CN + +8C-1F-64 (hex) SPIE Dürr Austria GmbH +360000-360FFF (base 16) SPIE Dürr Austria GmbH + Frank-Stronach-Straße 5 + Gleisdorf 8200 + AT + +8C-1F-64 (hex) Eon Instrumentation +308000-308FFF (base 16) Eon Instrumentation + 16333 Raymer Street Suite B + Van Nuys CA 91406 + US + +8C-1F-64 (hex) Cyberkar Systems inc. +DF0000-DF0FFF (base 16) Cyberkar Systems inc. + 3026 Anderson suit 202 + Terrebonne Quebec J6Y1W1 CA +8C-1F-64 (hex) EA Elektro-Automatik GmbH +57C000-57CFFF (base 16) EA Elektro-Automatik GmbH + Helmholtzstraße 31-37 + Viersen Nordrhein-Westfalen 41747 + DE + +8C-1F-64 (hex) Gogo BA +0AE000-0AEFFF (base 16) Gogo BA + 105 Edgeview Drive + Broomfield CO 80021 + US + +8C-1F-64 (hex) LEMIER +C95000-C95FFF (base 16) LEMIER + Entuziastov 34-4-21 + Moscow 105118 + RU + +8C-1F-64 (hex) MB connect line GmbH +12F000-12FFFF (base 16) MB connect line GmbH + Winnettener Strasse 6 + Dinkelsbuehl Bavaria 91550 + DE + +8C-1F-64 (hex) Jiangsu Yi Rong Mstar Technology Ltd. +5F4000-5F4FFF (base 16) Jiangsu Yi Rong Mstar Technology Ltd. + Building 1-4, No. 9, Yongfu Road, Dafeng Economic Development Zone, + Yancheng City Jiangsu 224199 + CN + +8C-1F-64 (hex) ICS SOLUTIONS INC. +C2E000-C2EFFF (base 16) ICS SOLUTIONS INC. + E-705,706, songdo-miraero, younsu-gu + incheon 21990 + KR + +8C-1F-64 (hex) Gyros Protein Technologies AB +633000-633FFF (base 16) Gyros Protein Technologies AB + Kungsängstull 4 + Uppsala 75319 + SE + +8C-1F-64 (hex) Lumiplan Duhamel +823000-823FFF (base 16) Lumiplan Duhamel + 2 rue de l'industrie + Domène Isère 38420 + FR + +8C-1F-64 (hex) XPS ELETRONICA LTDA +F7B000-F7BFFF (base 16) XPS ELETRONICA LTDA + AVENIDA JAÇANÃ, 470/474 - VILA NELSON + SÃO PAULO SÃO PAULO 02273-001 + BR + +8C-1F-64 (hex) Power Electronics Espana, S.L. +C83000-C83FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +E80000-E80FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +0D8000-0D8FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +AB2000-AB2FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +431000-431FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -21977,12 +22139,6 @@ A2E000-A2EFFF (base 16) Kokam Co., Ltd Suwon-si Gyeonggi-do 16203 KR -70-B3-D5 (hex) Power Electronics Espana, S.L. -148000-148FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) A.F.MENSAH, INC F5B000-F5BFFF (base 16) A.F.MENSAH, INC 252 NASSAU ST, 2ND FLOOR @@ -22442,12 +22598,6 @@ C55000-C55FFF (base 16) Intelligent Energy Ltd Loughborough Leicestershire LE11 3GB GB -70-B3-D5 (hex) Creotech Instruments S.A. -91E000-91EFFF (base 16) Creotech Instruments S.A. - ul. Gen. L. Okulickiego 7/9 - Piaseczno Mazovia 05-500 - PL - 70-B3-D5 (hex) McKay Brothers LLC A4B000-A4BFFF (base 16) McKay Brothers LLC 2355 Broadway @@ -23189,12 +23339,6 @@ FB9000-FB9FFF (base 16) IWS Global Pty Ltd Perth Western Australia 6090 AU -8C-1F-64 (hex) Power Electronics Espana, S.L. -1DE000-1DEFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Infosoft Digital Design and Services P L C9A000-C9AFFF (base 16) Infosoft Digital Design and Services P L 484, SECTOR-8 ,IMT MANESER,GURGAONMANESER @@ -24230,12 +24374,6 @@ EBE000-EBEFFF (base 16) Trafag Italia S.r.l. Legnano Milano 20025 IT -8C-1F-64 (hex) Power Electronics Espana, S.L. -28B000-28BFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) GigaIO Networks, Inc. 4AA000-4AAFFF (base 16) GigaIO Networks, Inc. 5924 Balfour Ct., Suite 101 @@ -24914,12 +25052,6 @@ C84000-C84FFF (base 16) Luceor Ithaca NY 14850 US -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 @@ -25022,6 +25154,144 @@ D67000-D67FFF (base 16) Groundtruth Ltd Saitama-shi Saitama 330-0855 JP +8C-1F-64 (hex) SekureTrak Inc. dba TraknProtect +C82000-C82FFF (base 16) SekureTrak Inc. dba TraknProtect + 1240 N. Lake Shore DriveUnit 5B + Chicago IL 60610 + US + +8C-1F-64 (hex) Wuxi Eutron Electronics Technology Co.,Ltd +3F5000-3F5FFF (base 16) Wuxi Eutron Electronics Technology Co.,Ltd + 1st floor , Building C , No. 40 Chunhui Middle Road, Xishan District, Wuxi City, Jiangsu + Wuxi 214000 + CN + +8C-1F-64 (hex) SiLC Technologies +BBB000-BBBFFF (base 16) SiLC Technologies + 181 W Huntington Dr. Ste 200 + Monrovia CA 91016 + US + +8C-1F-64 (hex) VITREA Smart Home Technologies Ltd. +320000-320FFF (base 16) VITREA Smart Home Technologies Ltd. + 3 Abraham Buma Shavit, 4A + Rishon Lezion 7559907 + IL + +8C-1F-64 (hex) DADHWAL AI PRIVATE LIMITED +A9F000-A9FFFF (base 16) DADHWAL AI PRIVATE LIMITED + F 74, INDUSTRIAL AREA PHASE 7 + SAS NAGAR PUNJAB 160055 + IN + +8C-1F-64 (hex) Viettel High Tech +D6D000-D6DFFF (base 16) Viettel High Tech + 380 Lac Long Quan St, Tay Ho + Hanoi 100000 + VN + +8C-1F-64 (hex) Ability Intelligent Corp. +ADA000-ADAFFF (base 16) Ability Intelligent Corp. + No. 200, Sec.3, Zhonghuan Rd., Xinzhuang Dist., New Taipei City 242030, Taiwan (R.O.C.) + New Taipei City 242030 + TW + +8C-1F-64 (hex) Guan Show Technologe Co., Ltd. +A41000-A41FFF (base 16) Guan Show Technologe Co., Ltd. + No.127, Jianguo 1st Rd., Lingya Dist. + Kaohsiung City 802 + TW + +70-B3-D5 (hex) Creotech Quantum SA +91E000-91EFFF (base 16) Creotech Quantum SA + Migdalowa 4 + Warsaw Mazovia 02-796 + PL + +8C-1F-64 (hex) MYIR Electronics Limited +8B4000-8B4FFF (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) TimeMachines Inc. +381000-381FFF (base 16) TimeMachines Inc. + 300 S 68th Street Place, Suite 100 + Lincoln NE 68510 + US + +8C-1F-64 (hex) Korea Electric Vehicle Infra Technology +35A000-35AFFF (base 16) Korea Electric Vehicle Infra Technology + 775, Gyeongin-ro, Yeongdeungpo-gu + Seoul Seoul-t'ukpyolsi 07299 + KR + +8C-1F-64 (hex) xTools Inc. +9AA000-9AAFFF (base 16) xTools Inc. + 651 North Broad Street, 201 + Middletown DE 19709 + US + +8C-1F-64 (hex) National Control Devices, LLC +89A000-89AFFF (base 16) National Control Devices, LLC + 430 Market St + Osceola MO 64776 + US + +8C-1F-64 (hex) Arctic Instruments Oy +A6C000-A6CFFF (base 16) Arctic Instruments Oy + Tekniikantie 14 + Espoo 02150 + FI + +8C-1F-64 (hex) IDUN Technologies AG +74D000-74DFFF (base 16) IDUN Technologies AG + Vega-Strasse 3 + Opfikon 8152 + CH + +8C-1F-64 (hex) Skylark Lasers +B33000-B33FFF (base 16) Skylark Lasers + Phase One, Ratho park, 88 Glasgow Rd, Ratho Station, Newbridge + Edinburgh EH28 8PP + GB + +8C-1F-64 (hex) Möbus Engineering GmbH +9EE000-9EEFFF (base 16) Möbus Engineering GmbH + Adam-Opel-Straße 3 + Trebur Hessen 65468 + DE + +70-B3-D5 (hex) Power Electronics Espana, S.L. +148000-148FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +773000-773FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Lotec Teknoloji Limited Sirketi +33F000-33FFFF (base 16) Lotec Teknoloji Limited Sirketi + Universiteler Mh. 1596 Cd. Hacettepe Teknokent 6. Ar-Ge C Blok No: 6C Ofis: 28 + Cankaya Ankara 06800 + TR + +8C-1F-64 (hex) Power Electronics Espana, S.L. +28B000-28BFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +1DE000-1DEFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power Suite 2, Level 3, 18 - 20 York St @@ -26555,12 +26825,6 @@ A57000-A57FFF (base 16) EkspertStroyProekt Moscow 129344 RU -70-B3-D5 (hex) Power Electronics Espana, S.L. -4CD000-4CDFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Research Laboratory of Design Automation, Ltd. 223000-223FFF (base 16) Research Laboratory of Design Automation, Ltd. 8 Birzhevoy Spusk @@ -26825,12 +27089,6 @@ ED9000-ED9FFF (base 16) AADONA Communication Pvt Ltd Gandra Paredes 4585-362 PT -70-B3-D5 (hex) Power Electronics Espana, S.L. -BDB000-BDBFFF (base 16) Power Electronics Espana, S.L. - PI Pla de Carrases, CV-35 Salida 30Salida 30- - lliria Valencia 46160 - ES - 70-B3-D5 (hex) Velvac Incorporated 4DD000-4DDFFF (base 16) Velvac Incorporated 2183 Alpine Way @@ -28829,12 +29087,6 @@ D72000-D72FFF (base 16) OnYield Inc Ltd Praha 6 16000 CZ -70-B3-D5 (hex) Power Electronics Espana, S.L. -2A9000-2A9FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Suprock Technologies 613000-613FFF (base 16) Suprock Technologies 45 Scott Hill Rd @@ -30203,12 +30455,6 @@ CCC000-CCCFFF (base 16) AEC s.r.l. ARGENTEUIL 95100 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -F4F000-F4FFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) NextEV Co., Ltd. 200000-200FFF (base 16) NextEV Co., Ltd. 20 Building, No. 56 AnTuo Road, Anting Town, Jiading @@ -30824,12 +31070,6 @@ BAD000-BADFFF (base 16) Technik & Design GmbH Berlin Berlin 12681 DE -70-B3-D5 (hex) PolyTech A/S -F4C000-F4CFFF (base 16) PolyTech A/S - HI Park 445 - Herning Herning 7400 - DK - 70-B3-D5 (hex) Birdland Audio AD5000-AD5FFF (base 16) Birdland Audio 484 Washington St. Ste.B-450 @@ -33014,12 +33254,6 @@ BF7000-BF7FFF (base 16) Intellicon Private Limited Gandhinagar Gujarat 382028 IN -8C-1F-64 (hex) Power Electronics Espana, S.L. -29A000-29AFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Nihon Bouhan Camera Inc 9A7000-9A7FFF (base 16) Nihon Bouhan Camera Inc 7F Daiichikosan Bldg ,3-14-10,Ginza @@ -33275,24 +33509,162 @@ A78000-A78FFF (base 16) TAIT Global LLC Saint-Prex CH-1162 CH +8C-1F-64 (hex) QUBIX SPA +4FC000-4FCFFF (base 16) QUBIX SPA + VIA CANADA 22A + PADOVA ITALY 35127 + IT + +8C-1F-64 (hex) CMI, Inc. +5A2000-5A2FFF (base 16) CMI, Inc. + 316 East 9th Street + Owensboro KY 42303 + US + +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) Metaphase Technologies +FC8000-FC8FFF (base 16) Metaphase Technologies + 200 Rittenhouse Circle, West Unit 7 + Bristol PA 19007 + US + 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 +8C-1F-64 (hex) Hatteland Technology AS +AD1000-AD1FFF (base 16) Hatteland Technology AS + Eikeskogvegen 52 + Aksdal 5570 + NO + +8C-1F-64 (hex) Neosem Inc. +7B2000-7B2FFF (base 16) Neosem Inc. + 12-26, Simin-daero 327beon-gil, Dongan-Gu + Anyang-Si GYEONGGI-DO 14055 + KR + +8C-1F-64 (hex) ENLESS WIRELESS +037000-037FFF (base 16) ENLESS WIRELESS + 45 TER AVENUE DE VERDUN + BRUGES 33520 + FR + +8C-1F-64 (hex) LOMAR SRL +DF2000-DF2FFF (base 16) LOMAR SRL + VIA GALVANI, 35 + FLERO BRESCIA 25020 + IT + +8C-1F-64 (hex) Chengdu Aplux Inteligence Technology Ltd. +F8E000-F8EFFF (base 16) Chengdu Aplux Inteligence Technology Ltd. + #701-703, 7th Floor, 1A, Jingronghui, #200 Tianfu 5th Street, Hi-Tech Industrial Development Zone, Chengdu, China + Chengdu Sichuan 610095 + CN + +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +03E000-03EFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP + +8C-1F-64 (hex) Zhejiang Tengen Electric Co.,Ltd. +027000-027FFF (base 16) Zhejiang Tengen Electric Co.,Ltd. + Sulv Industrial Area,Liushi Town + Yueqing Zhejiang 325604 + CN + +8C-1F-64 (hex) TAKAHATA PRECISION Co., LTD. +5AD000-5ADFFF (base 16) TAKAHATA PRECISION Co., LTD. + Nishi-Shinjuku, Shinjuku-ku, Tokyo 3-9-12 Building 9F + Nishi-Shinjuku 160-0023 + JP + +8C-1F-64 (hex) TECHMOVERS SYSTEMS INDIA LIMITED +3A5000-3A5FFF (base 16) TECHMOVERS SYSTEMS INDIA LIMITED + Unit no 327, 3rd floor, Bussa Industrial Estate, Behind Century Bazar, Prabhadevi + Mumbai Maharashtra 400025 + IN + +8C-1F-64 (hex) Aether Energy Alliance LLC +2F6000-2F6FFF (base 16) Aether Energy Alliance LLC + 353 Orchard Dale Drive + Clear Brook VA 22624 US -8C-1F-64 (hex) CMI, Inc. -5A2000-5A2FFF (base 16) CMI, Inc. - 316 East 9th Street - Owensboro KY 42303 +8C-1F-64 (hex) Instawork +614000-614FFF (base 16) Instawork + 55 Hawthorne Street + San Francisco CA 94105 US +8C-1F-64 (hex) Wacebo Europe Srl +58A000-58AFFF (base 16) Wacebo Europe Srl + viale Gianluigi Bonelli, 40 + Roma Roma 00127 + IT + +8C-1F-64 (hex) MDA SatConn UK +28F000-28FFFF (base 16) MDA SatConn UK + Spectrum Point,279 Farnborough Road, + Farnborough Hampshire GU14 7LS + GB + +8C-1F-64 (hex) Unlimited Bandwidth LLC +84B000-84BFFF (base 16) Unlimited Bandwidth LLC + 1320 W. Northwest Highway + Palatine 60067 + US + +8C-1F-64 (hex) Wuhan HYAIEV (华异) Technology Co., Ltd +930000-930FFF (base 16) Wuhan HYAIEV (华异) Technology Co., Ltd + Room 102, R&D Unit 1, Floors 1-2, Unit 2, Building C8, Rongke Zhigu Industrial Project(Phase Ⅲ), Liqiao Village, Hongshan District, Wuhan City + Wuhan 430000 + CN + +70-B3-D5 (hex) Polytech A/S +F4C000-F4CFFF (base 16) Polytech A/S + HI Park 445 + Herning Herning 7400 + DK + +70-B3-D5 (hex) Power Electronics Espana, S.L. +4CD000-4CDFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +BDB000-BDBFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +2A9000-2A9FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +F4F000-F4FFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +29A000-29AFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -34184,12 +34556,6 @@ E0E000-E0EFFF (base 16) Nokeval Oy Pune Maharashtra 411051 IN -8C-1F-64 (hex) Power Electronics Espana, S.L. -D08000-D08FFF (base 16) Power Electronics Espana, S.L. - Poligono Industrial Carrases. Ronda del camp d Aviacio 4 - Lliria Valencia 46160 - ES - 8C-1F-64 (hex) VECOS Europe B.V. C80000-C80FFF (base 16) VECOS Europe B.V. ESP 237 @@ -34760,12 +35126,6 @@ EAC000-EACFFF (base 16) Miracle Healthcare, Inc. Geumcheon-gu Seoul, Republic of Korea 08511 KR -8C-1F-64 (hex) Power Electronics Espana, S.L. -C2F000-C2FFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Edgeware AB FD1000-FD1FFF (base 16) Edgeware AB Master Samuelsgatan 42 @@ -40181,12 +40541,6 @@ D9D000-D9DFFF (base 16) MITSUBISHI HEAVY INDUSTRIES THERMAL SYSTEMS, LTD. Kiyosu Aichi 452-8561 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -1CA000-1CAFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) SHANGHAI ANGWEI INFORMATION TECHNOLOGY CO.,LTD. 457000-457FFF (base 16) SHANGHAI ANGWEI INFORMATION TECHNOLOGY CO.,LTD. ROOM 607,BUILDING 2, No.2555 XIUPU ROAD,PUDONG NEW AREA, SHANGHAI @@ -40667,12 +41021,6 @@ D0C000-D0CFFF (base 16) KS Beschallungstechnik GmbH Hettenleidelheim Rhineland-Palatinate 67310 DE -8C-1F-64 (hex) Power Electronics Espana, S.L. -5E1000-5E1FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Natron Energy DEA000-DEAFFF (base 16) Natron Energy 3542 Bassett St @@ -41488,3 +41836,132 @@ ACF000-ACFFFF (base 16) PROVENRUN 77 avenue Niel PARIS 75017 FR + +8C-1F-64 (hex) Automata GmbH & Co. KG +EFF000-EFFFFF (base 16) Automata GmbH & Co. KG + Gewerbering 5 + Ried Bavaria 86510 + DE + +8C-1F-64 (hex) Kairos Water, Inc +E2C000-E2CFFF (base 16) Kairos Water, Inc + 1700 Northside Dr NWSuite A7, Unit 5543 + Atlanta GA 30318 + US + +8C-1F-64 (hex) Camius +FB3000-FB3FFF (base 16) Camius + 8880 RIO SAN DIEGO DR + SAN DIEGO CA 92108 + US + +8C-1F-64 (hex) EXPERIO TECH PRIVATE LIMITED +D87000-D87FFF (base 16) EXPERIO TECH PRIVATE LIMITED + Ground floor Shiv shakti apatment Near Radha Krishna Mandir , Village bagdola,Dwarka sector -08 New Delhi-110077 + New Delhi Delhi 110077 + IN + +8C-1F-64 (hex) EverBot Technology CO., LTD +8F3000-8F3FFF (base 16) EverBot Technology CO., LTD + 10 F., No. 360, Sec. 1, Jingmao Rd., Beitun Dist., + Taichung City 請選擇 406040 + TW + +8C-1F-64 (hex) aelettronica group srl +BEB000-BEBFFF (base 16) aelettronica group srl + via matteotti,22 + gaggiano milano 20083 + IT + +8C-1F-64 (hex) Anuvu AB +205000-205FFF (base 16) Anuvu AB + Forradsgatan 4 + Sundsvall Vasternorrland 85633 + SE + +8C-1F-64 (hex) Transports Publics Genevois +640000-640FFF (base 16) Transports Publics Genevois + Route de la Chapelle 1 + Grand-Lancy Genève 1212 + CH + +8C-1F-64 (hex) AVEA Group, Inc. +BD4000-BD4FFF (base 16) AVEA Group, Inc. + 55 E Monroe Street Suite 3800 + Chicago IL 60603 + US + +8C-1F-64 (hex) CrossBar, Inc. +310000-310FFF (base 16) CrossBar, Inc. + 2055 Laurelwood Rd. Suite 230 + Santa Clara CA 95054-2729 + US + +8C-1F-64 (hex) Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. +EC5000-EC5FFF (base 16) Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. + No.666 Huatong Road, High-tech Industrial Development Zone, Tongzhou District, Nantong City, Jiangsu Province, China + Nantong Jiangsu 226315 + CN + +8C-1F-64 (hex) INTOWN CO., LTD. +007000-007FFF (base 16) INTOWN CO., LTD. + No.401 Intelliumcentum,21,Centum 6-ro,Haeundae-gu + Busan 48059 + KR + +8C-1F-64 (hex) Scramble Tools LLC +36C000-36CFFF (base 16) Scramble Tools LLC + 1401 21st ST, #7216 + Sacramento 95811 + US + +8C-1F-64 (hex) Scenario Automation +EC3000-EC3FFF (base 16) Scenario Automation + Rua Paulo Elias, 216 + São Carlos São Paulo 13564400 + BR + +8C-1F-64 (hex) VNET Corp. +C77000-C77FFF (base 16) VNET Corp. + B-1202, 128, Beobwon-ro, Songpa-gu + Seoul 05854 + KR + +8C-1F-64 (hex) Private +182000-182FFF (base 16) Private + +8C-1F-64 (hex) swiss electronic creation GmbH +8BB000-8BBFFF (base 16) swiss electronic creation GmbH + Allmendstrasse 29 + Fehraltorf 8320 + CH + +8C-1F-64 (hex) Power Electronics Espana, S.L. +5E1000-5E1FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +1CA000-1CAFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +D08000-D08FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +C2F000-C2FFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +602000-602FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index 5dbb806e2e481..2596757644b98 100644 --- a/hwdb.d/pci.ids +++ b/hwdb.d/pci.ids @@ -1,8 +1,8 @@ # # List of PCI IDs # -# Version: 2026.03.16 -# Date: 2026-03-16 03:15:01 +# Version: 2026.05.18 +# Date: 2026-05-18 03:15:02 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -30,13 +30,19 @@ # This is a relabelled RTL-8139 8139 AT-2500TX V3 Ethernet 0014 Loongson Technology LLC + 3b0f DMA Adress Translation Unit [Loongson 3 Processor Family] + 3c09 Internal PCI to PCI Bridge [Loongson 3 Processor Family] + 3c0f DMA Adress Translation Unit [Loongson 3 Processor Family] + 3c19 PCI Express x16 Root Port [Loongson 3 Processor Family] + 3c29 PCI Express x8 Root Port [Loongson 3 Processor Family] + 3c39 PCI Express x4 Root Port [Loongson 3 Processor Family] 7a00 7A1000 Chipset Hyper Transport Bridge Controller 7a02 2K1000 / 7A1000 Chipset Advanced Peripheral Bus Controller 7a03 2K1000/2000 / 7A1000 Chipset Gigabit Ethernet Controller 7a04 2K1000 / 7A1000 Chipset OTG USB Controller 7a05 2K1000 Vivante GC1000 GPU 7a06 2K1000 / 7A1000 Chipset Display Controller - 7a07 2K1000/2000 / 7A1000/2000 Chipset HD Audio Controller + 7a07 2K1000/2000/3000 / 3B6000M / 7A1000/2000 Chipset HD Audio Controller 7a08 2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller 7a09 2K1000 / 7A1000 Chipset PCIe x1 Bridge 7a0b 7A1000 Chipset SPI Controller @@ -49,34 +55,43 @@ 7a15 7A1000 Chipset Vivante GC1000 GPU 7a16 2K1000/2000 VPU Decoder 7a17 7A1000 Chipset AC97 Audio Controller - 7a18 2K2000 / 7A2000 Chipset 6Gb/s SATA AHCI Controller + 7a18 2K2000/3000 / 3B6000M / 7A2000 Chipset 6Gb/s SATA AHCI Controller 7a19 PCI-to-PCI Bridge 7a1a 2K2000 Configuration Bus - 7a1b 2K2000 / 7A2000 Chipset SPI Controller - 7a1d 2K2000 RapidIO Interface + 7a1b 2K2000/3000 / 3B6000M / 7A2000 Chipset SPI Controller + 7a1d 2K2000 / 2K3000 / 3B6000M RapidIO Interface 7a1e 2K2000 DES Controller 7a22 2K2000 Advanced Peripheral Bus Controller + 7a23 Gigabit Ethernet Controller [Chipset / CPU inside] 7a24 2K1000 / 7A1000/2000 Chipset USB OHCI Controller 7a25 2K2000 / 7A2000 Chipset LG100 GPU 7a26 2K1000 Camera Controller - 7a27 2K2000 / 7A2000 Chipset I2S Controller + 7a27 2K2000/3000 / 3B6000M / 7A2000 Chipset I2S Controller 7a29 7A1000 Chipset PCIe x8 Bridge 7a2e 2K2000 RSA Controller 7a2f 2K2000 DMA Controller - 7a34 2K2000 / 7A2000 Chipset USB 3.0 xHCI Controller + 7a34 2K2000/3000 / 3B6000M / 7A2000 Chipset USB 3.0 xHCI Controller + 7a35 LG200 GPU 7a36 2K2000 / 7A2000 Chipset Display Controller - 7a37 2K2000 HDMI Audio Controller + 7a37 2K2000/3000 / 3B6000M HDMI Audio Controller 7a39 2K2000 / 7A2000 Chipset PCIe x1 Root Port 7a3e 2K2000 RNG Controller - 7a44 2K2000 USB 2.0 xHCI Controller - 7a48 2K2000 SDIO Controller + 7a42 Advanced Peripheral Bus Controller [Chipset / CPU inside] + 7a44 2K2000/3000 / 3B6000M USB 2.0 xHCI Controller + 7a46 Video Display Controller [Chipset / CPU inside] + 7a47 Pulse-Code Modulation [Chipset / CPU inside] + 7a48 2K2000/3000 / 3B6000M SDIO Controller 7a49 2K2000 / 7A2000 Chipset PCIe x4 Root Port 7a54 2K2000 OTG USB Controller + 7a56 Video Processing Unit Decoder [Chipset / CPU inside] 7a59 7A2000 Chipset PCIe x8 Root Port + 7a66 Video Processing Unit Encoder [Chipset / CPU inside] 7a69 7A2000 Chipset PCIe x16 Root Port 7a79 2K2000 PCIe Root Complex - 7a88 2K2000 eMMC Controller + 7a88 2K2000/3000 / 3B6000M eMMC Controller + 7a89 PCI Express x1 Root Port [Chipset / CPU inside] 7a8e 2K2000 SE Controller + 7a99 PCI Express x4 Root Port [Chipset / CPU inside] 7af9 2K2000 PCIe Endpoint 0018 Fn-Link Technology Limited 6252 6252CPUB 802.11ax PCIe Wireless Network Adapter @@ -226,6 +241,7 @@ f011 JM1100-IV f111 JM1100-MV ff11 JM1100-YV +0771 Xi'an Microelectronics Technology Institute 0777 Ubiquiti Networks, Inc. 0795 Wired Inc. 6663 Butane II (MPEG2 encoder board) @@ -1240,6 +1256,7 @@ 1000 2004 PEX89000 Virtual PCIe TWC/NT 2.0 Endpoint # Lower lane count PEX89000 switch 1000 2005 PEX89000 Virtual PCIe gDMA Endpoint + c040 PEX90xxx PCIe Gen 6 Switch 1001 Kolter Electronic 0010 PCI 1616 Measurement card with 32 digital I/O lines 0011 OPTO-PCI Opto-Isolated digital I/O board @@ -1281,6 +1298,8 @@ 131c Kaveri [Radeon R7 Graphics] 131d Kaveri [Radeon R6 Graphics] 13c0 Granite Ridge [Radeon Graphics] +# Used in the Sony PlayStation 5 Phat + 13db Cyan Skillfish [PlayStation 5 APU] 13e9 Ariel/Navi10Lite 13f9 Oberon/Navi12Lite 13fe Cyan Skillfish [BC-250] @@ -4031,8 +4050,8 @@ 1da2 e409 Sapphire Technology Limited Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] 1da2 e410 Sapphire NITRO+ RX 5700 XT 1da2 e411 Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT]Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] - 7340 Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] - 106b 0210 Radeon Pro 5300M + 7340 Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] + 106b 0210 MacBookPro16,1 (16", 2019) [Radeon Pro 5300M] 106b 0219 iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300] 7341 Navi 14 [Radeon Pro W5500] 7347 Navi 14 [Radeon Pro W5500M] @@ -4153,6 +4172,8 @@ 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] +# ASUS RX 9060 XT 16GB + 1043 0639 Navi 44 [Radeon RX 9060 XT] 1458 2429 GV-R9060XTGAMING OC-16GD [Radeon RX 9060 XT GAMING OC 16G] 1eae 8601 RX-96TS316W7 [SWIFT RX 9060 XT OC White Triple Fan Gaming Edition 16GB] 75a0 Aqua Vanjaram [Instinct MI350X] @@ -4264,6 +4285,7 @@ 9500 RV670 [Radeon HD 3850 X2] 9501 RV670 [Radeon HD 3870] 174b e620 Radeon HD 3870 + 1787 2244 Radeon HD 3870 9504 RV670/M88 [Mobility Radeon HD 3850] 9505 RV670 [Radeon HD 3690/3850] 148c 3000 Radeon HD 3850 @@ -7581,7 +7603,37 @@ e350 80333 [SuperTrak EX24350] 105b Foxconn International, Inc. 9602 RS780/RS880 PCI to PCI bridge (int gfx) +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0ab T99W175 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0b0 DW5930e 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0b1 DW5930e 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0bf T99W175 5G Modem [Snapdragon X55] e0c3 T99W175 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0d8 T99W368 5G Modem [Snapdragon X65] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0d9 T99W373 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f0 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f1 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f2 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f5 DW5932e-eSIM 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f9 DW5932e 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e118 T99W640 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e11d DW5934e-eSIM 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e11e DW5934e 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e123 T99W760 5G Redcap Modem [Snapdragon X35] 105c Wipro Infotech Limited 105d Number 9 Computer Company 2309 Imagine 128 @@ -10192,9 +10244,7 @@ 1102 102f 3D Blaster RIVA TNT2 Ultra 14af 5820 Maxi Gamer Xentor 32 4843 4f34 Dynamite - 002a NV5 [Riva TNT2] - 002b NV5 [Riva TNT2] - 002c NV5 [Vanta / Vanta LT] + 002c NV6 [Vanta LT / Vanta / Vanta-16] 1043 0200 AGP-V3800 Combat SDRAM 1043 0201 AGP-V3800 Combat 1048 0c20 TNT2 Vanta @@ -10204,7 +10254,7 @@ 1102 1031 CT6938 VANTA 8MB 1102 1034 CT6894 VANTA 16MB 14af 5008 Maxi Gamer Phoenix 2 - 002d NV5 [Riva TNT2 Model 64 / Model 64 Pro] + 002d NV6 [Riva TNT2 Model 64 / Model 64 Pro] 1043 0200 AGP-V3800M 1043 0201 AGP-V3800M 1048 0c3a Erazor III LT @@ -13187,6 +13237,7 @@ 1f76 TU106GLM [Quadro RTX 3000 Mobile Refresh] 1f81 TU117 1f82 TU117 [GeForce GTX 1650] + 19da 3595 GeForce GTX 1650 OC GDDR6 1f83 TU117 [GeForce GTX 1630] 1f91 TU117M [GeForce GTX 1650 Mobile / Max-Q] 1f92 TU117M [GeForce GTX 1650 Mobile] @@ -13267,6 +13318,7 @@ 2203 GA102 [GeForce RTX 3090 Ti] 2204 GA102 [GeForce RTX 3090] 10de 147d GeForce RTX 3090 Founders Edition + 1462 3881 MSI RTX 3090 VENTUS 3X OC 3842 3973 GeForce RTX 3090 XC3 2205 GA102 [GeForce RTX 3080 Ti 20GB] 2206 GA102 [GeForce RTX 3080] @@ -13500,6 +13552,7 @@ 28f8 AD107GLM [RTX 2000 Ada Generation Embedded GPU] 2900 GB100 [Reserved Dev ID A] 2901 GB100 [B200] + 2909 GB100 [HGX B200 168GB] 2920 GB100 [TS4 / B100] 2924 GB100 2925 GB100 @@ -13568,6 +13621,7 @@ 31a1 GB110 [GB300 MaxQ] 31c0 GB110 [Reserved Dev ID B] 31c2 GB110 [GB300] + 31c3 GB110 [GB300] 31fe GB110 3200 GB112 3224 GB112 @@ -16215,6 +16269,8 @@ 117c 00c9 Celerity FC-641E 117c 00ca Celerity FC-642E 117c 00d4 Celerity FC-644E + 117c 40d8 ThunderLink FC 5642 + 117c 40d9 ThunderLink FC 5322 00c5 ExpressNVM PCIe Gen4 Switch 117c 00c6 ExpressNVM S48F PCIe Gen4 117c 00cb ExpressNVM S4FF PCIe Gen4 @@ -16224,6 +16280,11 @@ 117c 00c2 ExpressSAS H1244 GT 117c 00c3 ExpressSAS H12F0 GT 117c 00c4 ExpressSAS H120F GT + 117c 00e1 ExpressSAS H1280 GT + 117c 00e2 ExpressSAS H1208 GT + 117c 00e3 ExpressSAS H1244 GT + 117c 40e0 ThunderLink SH 5128 + 117c 40e4 ThunderLink SH 5128 8013 ExpressPCI UL4D 8014 ExpressPCI UL4S 8027 ExpressPCI UL5D @@ -16231,7 +16292,7 @@ 117c 0070 ExpressSAS H1280 117c 0071 ExpressSAS H1208 117c 0080 ExpressSAS H1244 - 117c 40ae ThunderLink TLSH-3128 + 117c 40ae ThunderLink SH 3128 8072 ExpressSAS 12Gb/s SAS/SATA HBA 117c 0072 ExpressSAS H12F0 117c 0073 ExpressSAS H120F @@ -17092,10 +17153,33 @@ 11f7 Scientific Atlanta # née PMC-Sierra Inc. 11f8 Microchip Technology + 4000 PM40100 Switchtec PFX 100xG4 Fanout PCIe Switch + 4028 PM40028 Switchtec PFX 28xG4 Fanout PCIe Switch 4036 PM40036 Switchtec PFX 36xG4 Fanout PCIe Switch 4052 PM40052 Switchtec PFX 52xG4 Fanout PCIe Switch + 4068 PM40068 Switchtec PFX 68xG4 Fanout PCIe Switch 4084 PM40084 Switchtec PFX 84xG4 Fanout PCIe Switch + 4100 PM41100 Switchtec PSX 100xG4 Programmable PCIe Switch 4128 PM41028 Switchtec PSX 28xG4 Programmable PCIe Switch + 4136 PM41036 Switchtec PSX 36xG4 Programmable PCIe Switch + 4152 PM41052 Switchtec PSX 52xG4 Programmable PCIe Switch + 4168 PM41068 Switchtec PSX 68xG4 Programmable PCIe Switch + 4184 PM41084 Switchtec PSX 84xG4 Programmable PCIe Switch + 4200 PM42100 Switchtec PAX 100xG4 Programmable Advanced Fabric PCIe Switch + 4228 PM42028 Switchtec PAX 28xG4 Programmable Advanced Fabric PCIe Switch + 4236 PM42036 Switchtec PAX 36xG4 Programmable Advanced Fabric PCIe Switch + 4252 PM42052 Switchtec PAX 52xG4 Programmable Advanced Fabric PCIe Switch + 4268 PM42068 Switchtec PAX 68xG4 Programmable Advanced Fabric PCIe Switch + 4284 PM42084 Switchtec PAX 84xG4 Programmable Advanced Fabric PCIe Switch + 4328 PM43028 Switchtec PFXA 28xG4 Automotive Fanout PCIe Switch + 4336 PM43036 Switchtec PFXA 36xG4 Automotive Fanout PCIe Switch + 4352 PM43052 Switchtec PFXA 52xG4 Automotive Fanout PCIe Switch + 4428 PM44028 Switchtec PSXA 28xG4 Automotive Programmable PCIe Switch + 4436 PM44036 Switchtec PSXA 36xG4 Automotive Programmable PCIe Switch + 4452 PM44052 Switchtec PSXA 52xG4 Automotive Programmable PCIe Switch + 4528 PM45028 Switchtec PAXA 28xG4 Automotive Programmable Advanced Fabric PCIe Switch + 4536 PM45036 Switchtec PAXA 36xG4 Automotive Programmable Advanced Fabric PCIe Switch + 4552 PM45052 Switchtec PAXA 52xG4 Automotive Programmable Advanced Fabric PCIe Switch 5000 PM50100 Switchtec PFX 100xG5 Fanout PCIe Switch 5028 PM50028 Switchtec PFX 28xG5 Fanout PCIe Switch 5036 PM50036 Switchtec PFX 36xG5 Fanout PCIe Switch @@ -17108,7 +17192,47 @@ 5152 PM51052 Switchtec PSX 52xG5 Programmable PCIe Switch 5168 PM51068 Switchtec PSX 68xG5 Programmable PCIe Switch 5184 PM51084 Switchtec PSX 84xG5 Programmable PCIe Switch + 5200 PM52100 Switchtec PAX 100xG5 Programmable Advanced Fabric PCIe Switch 5220 BR522x [PMC-Sierra maxRAID SAS Controller] + 5228 PM52028 Switchtec PAX 28xG5 Programmable Advanced Fabric PCIe Switch + 5236 PM52036 Switchtec PAX 36xG5 Programmable Advanced Fabric PCIe Switch + 5252 PM52052 Switchtec PAX 52xG5 Programmable Advanced Fabric PCIe Switch + 5268 PM52068 Switchtec PAX 68xG5 Programmable Advanced Fabric PCIe Switch + 5284 PM52084 Switchtec PAX 84xG5 Programmable Advanced Fabric PCIe Switch + 5300 PM53100 Switchtec PFXA 100xG5 Automotive Fanout PCIe Switch + 5328 PM53028 Switchtec PFXA 28xG5 Automotive Fanout PCIe Switch + 5336 PM53036 Switchtec PFXA 36xG5 Automotive Fanout PCIe Switch + 5352 PM53052 Switchtec PFXA 52xG5 Automotive Fanout PCIe Switch + 5368 PM53068 Switchtec PFXA 68xG5 Automotive Fanout PCIe Switch + 5384 PM53084 Switchtec PFXA 84xG5 Automotive Fanout PCIe Switch + 5400 PM54100 Switchtec PSXA 100xG5 Automotive Programmable PCIe Switch + 5428 PM54028 Switchtec PSXA 28xG5 Automotive Programmable PCIe Switch + 5436 PM54036 Switchtec PSXA 36xG5 Automotive Programmable PCIe Switch + 5452 PM54052 Switchtec PSXA 52xG5 Automotive Programmable PCIe Switch + 5468 PM54068 Switchtec PSXA 68xG5 Automotive Programmable PCIe Switch + 5484 PM54084 Switchtec PSXA 84xG5 Automotive Programmable PCIe Switch + 5500 PM55100 Switchtec PAXA 100xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5528 PM55028 Switchtec PAXA 28xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5536 PM55036 Switchtec PAXA 36xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5552 PM55052 Switchtec PAXA 52xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5568 PM55068 Switchtec PAXA 68xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5584 PM55084 Switchtec PAXA 84xG5 Automotive Programmable Advanced Fabric PCIe Switch + 6044 PM60144 Switchtec PFXs 144xG6 Secure-capable Fanout PCIe Switch + 6048 PM60048 Switchtec PFXs 48xG6 Secure-capable Fanout PCIe Switch + 6060 PM60160 Switchtec PFXs 160xG6 Secure-capable Fanout PCIe Switch + 6064 PM60064 Switchtec PFXs 64xG6 Secure-capable Fanout PCIe Switch + 6144 PM61144 Switchtec PSXs 144xG6 Secure-capable Programmable PCIe Switch + 6148 PM61048 Switchtec PSXs 48xG6 Secure-capable Programmable PCIe Switch + 6160 PM61160 Switchtec PSXs 160xG6 Secure-capable Programmable PCIe Switch + 6164 PM61064 Switchtec PSXs 64xG6 Secure-capable Programmable PCIe Switch + 6244 PM62144 Switchtec PFX 144xG6 Fanout PCIe Switch + 6248 PM62048 Switchtec PFX 48xG6 Fanout PCIe Switch + 6260 PM62160 Switchtec PFX 160xG6 Fanout PCIe Switch + 6264 PM62064 Switchtec PFX 64xG6 Fanout PCIe Switch + 6344 PM63144 Switchtec PSX 144xG6 Programmable PCIe Switch + 6348 PM63048 Switchtec PSX 48xG6 Programmable PCIe Switch + 6360 PM63160 Switchtec PSX 160xG6 Programmable PCIe Switch + 6364 PM63064 Switchtec PSX 64xG6 Programmable PCIe Switch 7364 PM7364 [FREEDM - 32 Frame Engine & Datalink Mgr] 7375 PM7375 [LASAR-155 ATM SAR] 7384 PM7384 [FREEDM - 84P672 Frm Engine & Datalink Mgr] @@ -17137,8 +17261,30 @@ 8535 PM8535 PFX 80xG3 PCIe Fanout Switch 8536 PM8536 PFX 96xG3 PCIe Fanout Switch 1bd4 0081 PM8536 PFX 96xG3 PCIe Fanout Switch + 8541 PM8541 PSX 24xG3 Programmable PCIe Switch + 8542 PM8542 PSX 32xG3 Programmable PCIe Switch + 8543 PM8543 PSX 48xG3 Programmable PCIe Switch + 8544 PM8544 PSX 64xG3 Programmable PCIe Switch + 8545 PM8545 PSX 80xG3 Programmable PCIe Switch 8546 PM8546 B-FEIP PSX 96xG3 PCIe Storage Switch + 8551 PM8551 PAX 24xG3 Programmable Advanced Fabric PCIe Switch + 8552 PM8552 PAX 32xG3 Programmable Advanced Fabric PCIe Switch + 8553 PM8553 PAX 48xG3 Programmable Advanced Fabric PCIe Switch + 8554 PM8554 PAX 64xG3 Programmable Advanced Fabric PCIe Switch + 8555 PM8555 PAX 80xG3 Programmable Advanced Fabric PCIe Switch + 8556 PM8556 PAX 96xG3 Programmable Advanced Fabric PCIe Switch + 8561 PM8561 Switchtec PFX-L 24xG3 Fanout-Lite PCIe Switch 8562 PM8562 Switchtec PFX-L 32xG3 Fanout-Lite PCIe Gen3 Switch + 8563 PM8563 Switchtec PFX-L 48xG3 Fanout-Lite PCIe Switch + 8564 PM8564 Switchtec PFX-L 64xG3 Fanout-Lite PCIe Switch + 8565 PM8565 Switchtec PFX-L 80xG3 Fanout-Lite PCIe Switch + 8566 PM8566 Switchtec PFX-L 96xG3 Fanout-Lite PCIe Switch + 8571 PM8571 Switchtec PFX-I 24xG3 Industrial Fanout PCIe Switch + 8572 PM8572 Switchtec PFX-I 32xG3 Industrial Fanout PCIe Switch + 8573 PM8573 Switchtec PFX-I 48xG3 Industrial Fanout PCIe Switch + 8574 PM8574 Switchtec PFX-I 64xG3 Industrial Fanout PCIe Switch + 8575 PM8575 Switchtec PFX-I 80xG3 Industrial Fanout PCIe Switch + 8576 PM8576 Switchtec PFX-I 96xG3 Industrial Fanout PCIe Switch 11f9 I-Cube Inc 11fa Kasan Electronics Company, Ltd. 11fb Datel Inc @@ -20700,31 +20846,31 @@ 1028 230f DC NVMe PM9D3a RI 80M.2 480GB ISE 1028 2310 DC NVMe PM9D3a RI 80M.2 960GB ISE 1028 2311 DC NVMe PM9D3a RI 80M.2 1.92TB ISE - 1028 2341 DC NVMe PM9D3a RI U.2 960GB  + 1028 2341 DC NVMe PM9D3a RI U.2 960GB 1028 2342 DC NVMe PM9D3a RI U.2 1.92TB 1028 2343 DC NVMe PM9D3a RI U.2 3.84TB - 1028 2344 DC NVMe PM9D3a RI U.2 7.68GTB + 1028 2344 DC NVMe PM9D3a RI U.2 7.68TB 1028 2345 DC NVMe PM9D3a RI U.2 15.36TB 1028 2346 DC NVMe FIPS PM9D3a RI U.2 960GB 1028 2347 DC NVMe FIPS PM9D3a RI U.2 1.92TB 1028 2348 DC NVMe FIPS PM9D3a RI U.2 3.84TB 1028 2349 DC NVMe FIPS PM9D3a RI U.2 7.68TB - 1028 234a DC NVMe FIPS PM9D3a RI U.2 15.36TB  - 1028 234d DC NVMe PM9D3a RI E3s 1.92TB - 1028 234e DC NVMe PM9D3a RI E3s 3.84TB  - 1028 234f DC NVMe PM9D3a RI E3s 7.68GTB - 1028 2350 DC NVMe PM9D3a RI E3s 15.36TB - 1028 2351 DC NVMe FIPS PM9D3a RI E3s 1.92TB - 1028 2352 DC NVMe FIPS PM9D3a RI E3s 3.84TB - 1028 2353 DC NVMe FIPS PM9D3a RI E3s 7.68TB - 1028 2354 DC NVMe FIPS PM9D3a RI E3s 15.36TB + 1028 234a DC NVMe FIPS PM9D3a RI U.2 15.36TB + 1028 234d DC NVMe PM9D3a RI E3.S 1.92TB + 1028 234e DC NVMe PM9D3a RI E3.S 3.84TB + 1028 234f DC NVMe PM9D3a RI E3.S 7.68TB + 1028 2350 DC NVMe PM9D3a RI E3.S 15.36TB + 1028 2351 DC NVMe FIPS PM9D3a RI E3.S 1.92TB + 1028 2352 DC NVMe FIPS PM9D3a RI E3.S 3.84TB + 1028 2353 DC NVMe FIPS PM9D3a RI E3.S 7.68TB + 1028 2354 DC NVMe FIPS PM9D3a RI E3.S 15.36TB 1028 2355 DC NVMe PM9D5a MU U.2 800GB 1028 2356 DC NVMe PM9D5a MU U.2 1.6TB 1028 2357 DC NVMe PM9D5a MU U.2 3.2TB 1028 2358 DC NVMe PM9D5a MU U.2 6.4TB - 1028 2359 DC NVMe PM9D5a MU E3.s 1.6TB - 1028 235a DC NVMe PM9D5a MU E3.s 3.2TB - 1028 235b DC NVMe PM9D5a MU E3.s 6.4TB + 1028 2359 DC NVMe PM9D5a MU E3.S 1.6TB + 1028 235a DC NVMe PM9D5a MU E3.S 3.2TB + 1028 235b DC NVMe PM9D5a MU E3.S 6.4TB aa00 NVMe SSD Controller BM1743 1028 2312 NVMe FIPS BM1743 QLC U.2 15.36TB 1028 2313 NVMe FIPS BM1743 QLC U.2 30.72TB @@ -20736,6 +20882,56 @@ 1028 2366 MZ3MO15THCLCAD3 1028 2367 MZ3MO30THCLFAD3 ac00 NVMe SSD Controller PM175x +# NVMe FIPS PM1753 RI E3.S 1.92TB + 1028 2383 MZ3L91T9HFJAAD9 +# NVMe PM1753 RI E3.S 1.92TB + 1028 2384 MZ3L91T9HFJAAD3 +# NVMe FIPS PM1753 RI E3.S 3.84TB + 1028 2385 MZ3L93T8HFJAAD9 +# NVMe PM1753 RI E3.S 3.84TB + 1028 2386 MZ3L93T8HFJAAD3 +# NVMe FIPS PM1753 RI E3.S 7.68TB + 1028 2387 MZ3L97T6HFLTAD9 +# NVMe PM1753 RI E3.S 7.68TB + 1028 2388 MZ3L97T6HFLTAD3 +# NVMe FIPS PM1753 RI E3.S 15.36TB + 1028 2389 MZ3L915THBLCAD9 +# NVMe PM1753 RI E3.S 15.36TB + 1028 238a MZ3L915THBLCAD3 +# NVMe FIPS PM1753 RI E3.S 30.72TB + 1028 238b MZ3L930THBLFAD9 +# NVMe PM1753 RI E3.S 30.72TB + 1028 238c MZ3L930THBLFAD3 +# NVMe FIPS PM1755 MU E3.S 1.6TB + 1028 238d MZ3L91T6HFJAAD9 +# NVMe PM1755 MU E3.S 1.6TB + 1028 238e MZ3L91T6HFJAAD3 +# NVMe FIPS PM1755 MU E3.S 3.2TB + 1028 238f MZ3L93T2HFJAAD9 +# NVMe PM1755 MU E3.S 3.2TB + 1028 2390 MZ3L93T2HFJAAD3 +# NVMe FIPS PM1755 MU E3.S 6.4TB + 1028 2391 MZ3L96T4HFLTAD9 +# NVMe PM1755 MU E3.S 6.4TB + 1028 2392 MZ3L96T4HFLTAD3 +# NVMe PM1753 RI U.2 1.92TB + 1028 2394 MZWL91T9HFJAAD3 +# NVMe PM1753 RI U.2 3.84TB + 1028 2396 MZWL93T8HFLTAD3 +# NVMe PM1753 RI U.2 7.68TB + 1028 2398 MZWL97T6HFLAAD3 +# NVMe PM1753 RI U.2 15.36TB + 1028 239a MZWL915THBLFAD3 +# NVMe FIPS PM1753 RI U.2 30.72TB + 1028 239b MZWL930THBLFAD9 +# NVMe PM1753 RI U.2 30.72TB + 1028 239c MZWL930THBLFAD3 +# NVMe PM1755 MU U.2 1.6TB + 1028 239e MZWL91T6HFJAAD3 +# NVMe PM1755 MU U.2 3.2TB + 1028 239f MZWL93T2HFLTAD3 +# NVMe PM1755 MU U.2 6.4TB + 1028 23a0 MZWL96T4HFLAAD3 ecec Exynos 8895 PCIe Root Complex 144e OLITEC 144f Askey Computer Corp. @@ -20958,6 +21154,7 @@ 7991 MT7996 secondary link PCIe Wi-Fi 7(802.11be) 320MHz Wireless Network Adapter [Filogic 680] 7992 MT7992 primary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] 799a MT7992 secondary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] + 8188 MT8188 [Kompanio 838] Root Complex 8650 MT7650 Bluetooth 14c4 IWASAKI Information Systems Co Ltd 14c5 Automation Products AB @@ -21496,6 +21693,7 @@ 193d 1087 NIC-ETH531F-3S-2P 16d7 BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller 117c 00cc FastFrame N422 Dual-port 25Gb Ethernet Adapter + 117c 40d7 ThunderLink NS 5252 Dual-port 25Gb Ethernet Adapter 14e4 1402 BCM957414A4142CC 10Gb/25Gb Ethernet PCIe 14e4 1404 BCM957414M4142C OCP 2x25G Type1 wRoCE 14e4 4140 NetXtreme E-Series Advanced Dual-port 25Gb SFP28 Network Daughter Card @@ -21567,7 +21765,7 @@ 17aa 3a23 IdeaPad S10e 1750 BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet 117c 00cf FastFrame N412 Dual-port 100Gb Ethernet Adapter - 117c 40d6 ThunderLink TLNS-5102 Dual-port 100Gb Ethernet Adapter + 117c 40d6 ThunderLink NS 5102 Dual-port 100Gb Ethernet Adapter 14e4 2100 NetXtreme-E Dual-port 100G QSFP56 Ethernet PCIe4.0 x16 Adapter (BCM957508-P2100G) 14e4 5208 NetXtreme-E Dual-port 100G QSFP56 Ethernet OCP 3.0 Adapter (BCM957508-N2100G) 14e4 520a NetXtreme-E Dual-port 100G DSFP Ethernet OCP 3.0 Adapter (BCM957508-N2100GD) @@ -21578,6 +21776,7 @@ 1028 09d4 PowerEdge XR11/XR12 LOM 1028 0b1b PowerEdge XR5610 LOM 117c 00da FastFrame N424 Quad-port 25Gb Ethernet Adapter + 117c 40df ThunderLink NS 5254 Quad-port 25Gb Ethernet Adapter 14e4 4250 NetXtreme-E Quad-port 25G SFP28 Ethernet PCIe4.0 x16 Adapter (BCM957504-P425G) 14e4 5045 NetXtreme-E BCM57504 4x25G OCP3.0 14e4 5100 NetXtreme-E Single-port 100G QSFP56 Ethernet OCP 3.0 Adapter (BCM957504-N1100G) @@ -21588,6 +21787,7 @@ 1590 0420 HPE Ethernet 25/50Gb 2-port 6310C Adapter 1752 BCM57502 NetXtreme-E 10Gb/25Gb/40Gb/50Gb Ethernet 1760 BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet + 117c 00cf FastFrame N522 Dual-port 200Gb Ethernet Adapter 14e4 9110 BCM57608 1x400G PCIe Ethernet NIC 14e4 9120 BCM57608 2x200G PCIe Ethernet NIC 14e4 9121 BCM57608 2x100G PCIe Ethernet NIC @@ -21813,6 +22013,8 @@ 43ba BCM43602 802.11ac Wireless LAN SoC 43bb BCM43602 802.11ac Wireless LAN SoC 43bc BCM43602 802.11ac Wireless LAN SoC + 43c3 BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter + 1043 86fb PCE-AC88 43d3 BCM43567 802.11ac Wireless Network Adapter 43d9 BCM43570 802.11ac Wireless Network Adapter 43dc BCM4355 802.11ac Wireless LAN SoC @@ -22663,7 +22865,6 @@ 0224 CX9 Family [ConnectX-9 Flash Recovery] 0225 CX9 Family [ConnectX-9 Secure Flash Recovery-RMA] 0226 CX10 Family [ConnectX-10 Flash Recovery] -# Name change request 0227 CX10 Family [ConnectX-10 RMA] 0228 CX9 PCIe Switch Family [ConnectX-9 PCIe Switch Flash Recovery] 0229 CX9 PCIe Switch Family [ConnectX-9 PCIe Switch Secure Flash Recovery-RMA] @@ -22697,6 +22898,8 @@ 027c NVLink-7 Switch in Flash Recovery Mode 027d NVLink-7 Switch RMA 027e Spectrum-7 Tile +# Spectrum-8 tile + 027f Spectrum-8 tile 0281 NPS-600 Flash Recovery 0282 ArcusE Flash recovery 0283 ArcusE RMA @@ -22704,21 +22907,16 @@ 0285 Sagitta RMA 0286 LibraE Flash Recovery 0287 LibraE RMA -# Flash recovery - 0288 Arcus2 + 0288 Arcus2 Flash Recovery 0289 Arcus2 RMA 0290 SagittaZ 0292 Arcus3 Flash Recovery 0293 Arcus3 RMA - 0294 Ophy 2.1 (SagittaZ) -# Sagitta - 0296 OPHY2.6 -# Sagitta - 0298 OPHY3.0 -# Sagitta - 029a OPHY3.1 -# Sagitta - 029c OPHY3.5 + 0294 OPHY2.1 [SagittaZ] + 0296 OPHY2.6 [Sagitta] + 0298 OPHY3.0 [Sagitta] + 029a OPHY3.1 [Sagitta] + 029c OPHY3.5 [Sagitta] 02a0 NVLink-8 Switch in Flash Recovery Mode 02a1 NVLink-8 Switch RMA 02a2 Spectrum-7 in Flash Recovery Mode @@ -22726,6 +22924,10 @@ # 400G Retimer 02a6 OrionR 02a7 OrionR RMA +# Spectrum-8 in Flash Recovery Mode + 02a8 Spectrum-8 in Flash Recovery Mode +# Spectrum-8 RMA + 02a9 Spectrum-8 RMA 1002 MT25400 Family [ConnectX-2 Virtual Function] 1003 MT27500 Family [ConnectX-3] 1014 04b5 PCIe3 40GbE RoCE Converged Host Bus Adapter for Power @@ -22944,6 +23146,8 @@ # SwitchX-2, 40GbE switch c738 MT51136 c739 MT51136 GW +# Spectrum-8 + c788 Spectrum-8 c838 MT52236 c839 MT52236 router caf1 ConnectX-4 CAPI Function @@ -24190,7 +24394,28 @@ 0302 MDM9x55 LTE Modem [Snapdragon X16] 0304 SDX24 [Snapdragon X24 4G] 0306 SDX55 [Snapdragon X55 5G] - 0308 SDX62 [Snapdragon X62 5G] + 0308 SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e142 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e143 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e144 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e145 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e146 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e150 T99W696 5G Modem [Snapdragon X61] + 105b e151 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e152 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e153 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e154 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e155 T99W696 5G Modem [Snapdragon X61] 0400 Datacenter Technologies QDF2432 PCI Express Root Port 0401 Datacenter Technologies QDF2400 PCI Express Root Port 1000 QCS405 PCIe Root Complex @@ -25340,6 +25565,8 @@ 19e5 0051 Hi1822 SP681 (2*25/10GE) 19e5 0052 Hi1822 SP680 (4*25/10GE) 19e5 00a1 Hi1822 SP670 (2*100GE) + 0229 Hi1872 Family + 022a Hi1872 Family Virtual Function 1710 iBMA Virtual Network Adapter 1711 Hi171x Series [iBMC Intelligent Management system chip w/VGA support] 1712 Intelligent Management system chip Virtual Network Adapter @@ -25600,7 +25827,10 @@ 0016 SEL-3350 Serial Expansion Board 0017 SEL-3350 GPIO Expansion Board 0018 SEL-3390E4 Ethernet Adapter + 0019 SEL-2241-2/SEL-3361 Mainboard 001c SEL-3390E4 Ethernet Adapter + 001d SEL-3350 GPIO Expansion Board + 001e SEL-3350 Serial Expansion Board 1aab Silver Creations AG 7750 Sceye 10L 1aae Global Velocity, Inc. @@ -26236,7 +26466,7 @@ 5021 PCIe Gen4 SSD # 1TB 5026 FireCuda 540 SSD - 5027 LaCie Rugged SSD Pro5 + 5027 BarraCuda 530 SSD / LaCie Rugged SSD Pro5 5100 PCIe Gen3 SSD 5101 PCIe Gen5 SSD 1bb3 Bluecherry @@ -26263,6 +26493,8 @@ 5208 PCIe 3TE7 Controller 5216 PCIe 3TE8 Controller 5236 PCIe 4TG2-P Controller +# based on PCIe 4TG2-P Controller, for 4TS2-P series + 523a PCIe 4TS2-P Controller 1bcd Apacer Technology 0120 NVMe SSD Drive 960GB 0180 PB4480 NVMe SSD (DRAM-less) @@ -26582,8 +26814,20 @@ 1c5f 1421 NVMe SSD PBlaze7 7A40 1920G 2.5" U.2 1c5f 1431 NVMe SSD PBlaze7 7A40 3840G 2.5" U.2 1c5f 1441 NVMe SSD PBlaze7 7A40 7680G 2.5" U.2 + 1c5f 1631 NVMe SSD PBlaze7 7A40 3840G 2.5" U.2 + 1c5f 1641 NVMe SSD PBlaze7 7A40 7680G 2.5" U.2 + 1c5f 1651 NVMe SSD PBlaze7 7A40 15360G 2.5" U.2 + 1c5f 1661 NVMe SSD PBlaze7 7A40 30720G 2.5" U.2 + 1c5f 1751 NVMe SSD PBlaze7 7A40 Ocean 15360G 2.5" U.2 + 1c5f 1761 NVMe SSD PBlaze7 7A40 Ocean 30720G 2.5" U.2 + 1c5f 1771 NVMe SSD PBlaze7 7A40 Ocean 61440G 2.5" U.2 + 1c5f 1781 NVMe SSD PBlaze7 7A40 Ocean 122880G 2.5" U.2 1c5f 5431 NVMe SSD PBlaze7 7A46 3200G 2.5" U.2 1c5f 5441 NVMe SSD PBlaze7 7A46 6400G 2.5" U.2 + 1c5f 5631 NVMe SSD PBlaze7 7A46 3200G 2.5" U.2 + 1c5f 5641 NVMe SSD PBlaze7 7A46 6400G 2.5" U.2 + 1c5f 5651 NVMe SSD PBlaze7 7A46 12800G 2.5" U.2 + 1c5f 5661 NVMe SSD PBlaze7 7A46 25600G 2.5" U.2 003d PBlaze5 920/926 1c5f 0a30 NVMe SSD PBlaze5 920 3840G AIC 1c5f 0a31 NVMe SSD PBlaze5 920 3840G 2.5" U.2 @@ -26975,6 +27219,7 @@ efa1 Elastic Fabric Adapter (EFA) efa2 Elastic Fabric Adapter (EFA) efa3 Elastic Fabric Adapter (EFA) + efa4 Elastic Fabric Adapter (EFA) 1d17 Zhaoxin 070f ZX-100 PCI Express Root Port 0710 ZX-100/ZX-200 PCI Express Root Port @@ -28024,6 +28269,9 @@ 0033 Exceria Plus G4 NVMe SSD (DRAM-less) 0034 CM9-based E3.S NVMe SSD 0035 CM9-based U3 NVMe SSD + 003d LC9 E3.L NVMe SSD + 1028 244f RLC9GZV245T + 1028 2450 RLC9CZV245T 003e LC9 E3.S NVMe SSD 003f LC9 U.2 NVMe SSD 1e17 Arnold & Richter Cine Technik GmbH & Co. Betriebs KG @@ -28460,6 +28708,12 @@ 1e95 0001 M.2 2280 960 GB 1e95 0002 M.2 2280 1920 GB 1e95 0003 M.2 22110 3840 GB + 1e95 0004 U.2 1920 GB + 1e95 0005 U.2 3840 GB + 1e95 0006 U.2 7680 GB + 1e95 0007 U.2 1600 GB + 1e95 0008 U.2 3200 GB + 1e95 0009 U.2 6400 GB 100f EJ5-2W3840 NVMe SSD U.2 1010 CX3 Series NVMe SSD 1e95 0000 M.2 2280 480 GB @@ -28664,6 +28918,36 @@ 1ee4 0625 NVMe SSD U.2 1.6TB (P8128Z3) 1ee4 0626 NVMe SSD U.2 3.2TB (P8128Z3) 1ee4 0627 NVMe SSD U.2 6.4TB (P8128Z3) + 1ee4 0715 NVMe SSD U.2 1.92TB (P8118Z4) + 1ee4 0716 NVMe SSD U.2 3.84TB (P8118Z4) + 1ee4 0717 NVMe SSD U.2 7.68TB (P8118Z4) + 1ee4 0725 NVMe SSD U.2 1.6TB (P8118Z4) + 1ee4 0726 NVMe SSD U.2 3.2TB (P8118Z4) + 1ee4 0727 NVMe SSD U.2 6.4TB (P8118Z4) + 1ee4 0815 NVMe SSD U.2 1.92TB (P8118H4) + 1ee4 0816 NVMe SSD U.2 3.84TB (P8118H4) + 1ee4 0817 NVMe SSD U.2 7.68TB (P8118H4) + 1ee4 0825 NVMe SSD U.2 1.6TB (P8118H4) + 1ee4 0826 NVMe SSD U.2 3.2TB (P8118H4) + 1ee4 0827 NVMe SSD U.2 6.4TB (P8118H4) + 1ee4 0915 NVMe SSD U.2 1.92TB (P8118E2) + 1ee4 0916 NVMe SSD U.2 3.84TB (P8118E2) + 1ee4 0917 NVMe SSD U.2 7.68TB (P8118E2) + 1ee4 0925 NVMe SSD U.2 1.6TB (P8118E2) + 1ee4 0926 NVMe SSD U.2 3.2TB (P8118E2) + 1ee4 0927 NVMe SSD U.2 6.4TB (P8118E2) + 1ee4 0a15 NVMe SSD U.2 1.92TB (P8118H2) + 1ee4 0a16 NVMe SSD U.2 3.84TB (P8118H2) + 1ee4 0a17 NVMe SSD U.2 7.68TB (P8118H2) + 1ee4 0a25 NVMe SSD U.2 1.6TB (P8118H2) + 1ee4 0a26 NVMe SSD U.2 3.2TB (P8118H2) + 1ee4 0a27 NVMe SSD U.2 6.4TB (P8118H2) + 1ee4 0b15 NVMe SSD U.2 1.92TB (P8118H3) + 1ee4 0b16 NVMe SSD U.2 3.84TB (P8118H3) + 1ee4 0b17 NVMe SSD U.2 7.68TB (P8118H3) + 1ee4 0b25 NVMe SSD U.2 1.6TB (P8118H3) + 1ee4 0b26 NVMe SSD U.2 3.2TB (P8118H3) + 1ee4 0b27 NVMe SSD U.2 6.4TB (P8118H3) 1ee4 3013 NVMe SSD AIC 480GB (P8118E) 1ee4 3014 NVMe SSD AIC 960GB (P8118E) 1ee4 3015 NVMe SSD AIC 1.92TB (P8118E) @@ -28821,6 +29105,22 @@ 1ee4 1625 NVMe SSD M.2 1.6TB (P8128Z3) 1ee4 1626 NVMe SSD M.2 3.2TB (P8128Z3) 1ee4 1627 NVMe SSD M.2 6.4TB (P8128Z3) + 1ee4 1714 NVMe SSD M.2 960GB (P8118Z4) + 1ee4 1715 NVMe SSD M.2 1.92TB (P8118Z4) + 1ee4 1716 NVMe SSD M.2 3.84TB (P8118Z4) + 1ee4 1717 NVMe SSD M.2 7.68TB (P8118Z4) + 1ee4 1724 NVMe SSD M.2 800GB (P8118Z4) + 1ee4 1725 NVMe SSD M.2 1.6TB (P8118Z4) + 1ee4 1726 NVMe SSD M.2 3.2TB (P8118Z4) + 1ee4 1727 NVMe SSD M.2 6.4TB (P8118Z4) + 1ee4 1814 NVMe SSD M.2 960GB (P8118H4) + 1ee4 1815 NVMe SSD M.2 1.92TB (P8118H4) + 1ee4 1816 NVMe SSD M.2 3.84TB (P8118H4) + 1ee4 1817 NVMe SSD M.2 7.68TB (P8118H4) + 1ee4 1824 NVMe SSD M.2 800GB (P8118H4) + 1ee4 1825 NVMe SSD M.2 1.6TB (P8118H4) + 1ee4 1826 NVMe SSD M.2 3.2TB (P8118H4) + 1ee4 1827 NVMe SSD M.2 6.4TB (P8118H4) 1ee9 SUSE LLC 1eec Viscore Technologies Ltd 0102 VSE250231S Dual-port 10Gb/25Gb Ethernet PCIe @@ -28967,9 +29267,10 @@ 1f0f 0001 S2055AS, 2x 25GbE, SFP28, PCIe 4.0 x8 1f0f 0002 S2025XS, 2x 10GbE, SFP+, PCIe 4.0 x8 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 0001 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0002 S2025XT, 2x 10GbE, BASE-T, PCIe 4.0 x4, Fan 1f0f 0003 S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0004 S2045XT, 4x 10GbE, BASE-T, PCIe 4.0 x8, Fan 350a M18305 Family Virtual Function 1f0f 0001 M18305 Family Virtual Function 9088 D1055AS PCI Express Switch Downstream Port @@ -29100,6 +29401,17 @@ 2018 DPU Card # Network Accelerating Card 2020 DPU + 3011 K3 Family [FLEXFLOW-3100R] + 1f47 000a FLEXFLOW-3100R 1*100GE Ethernet Adapter + 1f47 000b FLEXFLOW-3100R 2*100GE Ethernet Adapter + 1f47 000c FLEXFLOW-3100R 4*100GE Ethernet Adapter + 1f47 000d FLEXFLOW-3100R 8*100GE Ethernet Adapter + 1f47 000e FLEXFLOW-3100R 1*100GE Ethernet Adapter + 1f47 000f FLEXFLOW-3100R 2*100GE Ethernet Adapter + 1f47 0010 FLEXFLOW-3100R 4*100GE Ethernet Adapter + 1f47 0011 FLEXFLOW-3100R 8*100GE Ethernet Adapter + 3012 K3 Family [FLEXFLOW-3100R Virtual Function] + 3013 K3 Family [FLEXFLOW-3100R MGMT Function] 3101 FLEXFLOW-2100R Ethernet Controller 1f47 0001 Ethernet 10G 2P FLEXFLOW-2100R 1f47 0002 Ethernet 25G 2P FLEXFLOW-2100R @@ -29118,9 +29430,6 @@ 1f47 0006 Ethernet 25G 2P FLEXFLOW-2200R 1f47 0007 Ethernet 50G 2P FLEXFLOW-2200R 1f47 0008 Ethernet 100G 2P FLEXFLOW-2200R - 3301 K3 Family [FLEXFLOW-3100R] - 1f47 0001 FLEXFLOW-3100R 1*100GE Ethernet Adapter - 3302 K3 Family [FLEXFLOW-3100R Virtual Function] 3303 K3 Family [CONFLUX-3100R MGMT Function] 4001 K2-Pro Family [CONFLUX-2200E] 1f47 0001 Ethernet 25G 2P CONFLUX-2200E @@ -29183,7 +29492,7 @@ 1f49 NeuReality LTD 1f4b Axera Semiconductor Co., Ltd 1f52 MangoBoost Inc. - 1008 Mango GPUBoost - RDMA + 1008 Mango BoostX - RoCE AI 1020 Mango NetworkBoost - TCP 1022 Mango StorageBoost - NTI 1023 Mango StorageBoost - NTT @@ -29533,6 +29842,8 @@ 2036 1005 NP36000 Virtual PCIe C2PMem 1.x Endpoint 2036 1006 NP36000 Virtual PCIe Pktgen 1.x Endpoint 203b XTX Markets Technologies Ltd. +# Vendor ID 2042 assigned by PCI-SIG +2042 Xi'an UniIC Semiconductors Co., Ltd 2044 Shenzhen Jiahua Zhongli Technology Co., LTD. 8200 CeaCent CS211X 12G SAS RAID controller 2044 2110 CeaCent CS2110-8i @@ -29725,13 +30036,21 @@ 20f6 Shenzhen Zhishi Network Technology Co., Ltd. 0001 MPU H1 20f9 Shenzhen Silicon Dynamic Networks Co., Ltd. +2100 Shenzhen Kimviking Semiconductor Co., Ltd. 2105 Shanghai Timar Integrated Circuit Co., LTD 2106 ZCHL Technology Co., Ltd 0001 HL100 Accelerator Controller 2106 0001 HLC100 Accelerator Card # HXQ 2108 HuiLink Technologies (Xiamen) Co., Ltd. +# HXQ + 2401 PCIe4.0 to USB3.2 Gen2 Host Controller +2114 EigenQ, Inc. + 0007 QMA Board M.2 Gen2 + 000a QMA Board PCIe Gen2 2116 ZyDAS Technology Corp. +# Add Vendor id 0x2123 to pci.ids +2123 Shanghai Warpdrive Technology Co., Ltd 21b4 Hunan Goke Microelectronics Co., Ltd 21c3 21st Century Computer Corp. 22b8 Flex-Logix Technologies @@ -29752,27 +30071,26 @@ 5008 A1000/U-SNS8154P3 x2 NVMe SSD [E8] 500a DC1000B NVMe SSD [E12DC] 500b DC1000M NVMe SSD [SM2270] - 500c OM8PCP Design-In PCIe 3 NVMe SSD (DRAM-less) - 500d OM3PDP3 NVMe SSD + 500c OM8PCP3 PCIe 3 NVMe SSD (DRAM-less) + 500d OM3PDP3 PCIe 3 NVMe SSD (DRAM-less) 500e NV1 NVMe SSD [E13T] (DRAM-less) 500f NV1 NVMe SSD [SM2263XT] (DRAM-less) 5010 OM8SBP NVMe PCIe SSD (DRAM-less) 5012 DC1500M NVMe SSD [SM2270] 5013 KC3000/FURY Renegade NVMe SSD [E18] - 5014 OM8SEP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) - 5016 OM3PGP4 NVMe SSD (DRAM-less) + 5014 OM8SEP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) + 5016 OM3PGP4 PCIe 4 NVMe SSD (DRAM-less) 5017 NV2 NVMe SSD [SM2267XT] (DRAM-less) 5018 OM8SFP4 PCIe 4 NVMe SSD (DRAM-less) 5019 NV2 NVMe SSD [E21T] (DRAM-less) -# 128GB - 501a OM8PGP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + 501a OM8PGP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) 501b OM8PGP4 NVMe PCIe SSD (DRAM-less) 501c NV2 NVMe SSD [E19T] (DRAM-less) 501d NV2 NVMe SSD [TC2200] (DRAM-less) 501e OM3PGP4 NVMe SSD (DRAM-less) 501f FURY Renegade NVMe SSD [E18] (Heatsink) - 5021 OM8SEP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) - 5022 OM8PGP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + 5021 OM8SEP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) + 5022 OM8PGP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) 5023 NV2 NVMe SSD [SM2269XT] (DRAM-less) 5024 DC2000B NVMe SSD [E18DC] 5025 NV3 NVMe SSD [TC2201] (DRAM-less) @@ -29784,6 +30102,7 @@ 502c DC3000ME NVMe SSD [SC5] 502d OM8TAP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) 5030 NV3 2230 NVMe SSD [SM2268XT2] (DRAM-less) + 5034 NV3 NVMe SSD [E33T] (DRAM-less) 270b Xantel Corporation 270f Chaintech Computer Co. Ltd 2711 AVID Technology Inc. @@ -29978,7 +30297,16 @@ 434e 0003 CN5000 SuperNIC, Single Port, QSFP, x16 PCIe Gen 5, II 434e 0004 CN5000 SuperNIC, Dual Port, QSFP-DD, x16 PCIe Gen 5, II 0002 CN6000 HFI Silicon, Dual Port, BGA [discrete] + 434e 0001 CN6000 SuperNIC, Single Port, QSFP-DD, x16 PCIe Gen 6 8001 CN5000 Switch Silicon, 48 Port, BGA +# Add CN5000 Switch + 434e 0101 CN5000 Switch +# Add CN5000 Director Class Switch Spine + 434e 0103 CN5000 Director Class Switch Spine +# Add CN5000 Director Class Switch Leaf + 434e 0104 CN5000 Director Class Switch Leaf +# Add CN6000 Switch + 434e 0106 CN6000 Switch 4444 Internext Compression Inc 0016 iTVC16 (CX23416) Video Decoder 0070 0003 WinTV PVR 250 @@ -30209,6 +30537,13 @@ 4c53 3002 PLUSTEST-MM card (PMC) 4c54 Lisuan Technology Co., Ltd. 5000 LISUAN 7G100 Series Graphics +# LISUAN 7G100 Series Graphics + 5001 LISUAN 7G100 Series Graphics + 5002 LISUAN 7G100 Series Graphics + 5003 LISUAN 7G100 Series Graphics + 5004 LISUAN 7G100 Series Graphics + 5005 LISUAN 7G100 Series Graphics + 5006 LISUAN 7G100 Series Graphics 4ca1 Seanix Technology Inc 4d51 MediaQ Inc. 0200 MQ-200 @@ -30697,7 +31032,7 @@ 10cf 16bf LIFEBOOK E752 0155 Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port 8086 2010 Server Board S1200BTS - 0156 3rd Gen Core processor Graphics Controller + 0156 Ivy Bridge mobile GT1 [HD Graphics] 1043 108d VivoBook X202EV 0158 Xeon E3-1200 v2/Ivy Bridge DRAM Controller 1043 844d P8 series motherboard @@ -30711,7 +31046,7 @@ 0162 IvyBridge GT2 [HD Graphics 4000] 1043 84ca P8 series motherboard 1849 0162 Motherboard - 0166 3rd Gen Core processor Graphics Controller + 0166 Ivy Bridge mobile GT2 [HD Graphics 4000] 1043 1517 Zenbook Prime UX31A 1043 2103 N56VZ 10cf 16c1 LIFEBOOK E752 @@ -30720,38 +31055,63 @@ 0172 Xeon E3-1200 v2/3rd Gen Core processor Graphics Controller 0176 3rd Gen Core processor Graphics Controller 0201 Arctic Sound - 0284 Comet Lake PCH-LP LPC Premium Controller/eSPI Controller + 0284 400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller 1028 09be Latitude 7410 - 02a3 Comet Lake PCH-LP SMBus Host Controller + 0285 400 Series Chipset Family On-Package PCH-LP Mainstream/Base U LPC/eSPI Controller + 02a0 400 Series Chipset Family On-Package P2SB + 02a1 400 Series Chipset Family On-Package PMC + 02a3 400 Series Chipset Family On-Package SMBus 1028 09be Latitude 7410 - 02a4 Comet Lake SPI (flash) Controller + 02a4 400 Series Chipset Family On-Package SPI (flash) Controller 1028 09be Latitude 7410 - 02a6 Comet Lake North Peak - 02b0 Comet Lake PCI Express Root Port #9 - 02b1 Comet Lake PCI Express Root Port #10 - 02b3 Comet Lake PCI Express Root Port #12 - 02b4 Comet Lake PCI Express Root Port #13 - 02b5 Comet Lake PCI Express Root Port #14 - 02b8 Comet Lake PCI Express Root Port #1 - 02bc Comet Lake PCI Express Root Port #5 - 02bf Comet Lake PCI Express Root Port #8 - 02c5 Comet Lake Serial IO I2C Host Controller + 02a6 400 Series Chipset Family On-Package Trace Hub + 02a8 400 Series Chipset Family On-Package UART #0 + 02a9 400 Series Chipset Family On-Package UART #1 + 02aa 400 Series Chipset Family On-Package SPI #0 + 02ab 400 Series Chipset Family On-Package SPI #1 + 02b0 400 Series Chipset Family On-Package PCIe Root Port #9 + 02b1 400 Series Chipset Family On-Package PCIe Root Port #10 + 02b2 400 Series Chipset Family On-Package PCIe Root Port #11 + 02b3 400 Series Chipset Family On-Package PCIe Root Port #12 + 02b4 400 Series Chipset Family On-Package PCIe Root Port #13 + 02b5 400 Series Chipset Family On-Package PCIe Root Port #14 + 02b6 400 Series Chipset Family On-Package PCIe Root Port #15 + 02b7 400 Series Chipset Family On-Package PCIe Root Port #16 + 02b8 400 Series Chipset Family On-Package PCIe Root Port #1 + 02b9 400 Series Chipset Family On-Package PCIe Root Port #2 + 02ba 400 Series Chipset Family On-Package PCIe Root Port #3 + 02bb 400 Series Chipset Family On-Package PCIe Root Port #4 + 02bc 400 Series Chipset Family On-Package PCIe Root Port #5 + 02bd 400 Series Chipset Family On-Package PCIe Root Port #6 + 02be 400 Series Chipset Family On-Package PCIe Root Port #7 + 02bf 400 Series Chipset Family On-Package PCIe Root Port #8 + 02c4 400 Series Chipset Family On-Package eMMC + 02c5 400 Series Chipset Family On-Package I2C #4 1028 09be Latitude 7410 - 02c8 Comet Lake PCH-LP cAVS + 02c6 400 Series Chipset Family On-Package I2C #5 + 02c7 400 Series Chipset Family On-Package UART #2 + 02c8 400 Series Chipset Family On-Package HD Audio 1028 09be Latitude 7410 - 02d3 Comet Lake SATA AHCI Controller - 02d7 Comet Lake RAID Controller - 02e0 Comet Lake Management Engine Interface + 02d3 400 Series Chipset Family On-Package SATA Controller (AHCI) + 02d5 400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + 02d7 400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + 02e0 400 Series Chipset Family On-Package MEI #1 1028 09be Latitude 7410 - 02e3 Comet Lake AMT SOL Redirection - 02e8 Serial IO I2C Host Controller + 02e1 400 Series Chipset Family On-Package MEI #2 + 02e2 400 Series Chipset Family On-Package IDE Redirection (IDER-R) + 02e3 400 Series Chipset Family On-Package Keyboard and Text (KT) Redirection + 02e4 400 Series Chipset Family On-Package MEI #3 + 02e5 400 Series Chipset Family On-Package MEI #4 + 02e8 400 Series Chipset Family On-Package I2C #0 1028 09be Latitude 7410 - 02e9 Comet Lake Serial IO I2C Host Controller + 02e9 400 Series Chipset Family On-Package I2C #1 1028 09be Latitude 7410 - 02ea Comet Lake PCH-LP LPSS: I2C Controller #2 - 02ed Comet Lake PCH-LP USB 3.1 xHCI Host Controller + 02ea 400 Series Chipset Family On-Package I2C #2 + 02eb 400 Series Chipset Family On-Package I2C #3 + 02ed 400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller 1028 09be Latitude 7410 - 02ef Comet Lake PCH-LP Shared SRAM + 02ee 400 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 02ef 400 Series Chipset Family On-Package Shared SRAM 1028 09be Latitude 7410 02f0 Comet Lake PCH-LP CNVi WiFi 8086 0034 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak] @@ -30761,10 +31121,11 @@ 8086 0264 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9461 80MHz 1x1 [Jefferson Peak] 8086 02a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] 8086 4070 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] - 02f5 Comet Lake PCH-LP SCS3 - 02f9 Comet Lake Thermal Subsytem + 02f5 400 Series Chipset Family On-Package SDXC + 02f9 400 Series Chipset Family On-Package Thermal Subsystem 1028 09be Latitude 7410 - 02fc Comet Lake Integrated Sensor Solution + 02fb 400 Series Chipset Family On-Package SPI #2 + 02fc 400 Series Chipset Family On-Package Integrated Sensor Hub 1028 09be Latitude 7410 0309 80303 I/O Processor PCI-to-PCI Bridge 030d 80312 I/O Companion Chip PCI-to-PCI Bridge @@ -30856,33 +31217,59 @@ 068d HM470 Chipset LPC/eSPI Controller 068e WM490 Chipset LPC/eSPI Controller 0697 W480 Chipset LPC/eSPI Controller - 06a3 Comet Lake PCH SMBus Controller - 06a4 Comet Lake PCH SPI Controller - 06a8 Comet Lake PCH Serial IO UART Host Controller #0 - 06a9 Comet Lake PCH Serial IO UART Host Controller #1 - 06aa Comet Lake PCH Serial IO SPI Controller #0 - 06ab Comet Lake PCH Serial IO SPI Controller #1 - 06ac Comet Lake PCI Express Root Port #21 - 06b0 Comet Lake PCI Express Root Port #9 - 06b8 Comet Lake PCIe Root Port #1 - 06ba Comet Lake PCI Express Root Port #1 - 06bb Comet Lake PCI Express Root Port #4 - 06bd Comet Lake PCIe Port #6 - 06be Comet Lake PCIe Root Port #7 - 06bf Comet Lake PCIe Port #8 - 06c0 Comet Lake PCI Express Root Port #17 - 06c8 Comet Lake PCH cAVS - 06d2 Comet Lake SATA AHCI Controller + 06a0 400 Series Chipset Family Platform P2SB + 06a1 400 Series Chipset Family Platform PMC + 06a3 400 Series Chipset Family Platform SMBus + 06a4 400 Series Chipset Family Platform SPI (flash) Controller + 06a6 400 Series Chipset Family Platform Trace Hub + 06a8 400 Series Chipset Family Platform UART #0 + 06a9 400 Series Chipset Family Platform UART #1 + 06aa 400 Series Chipset Family Platform GSPI #0 + 06ab 400 Series Chipset Family Platform GSPI #1 + 06ac 400 Series Chipset Family PCIe Root Port #21 + 06ad 400 Series Chipset Family PCIe Root Port #22 + 06ae 400 Series Chipset Family PCIe Root Port #23 + 06af 400 Series Chipset Family PCIe Root Port #24 + 06b0 400 Series Chipset Family PCIe Root Port #9 + 06b1 400 Series Chipset Family PCIe Root Port #10 + 06b2 400 Series Chipset Family PCIe Root Port #11 + 06b3 400 Series Chipset Family PCIe Root Port #12 + 06b4 400 Series Chipset Family PCIe Root Port #13 + 06b5 400 Series Chipset Family PCIe Root Port #14 + 06b6 400 Series Chipset Family PCIe Root Port #15 + 06b7 400 Series Chipset Family PCIe Root Port #16 + 06b8 400 Series Chipset Family PCIe Root Port #1 + 06b9 400 Series Chipset Family PCIe Root Port #2 + 06ba 400 Series Chipset Family PCIe Root Port #3 + 06bb 400 Series Chipset Family PCIe Root Port #4 + 06bc 400 Series Chipset Family PCIe Root Port #5 + 06bd 400 Series Chipset Family PCIe Root Port #6 + 06be 400 Series Chipset Family PCIe Root Port #7 + 06bf 400 Series Chipset Family PCIe Root Port #8 + 06c0 400 Series Chipset Family PCIe Root Port #17 + 06c1 400 Series Chipset Family PCIe Root Port #18 + 06c2 400 Series Chipset Family PCIe Root Port #19 + 06c3 400 Series Chipset Family PCIe Root Port #20 + 06c7 400 Series Chipset Family UART #2 + 06c8 400 Series Chipset Family HD Audio + 06d2 400 Series Chipset Family SATA Controller (AHCI) (Desktop) + 06d3 400 Series Chipset Family SATA Controller (AHCI) (Mobile) + 06d5 400 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) 06d6 Comet Lake PCH-H RAID - 06d7 Comet Lake PCH-H RAID - 06e0 Comet Lake HECI Controller - 06e3 Comet Lake Keyboard and Text (KT) Redirection - 06e8 Comet Lake PCH Serial IO I2C Controller #0 - 06e9 Comet Lake PCH Serial IO I2C Controller #1 - 06ea Comet Lake PCH Serial IO I2C Controller #2 - 06eb Comet Lake PCH Serial IO I2C Controller #3 - 06ed Comet Lake USB 3.1 xHCI Host Controller - 06ef Comet Lake PCH Shared SRAM + 06d7 400 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + 06de 400 Series Chipset Family SATA Controller (AHCI) Optane Caching + 06e0 400 Series Chipset Family HECI #1 + 06e1 400 Series Chipset Family HECI #2 + 06e2 400 Series Chipset Family IDE Redirection (IDE-R) + 06e3 400 Series Chipset Family Keyboard and Text (KT) Redirection + 06e4 400 Series Chipset Family HECI #3 + 06e5 400 Series Chipset Family HECI #4 + 06e8 400 Series Chipset Family I2C #0 + 06e9 400 Series Chipset Family I2C #1 + 06ea 400 Series Chipset Family I2C #2 + 06eb 400 Series Chipset Family I2C #3 + 06ed 400 Series Chipset Family USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + 06ef 400 Series Chipset Family Shared SRAM 06f0 Comet Lake PCH CNVi WiFi 1a56 1651 Dual Band Wi-Fi 6(802.11ax) Killer AX1650s 160MHz 2x2 [Cyclone Peak] 1a56 1652 Dual Band Wi-Fi 6(802.11ax) Killer AX1650i 160MHz 2x2 [Cyclone Peak] @@ -30890,9 +31277,10 @@ 8086 0074 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] 8086 02a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] 8086 42a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] - 06f9 Comet Lake PCH Thermal Controller - 06fb Comet Lake PCH Serial IO SPI Controller #2 - 06fc Comet Lake PCH Integrated Sensor Solution + 06f5 400 Series Chipset Family SCS3 SDXC + 06f9 400 Series Chipset Family Thermal Subsystem + 06fb 400 Series Chipset Family GSPI #2 + 06fc 400 Series Chipset Family Integrated Sensor Hub 0700 CE Media Processor A/V Bridge 0701 CE Media Processor NAND Flash Controller 0703 CE Media Processor Media Control Unit 1 @@ -31160,12 +31548,12 @@ 0962 80960RM (i960RM) Bridge 0964 80960RP (i960RP) Microprocessor/Bridge 0975 Optane NVME SSD H10 with Solid State Storage [Teton Glacier] - 0998 Ice Lake IEH - 09a2 Ice Lake Memory Map/VT-d - 09a3 Ice Lake RAS - 09a4 Ice Lake Mesh 2 PCIe - 09a6 Ice Lake MSM - 09a7 Ice Lake PMON MSM + 0998 Xeon 6900 6700 6500 Series with P-Cores Processors IEH + 09a2 Xeon 6900 6700 6500 Series with P-Cores Processors VT-d + 09a3 Xeon 6900 6700 6500 Series with P-Cores Processors RAS + 09a4 Xeon 6900 6700 6500 Series with P-Cores Processors UFI + 09a6 Xeon 6900 6700 6500 Series with P-Cores Processors MSM + 09a7 Xeon 6900 6700 6500 Series with P-Cores Processors PMON MSM 09ab RST VMD Managed Controller 09ad Optane NVME SSD H20 with Solid State Storage [Pyramid Glacier] 09c4 PAC with Intel Arria 10 GX FPGA @@ -31243,7 +31631,7 @@ 1028 1fe8 Express Flash NVMe 2.0TB HHHL AIC (P4600) 1028 1fe9 Express Flash NVMe 4.0TB HHHL AIC (P4600) 0b00 Ice Lake CBDMA [QuickData Technology] - 0b23 Xeon Root Event Collector + 0b23 Xeon 6900 6700 6500 Series with P-Cores Processors IEH 0b25 Data Streaming Accelerator (DSA) 0b26 Thunderbolt 4 Bridge [Goshen Ridge 2020] 0b27 Thunderbolt 4 USB Controller [Goshen Ridge 2020] @@ -31377,17 +31765,26 @@ 0d22 Crystal Well Integrated Iris Pro Graphics 5200 0d26 Crystal Well Integrated Graphics Controller 0d36 Crystal Well Integrated Graphics Controller - 0d4c Ethernet Connection (11) I219-LM - 0d4d Ethernet Connection (11) I219-V + 0d4c 400 Series Chipset Family Platform GbE Controller (Corporate/vPro) + 0d4d 400 Series Chipset Family Platform GbE Controller (Consumer) 8086 0d4d Ethernet Connection (11) I219-V - 0d4e Ethernet Connection (10) I219-LM - 0d4f Ethernet Connection (10) I219-V + 0d4e 400 Series Chipset Family On-Package GbE Controller (Corporate/vPro) + 0d4f 400 Series Chipset Family On-Package GbE Controller (Consumer) 0d53 Ethernet Connection (12) I219-LM 0d55 Ethernet Connection (12) I219-V 0d58 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 8086 0000 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 8086 0001 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 0d9f Ethernet Controller I225-IT + 0db0 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #0 + 0db1 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #1 + 0db2 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #2 + 0db3 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #3 + 0db4 Xeon 6900 6700 6500 Series with P-Cores Processors NTB + 0db6 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #4 + 0db7 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #5 + 0db8 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #6 + 0db9 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #7 0dc5 Ethernet Connection (23) I219-LM 1028 0c06 Precision 3580 0dc6 Ethernet Connection (23) I219-V @@ -32159,6 +32556,7 @@ 11a5 Merrifield Serial IO PWM Controller 11c3 Quark SoC X1000 PCIe Root Port 0 11c4 Quark SoC X1000 PCIe Root Port 1 + 11df Infrastructure Data Path Function 11eb Simics NVMe Controller 1200 IXP1200 Network Processor 172a 0000 AEP SSL Accelerator @@ -32874,6 +33272,7 @@ 8086 000c Ethernet Network Adapter XXV710-DA2 for OCP 3.0 8086 000d Ethernet 25G 2P XXV710 OCP 8086 4001 Ethernet Network Adapter XXV710-2 + 1590 Ethernet Connection E810-C 1591 Ethernet Controller E810-C for backplane 8086 bcce Ethernet Controller E810-C for Intel(R) Open FPGA Stack 1592 Ethernet Controller E810-C for QSFP @@ -32922,6 +33321,9 @@ 8086 4010 Ethernet Network Adapter E810-XXV-4 8086 4013 Ethernet Network Adapter E810-XXV-4 for OCP 3.0 8086 401c Ethernet Network Adapter E810-XXV-4 for OCP 3.0 + 1594 Ethernet Controller E810-C/X557-AT 10GBASE-T + 1595 Ethernet Controller E810-C 1GbE + 1598 Ethernet Connection E810-XXV 1599 Ethernet Controller E810-XXV for backplane 8086 0001 Ethernet 25G 2P E810-XXV-k Mezz 159a Ethernet Controller E810-XXV for QSFP @@ -32945,6 +33347,8 @@ 8086 4002 Ethernet Network Adapter E810-XXV-2 for OCP 3.0 8086 4003 Ethernet Network Adapter E810-XXV-2 8086 4015 Ethernet Network Adapter E810-XXV-2 for OCP 3.0 + 159c Ethernet Controller E810-XXV/X557-AT 10GBASE-T + 159d Ethernet Controller E810-XXV 1GbE 15a0 Ethernet Connection (2) I218-LM 15a1 Ethernet Connection (2) I218-V 15a2 Ethernet Connection (3) I218-LM @@ -33036,8 +33440,8 @@ 15f4 Ethernet Connection (15) I219-LM 15f5 Ethernet Connection (15) I219-V 15f6 I210 Gigabit Ethernet Connection - 15f9 Ethernet Connection (14) I219-LM - 15fa Ethernet Connection (14) I219-V + 15f9 500 Series Chipset Family GbE Controller (Corporate/vPro) + 15fa 500 Series Chipset Family GbE Controller (Consumer) 15fb Ethernet Connection (13) I219-LM 15fc Ethernet Connection (13) I219-V 15ff Ethernet Controller X710 for 10GBASE-T @@ -35148,6 +35552,7 @@ 8086 3905 NVMe Datacenter SSD [Optane] 15mm 2.5" U.2 (P4800X) 2710 Dynamic Load Balancer 2.0 (DLB) 2714 Dynamic Load Balancer 2.5 (DLB) + 2715 Dynamic Load Balancer (DLB) Virtual Function 2723 Wi-Fi 6 AX200 1a56 1654 Killer Wi-Fi 6 AX1650x (AX200NGW) 8086 0084 Wi-Fi 6 AX200NGW @@ -35572,7 +35977,7 @@ 1028 01da OptiPlex 745 1462 7235 P965 Neo MS-7235 mainboard 2821 82801HR/HO/HH (ICH8R/DO/DH) 6 port SATA Controller [AHCI mode] - 2822 SATA Controller [RAID mode] + 2822 SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) 1028 020d Inspiron 530 103c 2a6f Asus IPIBL-LB Motherboard 1043 8277 P5K PRO Motherboard: 82801IR [ICH9R] @@ -35583,7 +35988,7 @@ 2825 82801HR/HO/HH (ICH8R/DO/DH) 2 port SATA Controller [IDE mode] 1028 01da OptiPlex 745 1462 7235 P965 Neo MS-7235 mainboard - 2826 SATA Controller [RAID Mode] + 2826 SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) 1d49 0100 Intel RSTe SATA Software RAID 1d49 0101 Intel RSTe SATA Software RAID 1d49 0102 Intel RSTe SATA Software RAID @@ -35609,7 +36014,7 @@ 17aa 20a7 ThinkPad T61/R61 17c0 4083 Medion WIM 2210 Notebook PC [MD96850] e4bf cc47 CCG-RUMBA - 282a 82801 Mobile SATA Controller [RAID mode] + 282a SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) 1028 040b Latitude E6510 e4bf 50c1 PC1-GROOVE 282b C740 Series (Emmitsburg) Chipsets SATA2 Controller (RAID) Alternate ID @@ -36742,10 +37147,17 @@ 31f0 Gemini Lake Host Bridge 3200 GD31244 PCI-X SATA HBA 1775 c200 C2K onboard SATA host bus adapter - 3245 Xeon UPI Mesh Stop M2UPI Registers - 324a Xeon IMC0 Mesh to Mem Registers - 324c Xeon Unicast Group1 CHA Registers - 324d Xeon Unicast Group0 CHA Registers + 3240 Xeon 6900 6700 6500 Series with P-Cores Processors UPI Misc + 3241 Xeon 6900 6700 6500 Series with P-Cores Processors UPI link/Phy0 + 3242 Xeon 6900 6700 6500 Series with P-Cores Processors UPI Phy0 + 3245 Xeon 6900 6700 6500 Series with P-Cores Processors UPI + 324a Xeon 6900 6700 6500 Series with P-Cores Processors IMC + 324c Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 1 + 324d Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 0 + 3250 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Event Control + 3251 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Register Access Control Unit + 3252 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Decode + 3256 Xeon 6900 6700 6500 Series with P-Cores Processors Trace Hub 3258 Power Control Unit (PCU) CR0 3259 Power Control Unit (PCU) CR1 325a Power Control Unit (PCU) CR2 @@ -36838,13 +37250,13 @@ 344b Ice Lake PCU Registers 344c Ice Lake CHA Registers 344d Ice Lake CHA Registers - 344f Ice Lake CHA Registers + 344f Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 0 3450 Ice Lake Ubox Registers 3451 Ice Lake Ubox Registers 3452 Ice Lake Ubox Registers 3455 Ice Lake Ubox Registers 3456 Ice Lake NorthPeak - 3457 Ice Lake CHA Registers + 3457 Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 1 3458 Ice Lake PCU Registers 3459 Ice Lake PCU Registers 345a Ice Lake PCU Registers @@ -37537,6 +37949,7 @@ 3e98 CoffeeLake-S GT2 [UHD Graphics 630] 3e9a Coffee Lake-S GT2 [UHD Graphics P630] 3e9b CoffeeLake-H GT2 [UHD Graphics 630] + 106b 019c MacBookPro16,1 (16", 2019) 3e9c Coffee Lake-S GT1 [UHD Graphics 610] 3ea0 WhiskeyLake-U GT2 [UHD Graphics 620] 1028 089e Inspiron 5482 @@ -37682,44 +38095,95 @@ 8086 1216 WiMAX/WiFi Link 5150 ABG 8086 1311 WiMAX/WiFi Link 5150 AGN 8086 1316 WiMAX/WiFi Link 5150 ABG - 4384 Q570 LPC/eSPI Controller - 4385 Z590 LPC/eSPI Controller - 4386 H570 LPC/eSPI Controller - 4387 B560 LPC/eSPI Controller - 4388 H510 LPC/eSPI Controller - 4389 WM590 LPC/eSPI Controller - 438a QM580 LPC/eSPI Controller - 438b HM570 LPC/eSPI Controller - 438c C252 LPC/eSPI Controller - 438d C256 LPC/eSPI Controller + 4384 Q570 Chipset eSPI Controller + 4385 Z590 Chipset eSPI Controller + 4386 H570 Chipset eSPI Controller + 4387 B560 Chipset eSPI Controller + 4388 H510 Chipset eSPI Controller + 4389 WM590 Chipset eSPI Controller + 438a QM580 Chipset eSPI Controller + 438b HM570 Chipset eSPI Controller + 438c C252 Chipset eSPI Controller + 438d C256 Chipset eSPI Controller 438e H310D LPC/eSPI Controller - 438f W580 LPC/eSPI Controller + 438f W580 Chipset eSPI Controller 4390 RM590E LPC/eSPI Controller 4391 R580E LPC/eSPI Controller - 43a3 Tiger Lake-H SMBus Controller - 43a4 Tiger Lake-H SPI Controller - 43b0 Tiger Lake-H PCI Express Root Port #9 - 43b8 Tiger Lake-H PCIe Root Port #1 - 43ba Tiger Lake-H PCIe Root Port #3 - 43bb Tiger Lake-H PCIe Root Port #4 - 43bc Tiger Lake-H PCI Express Root Port #5 - 43be 11th Gen Core Processor PCIe Root Port #7 - 43c0 Tiger Lake-H PCIe Root Port #17 - 43c7 Tiger Lake-H PCIe Root Port #24 - 43c8 Tiger Lake-H HD Audio Controller - 43d3 Tiger Lake SATA AHCI Controller - 43e0 Tiger Lake-H Management Engine Interface - 43e3 Tiger Lake AMT SOL Redirection - 43e8 Tiger Lake-H Serial IO I2C Controller #0 - 43e9 Tiger Lake-H Serial IO I2C Controller #1 - 43ed Tiger Lake-H USB 3.2 Gen 2x1 xHCI Host Controller - 43ef Tiger Lake-H Shared SRAM + 43a0 500 Series Chipset Family P2SB + 43a1 500 Series Chipset Family PMC + 43a3 500 Series Chipset Family SMBus + 43a4 500 Series Chipset Family SPI (flash) Controller + 43a6 500 Series Chipset Family Trace Hub + 43a7 500 Series Chipset Family UART #2 + 43a8 500 Series Chipset Family UART #0 + 43a9 500 Series Chipset Family UART #1 + 43aa 500 Series Chipset Family GSPI #0 + 43ab 500 Series Chipset Family GSPI #1 + 43ad 500 Series Chipset Family I2C #4 + 43ae 500 Series Chipset Family I2C #5 + 43b0 500 Series Chipset Family PCIe Root Port #9 + 43b1 500 Series Chipset Family PCIe Root Port #10 + 43b2 500 Series Chipset Family PCIe Root Port #11 + 43b3 500 Series Chipset Family PCIe Root Port #12 + 43b4 500 Series Chipset Family PCIe Root Port #13 + 43b5 500 Series Chipset Family PCIe Root Port #14 + 43b6 500 Series Chipset Family PCIe Root Port #15 + 43b7 500 Series Chipset Family PCIe Root Port #16 + 43b8 500 Series Chipset Family PCIe Root Port #1 + 43b9 500 Series Chipset Family PCIe Root Port #2 + 43ba 500 Series Chipset Family PCIe Root Port #3 + 43bb 500 Series Chipset Family PCIe Root Port #4 + 43bc 500 Series Chipset Family PCIe Root Port #5 + 43bd 500 Series Chipset Family PCIe Root Port #6 + 43be 500 Series Chipset Family PCIe Root Port #7 + 43bf 500 Series Chipset Family PCIe Root Port #8 + 43c0 500 Series Chipset Family PCIe Root Port #17 + 43c1 500 Series Chipset Family PCIe Root Port #18 + 43c2 500 Series Chipset Family PCIe Root Port #19 + 43c3 500 Series Chipset Family PCIe Root Port #20 + 43c4 500 Series Chipset Family PCIe Root Port #21 + 43c5 500 Series Chipset Family PCIe Root Port #22 + 43c6 500 Series Chipset Family PCIe Root Port #23 + 43c7 500 Series Chipset Family PCIe Root Port #24 + 43c8 500 Series Chipset Family HD Audio + 43c9 500 Series Chipset Family HD Audio + 43ca 500 Series Chipset Family HD Audio + 43cb 500 Series Chipset Family HD Audio + 43cc 500 Series Chipset Family HD Audio + 43cd 500 Series Chipset Family HD Audio + 43ce 500 Series Chipset Family HD Audio + 43cf 500 Series Chipset Family HD Audio + 43d0 500 Series Chipset Family Touch Host Controller (THC) #0 + 43d1 500 Series Chipset Family Touch Host Controller (THC) #1 + 43d2 500 Series Chipset Family SATA Controller (AHCI) (Server/Desktop) + 43d3 500 Series Chipset Family SATA Controller (AHCI) (Mobile) + 43d4 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Desktop) + 43d5 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) + 43d6 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Server/Desktop) + 43d7 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + 43d8 500 Series Chipset Family I2C #6 + 43da 500 Series Chipset Family UART #3 + 43e0 500 Series Chipset Family CSME HECI #1 + 43e1 500 Series Chipset Family CSME HECI #2 + 43e2 500 Series Chipset Family CSME IDE Redirection (IDE-R) + 43e3 500 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 43e4 500 Series Chipset Family CSME HECI #3 + 43e5 500 Series Chipset Family CSME HECI #4 + 43e8 500 Series Chipset Family I2C #0 + 43e9 500 Series Chipset Family I2C #1 + 43ea 500 Series Chipset Family I2C #2 + 43eb 500 Series Chipset Family I2C #3 + 43ed 500 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + 43ee 500 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 43ef 500 Series Chipset Family Shared SRAM 43f0 Tiger Lake PCH CNVi WiFi 8086 0034 Wireless-AC 9560 8086 0074 Wi-Fi 6 AX201 160MHz 8086 0264 Wireless-AC 9461 8086 02a4 Wireless-AC 9462 - 43fc Tiger Lake-H Integrated Sensor Hub + 43fb 500 Series Chipset Family GSPI #2 + 43fc 500 Series Chipset Family Integrated Sensor Hub + 43fd 500 Series Chipset Family GSPI #3 444e Turbo Memory Controller 4511 Elkhart Lake Gaussian and Neural Accelerator 4538 Elkhart Lake PCI-e Root Complex @@ -38058,8 +38522,8 @@ 5503 Ethernet Controller I226-LMvP 550a Ethernet Connection (18) I219-LM 550b Ethernet Connection (18) I219-LM - 550c Ethernet Connection (19) I219-LM - 550d Ethernet Connection (19) I219-V + 550c 800 Series Chipset Family GbE Controller (Corporate/vPro) + 550d 800 Series Chipset Family GbE Controller (Consumer) 550e Ethernet Connection (20) I219-LM 550f Ethernet Connection (20) I219-V 5510 Ethernet Connection (21) I219-LM @@ -38108,9 +38572,12 @@ 5785 JHL9540 Thunderbolt 4 USB Controller [Barlow Ridge Host 40G 2023] 5786 JHL9480 Thunderbolt 5 80/120G Bridge [Barlow Ridge Hub 80G 2023] 5787 JHL9480 Thunderbolt 5 80/120G USB Controller [Barlow Ridge Hub 80G 2023] - 5794 Granite Rapids SPI Controller - 5795 Granite Rapids Chipset LPC Controller - 5796 Granite Rapids SMBus Controller + 5792 Xeon 6900 6700 6500 Series with P-Cores Processors ILMI + 5793 Xeon 6900 6700 6500 Series with P-Cores Processors ACPI + 5794 Xeon 6900 6700 6500 Series with P-Cores Processors SPI + 5795 Xeon 6900 6700 6500 Series with P-Cores Processors eSPI + 5796 Xeon 6900 6700 6500 Series with P-Cores Processors SMBus + 5797 Xeon 6900 6700 6500 Series with P-Cores Processors UART 579c Ethernet Connection E825-C for backplane 579d Ethernet Connection E825-C for QSFP 579e Ethernet Connection E825-C for SFP @@ -38119,15 +38586,16 @@ 57a1 Ethernet Connection (24) I219-V 57a4 JHL9440 Thunderbolt 4 Bridge [Barlow Ridge Hub 40G 2023] 57a5 JHL9440 Thunderbolt 4 USB Controller [Barlow Ridge Hub 40G 2023] - 57ad E610 Virtual Function + 57ac Ethernet Controller E610 + 57ad Ethernet Controller E610 Virtual Function 57ae Ethernet Controller E610 Backplane 57af Ethernet Controller E610 SFP - 57b0 Ethernet Controller E610 10GBASE T + 57b0 Ethernet Controller E610-XT/E610-XT2 10GBASE-T 8086 0001 Ethernet Network Adapter E610-XT4 8086 0002 Ethernet Network Adapter E610-XT2 8086 0003 Ethernet Network Adapter E610-XT4 for OCP 3.0 8086 0004 Ethernet Network Adapter E610-XT2 for OCP 3.0 - 57b1 Ethernet Controller E610 2.5GBASE T + 57b1 Ethernet Controller E610-AT2 1000BASE-T 8086 0000 Ethernet Converged Network Adapter E610 8086 0002 Ethernet Network Adapter E610-IT4 8086 0003 Ethernet Network Adapter E610-IT4 for OCP 3.0 @@ -38553,7 +39021,7 @@ 777a Arrow Lake-H [Serial IO I2C Host Controller] 777b Arrow Lake-H [Serial IO I2C Host Controller] 777d Arrow Lake USB 3.2 xHCI Controller - 777f Arrow Lake Shared SRAM + 777f Core Ultra 200H/200V Series Processors Shared SRAM 7800 82740 (i740) AGP Graphics Accelerator 003d 0008 Starfighter AGP 003d 000b Starfighter AGP @@ -38562,114 +39030,147 @@ 10b4 202f Lightspeed 740 8086 0000 Terminator 2x/i 8086 0100 Intel740 Graphics Accelerator - 7a04 Z790 Chipset LPC/eSPI Controller - 7a05 H770 Chipset LPC/eSPI Controller - 7a06 B760 Chipset LPC/eSPI Controller - 7a0c HM770 Chipset LPC/eSPI Controller + 7a04 Z790 Chipset eSPI Controller + 7a05 H770 Chipset eSPI Controller + 7a06 B760 Chipset eSPI Controller + 7a0c HM770 Chipset eSPI Controller 7a0d WM790 Chipset LPC/eSPI Controller - 7a13 C266 Chipset LPC/eSPI Controller - 7a14 C262 Chipset LPC/eSPI Controller - 7a20 700 Series Chipset P2SB - 7a21 700 Series Chipset Power Management Controller - 7a23 700 Series Chipset SMBus Controller - 7a24 Raptor Lake SPI (flash) Controller - 7a27 Raptor Lake PCH Shared SRAM - 7a28 700 Series Chipset Serial IO UART Controller #0 - 7a29 700 Series Chipset Serial IO UART Controller #1 - 7a2a 700 Series Chipset Serial IO GSPI Controller #0 - 7a2b 700 Series Chipset Serial IO GSPI Controller #1 - 7a30 Raptor Lake PCI Express Root Port #9 - 7a31 Raptor Lake PCI Express Root Port #10 - 7a32 Raptor Lake PCI Express Root Port #11 - 7a33 Raptor Lake PCI Express Root Port #12 - 7a34 Raptor Lake PCI Express Root Port #13 - 7a35 Raptor Lake PCI Express Root Port #14 - 7a36 Raptor Lake PCI Express Root Port #15 - 7a37 Raptor Lake PCI Express Root Port #16 - 7a38 Raptor Lake PCI Express Root Port #1 - 7a39 Raptor Lake PCI Express Root Port #2 - 7a3a Raptor Lake PCI Express Root Port #3 - 7a3b Raptor Lake PCI Express Root Port #4 - 7a3c Raptor Lake PCI Express Root Port #5 - 7a3d Raptor Lake PCI Express Root Port #6 - 7a3e Raptor Lake PCI Express Root Port #7 - 7a3f Raptor Lake PCI Express Root Port #8 - 7a40 Raptor Lake PCI Express Root Port #17 - 7a41 Raptor Lake PCI Express Root Port #18 - 7a42 Raptor Lake PCI Express Root Port #19 - 7a43 Raptor Lake PCI Express Root Port #20 - 7a44 Raptor Lake PCI Express Root Port #21 - 7a45 Raptor Lake PCI Express Root Port #22 - 7a46 Raptor Lake PCI Express Root Port #23 - 7a47 Raptor Lake PCI Express Root Port #24 - 7a48 Raptor Lake PCI Express Root Port #25 - 7a49 Raptor Lake PCI Express Root Port #26 - 7a4a Raptor Lake PCI Express Root Port #27 - 7a4b Raptor Lake PCI Express Root Port #28 - 7a4c Raptor Lake Serial IO I2C Host Controller #0 - 7a4d Raptor Lake Serial IO I2C Host Controller #1 - 7a4e Raptor Lake Serial IO I2C Host Controller #2 - 7a4f Raptor Lake Serial IO I2C Host Controller #3 - 7a50 Raptor Lake High Definition Audio Controller - 7a5c 700 Series Chipset Serial IO UART Controller #3 - 7a60 Raptor Lake USB 3.2 Gen 2x2 (20 Gb/s) XHCI Host Controller - 7a61 Raptor Lake USB 3.2 Gen 1x1 (5 Gb/s) xDCI Device Controller - 7a62 Raptor Lake SATA AHCI Controller - 7a68 Raptor Lake CSME HECI #1 - 7a69 Raptor Lake CSME HECI #2 - 7a6a Raptor Lake CSME IDE Redirection - 7a6b Raptor Lake CSME Keyboard and Text (KT) Redirection - 7a6c Raptor Lake CSME HECI #3 - 7a6d Raptor Lake CSME HECI #4 + 7a13 C266 Chipset eSPI Controller + 7a14 C262 Chipset eSPI Controller + 7a20 700 Series Chipset Family P2SB + 7a21 700 Series Chipset Family PMC + 7a23 700 Series Chipset Family SMBus + 7a24 700 Series Chipset Family SPI (flash) Controller + 7a26 700 Series Chipset Family Trace Hub + 7a27 700 Series Chipset Family Shared SRAM + 7a28 700 Series Chipset Family UART #0 + 7a29 700 Series Chipset Family UART #1 + 7a2a 700 Series Chipset Family GSPI #0 + 7a2b 700 Series Chipset Family GSPI #1 + 7a30 700 Series Chipset Family PCIe Root Port #9 + 7a31 700 Series Chipset Family PCIe Root Port #10 + 7a32 700 Series Chipset Family PCIe Root Port #11 + 7a33 700 Series Chipset Family PCIe Root Port #12 + 7a34 700 Series Chipset Family PCIe Root Port #13 + 7a35 700 Series Chipset Family PCIe Root Port #14 + 7a36 700 Series Chipset Family PCIe Root Port #15 + 7a37 700 Series Chipset Family PCIe Root Port #16 + 7a38 700 Series Chipset Family PCIe Root Port #1 + 7a39 700 Series Chipset Family PCIe Root Port #2 + 7a3a 700 Series Chipset Family PCIe Root Port #3 + 7a3b 700 Series Chipset Family PCIe Root Port #4 + 7a3c 700 Series Chipset Family PCIe Root Port #5 + 7a3d 700 Series Chipset Family PCIe Root Port #6 + 7a3e 700 Series Chipset Family PCIe Root Port #7 + 7a3f 700 Series Chipset Family PCIe Root Port #8 + 7a40 700 Series Chipset Family PCIe Root Port #17 + 7a41 700 Series Chipset Family PCIe Root Port #18 + 7a42 700 Series Chipset Family PCIe Root Port #19 + 7a43 700 Series Chipset Family PCIe Root Port #20 + 7a44 700 Series Chipset Family PCIe Root Port #21 + 7a45 700 Series Chipset Family PCIe Root Port #22 + 7a46 700 Series Chipset Family PCIe Root Port #23 + 7a47 700 Series Chipset Family PCIe Root Port #24 + 7a48 700 Series Chipset Family PCIe Root Port #25 + 7a49 700 Series Chipset Family PCIe Root Port #26 + 7a4a 700 Series Chipset Family PCIe Root Port #27 + 7a4b 700 Series Chipset Family PCIe Root Port #28 + 7a4c 700 Series Chipset Family I2C Controller #0 + 7a4d 700 Series Chipset Family I2C Controller #1 + 7a4e 700 Series Chipset Family I2C Controller #2 + 7a4f 700 Series Chipset Family I2C Controller #3 + 7a50 700 Series Chipset Family HD Audio Controller + 7a5c 700 Series Chipset Family UART #3 + 7a60 700 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + 7a61 700 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 7a62 700 Series Chipset Family SATA Controller (AHCI) + 7a68 700 Series Chipset Family CSME HECI #1 + 7a69 700 Series Chipset Family CSME HECI #2 + 7a6a 700 Series Chipset Family CSME IDE Redirection (IDE-R) + 7a6b 700 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 7a6c 700 Series Chipset Family CSME HECI #3 + 7a6d 700 Series Chipset Family CSME HECI #4 7a70 700 Series Chipset CNVi WiFi 8086 0074 Wi-Fi 6 AX201 160MHz 8086 0090 WiFi 6E AX211 160MHz - 7a79 700 Series Chipset Serial IO GSPI Controller #3 - 7a7b 700 Series Chipset Serial IO GSPI Controller #2 - 7a7c Raptor Lake Serial IO I2C Host Controller #4 - 7a7d Raptor Lake Serial IO I2C Host Controller #5 - 7a7e 700 Series Chipset Serial IO UART Controller #2 - 7a83 Q670 Chipset LPC/eSPI Controller -# Unlike other PCH components. The eSPI controller is specific to each chipset model - 7a84 Z690 Chipset LPC/eSPI Controller - 7a85 H670 Chipset LPC/eSPI Controller - 7a86 B660 Chipset LPC/eSPI Controller - 7a87 H610 Chipset LPC/eSPI Controller - 7a88 W680 Chipset LPC/eSPI Controller - 7a8c HM670 Chipset LPC/eSPI Controller - 7a8d WM690 Chipset LPC/eSPI Controller - 7aa3 Alder Lake-S PCH SMBus Controller - 7aa4 Alder Lake-S PCH SPI Controller - 7aa7 Alder Lake-S PCH Shared SRAM - 7aa8 Alder Lake-S PCH Serial IO UART #0 - 7aab Alder Lake-S PCH Serial IO SPI Controller #1 - 7ab0 Alder Lake-S PCH PCI Express Root Port #9 - 7ab4 Alder Lake-S PCH PCI Express Root Port #13 - 7ab8 Alder Lake-S PCH PCI Express Root Port #1 - 7ab9 Alder Lake-S PCH PCI Express Root Port #2 - 7aba Alder Lake-S PCH PCI Express Root Port #3 - 7abc Alder Lake-S PCH PCI Express Root Port #5 - 7abd Alder Lake-S PCH PCI Express Root Port #6 - 7abf Alder Lake-S PCH PCI Express Root Port #8 - 7ac4 Alder Lake-S PCH PCI Express Root Port #21 - 7ac8 Alder Lake-S PCH PCI Express Root Port #25 - 7acc Alder Lake-S PCH Serial IO I2C Controller #0 - 7acd Alder Lake-S PCH Serial IO I2C Controller #1 - 7ace Alder Lake-S PCH Serial IO I2C Controller #2 - 7acf Alder Lake-S PCH Serial IO I2C Controller #3 - 7ad0 Alder Lake-S HD Audio Controller - 7ae0 Alder Lake-S PCH USB 3.2 Gen 2x2 XHCI Controller - 7ae1 Alder Lake-S PCH USB 3.2 Gen 1x1 xDCI Controller - 7ae2 Alder Lake-S PCH SATA Controller [AHCI Mode] - 7ae8 Alder Lake-S PCH HECI Controller #1 - 7aeb Alder Lake-S Keyboard and Text (KT) Redirection + 7a78 700 Series Chipset Family Integrated Sensor Hub + 7a79 700 Series Chipset Family GSPI #3 + 7a7b 700 Series Chipset Family GSPI #2 + 7a7c 700 Series Chipset Family I2C Controller #4 + 7a7d 700 Series Chipset Family I2C Controller #5 + 7a7e 700 Series Chipset Family UART #2 + 7a83 Q670 Chipset eSPI Controller + 7a84 Z690 Chipset eSPI Controller + 7a85 H670 Chipset eSPI Controller + 7a86 B660 Chipset eSPI Controller + 7a87 H610 Chipset eSPI Controller + 7a88 W680 Chipset eSPI Controller + 7a8a W790 Chipset eSPI Controller + 7a8c HM670 Chipset eSPI Controller + 7a8d WM690 Chipset eSPI Controller + 7aa0 600 Series Chipset Family P2SB + 7aa1 600 Series Chipset Family PMC + 7aa3 600 Series Chipset Family SMBus + 7aa4 600 Series Chipset Family SPI (flash) Controller + 7aa6 600 Series Chipset Family Trace Hub + 7aa7 600 Series Chipset Family Shared SRAM + 7aa8 600 Series Chipset Family UART #0 + 7aa9 600 Series Chipset Family UART #1 + 7aaa 600 Series Chipset Family GSPI #0 + 7aab 600 Series Chipset Family GSPI #1 + 7ab0 600 Series Chipset Family PCIe Root Port #9 + 7ab1 600 Series Chipset Family PCIe Root Port #10 + 7ab2 600 Series Chipset Family PCIe Root Port #11 + 7ab3 600 Series Chipset Family PCIe Root Port #12 + 7ab4 600 Series Chipset Family PCIe Root Port #13 + 7ab5 600 Series Chipset Family PCIe Root Port #14 + 7ab6 600 Series Chipset Family PCIe Root Port #15 + 7ab7 600 Series Chipset Family PCIe Root Port #16 + 7ab8 600 Series Chipset Family PCIe Root Port #1 + 7ab9 600 Series Chipset Family PCIe Root Port #2 + 7aba 600 Series Chipset Family PCIe Root Port #3 + 7abb 600 Series Chipset Family PCIe Root Port #4 + 7abc 600 Series Chipset Family PCIe Root Port #5 + 7abd 600 Series Chipset Family PCIe Root Port #6 + 7abe 600 Series Chipset Family PCIe Root Port #7 + 7abf 600 Series Chipset Family PCIe Root Port #8 + 7ac0 600 Series Chipset Family PCIe Root Port #17 + 7ac1 600 Series Chipset Family PCIe Root Port #18 + 7ac2 600 Series Chipset Family PCIe Root Port #19 + 7ac3 600 Series Chipset Family PCIe Root Port #20 + 7ac4 600 Series Chipset Family PCIe Root Port #21 + 7ac5 600 Series Chipset Family PCIe Root Port #22 + 7ac6 600 Series Chipset Family PCIe Root Port #23 + 7ac7 600 Series Chipset Family PCIe Root Port #24 + 7ac8 600 Series Chipset Family PCIe Root Port #25 + 7ac9 600 Series Chipset Family PCIe Root Port #26 + 7aca 600 Series Chipset Family PCIe Root Port #27 + 7acb 600 Series Chipset Family PCIe Root Port #28 + 7acc 600 Series Chipset Family I2C Controller #0 + 7acd 600 Series Chipset Family I2C Controller #1 + 7ace 600 Series Chipset Family I2C Controller #2 + 7acf 600 Series Chipset Family 12C Controller #3 + 7ad0 600 Series Chipset Family HD Audio + 7adc 600 Series Chipset Family UART #3 + 7ae0 600 Series Chipset Family USB 3.2 Gen 2x2 (20Gbs) XHCI Host Controller + 7ae1 600 Series Chipset Family USB 3.2 Gen 1x1 (5Gbs) Device Controller (xDCI) + 7ae2 600 Series Chipset Family SATA Controller (AHCI) + 7ae8 600 Series Chipset Family CSME HECI #1 + 7ae9 600 Series Chipset Family CSME HECI #2 + 7aea 600 Series Chipset Family CSME IDE Redirection (IDE-R) + 7aeb 600 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 7aec 600 Series Chipset Family CSME HECI #3 + 7aed 600 Series Chipset Family CSME HECI #4 7af0 Alder Lake-S PCH CNVi WiFi 8086 0034 Wireless-AC 9560 8086 0070 Wi-Fi 6 AX201 160MHz 8086 0094 Wi-Fi 6 AX201 160MHz - 7af8 Alder Lake-S Integrated Sensor Hub - 7afc Alder Lake-S PCH Serial IO I2C Controller #4 - 7afd Alder Lake-S PCH Serial IO I2C Controller #5 + 7af8 600 Series Chipset Family Integrated Sensor Hub + 7af9 600 Series Chipset Family GSPI #3 + 7afb 600 Series Chipset Family GSPI #2 + 7afc 600 Series Chipset Family I2C Controller #4 + 7afd 600 Series Chipset Family I2C Controller #5 + 7afe 600 Series Chipset Family UART #2 7d01 Meteor Lake-H 6p+8e cores Host Bridge/DRAM Controller 7d03 Meteor Lake-P Dynamic Tuning Technology 7d06 Arrow Lake-H 6p+8e cores Host Bridge/DRAM Controller @@ -38727,34 +39228,79 @@ 7eca Meteor Lake-H/U PCIe Root Port #10 7ecb Arrow Lake-H/U PCIe Root Port #11 (PXPE) 7ecc Meteor Lake-H PCIe Root Port #12 - 7f03 Q870 Chipset LPC/eSPI Controller - 7f04 Z890 Chipset LPC/eSPI Controller - 7f06 B860 Chipset LPC/eSPI Controller - 7f07 H810 Chipset LPC/eSPI Controller - 7f08 W880 Chipset LPC/eSPI Controller - 7f0c HM870 Chipset LPC/eSPI Controller - 7f0d WM880 Chipset LPC/eSPI Controller - 7f20 800 Series PCH P2SB - 7f21 800 Series PCH Power Management Controller - 7f23 800 Series PCH SMBus Controller - 7f24 800 Series PCH SPI (flash) Controller - 7f27 800 Series PCH Shared SRAM - 7f30 800 Series PCH PCIe Root Port #9 - 7f34 800 Series PCH PCIe Root Port #13 - 7f36 800 Series PCH PCIe Root Port #15 - 7f38 800 Series PCH PCIe Root Port #1 - 7f3e 800 Series PCH PCIe Root Port #7 - 7f40 800 Series PCH PCIe Root Port #17 - 7f44 800 Series PCH PCIe Root Port #21 - 7f4c 800 Series PCH I2C Controller #0 - 7f4f 800 Series PCH I2C Controller #3 - 7f50 800 Series ACE (Audio Context Engine) - 7f68 800 Series PCH HECI #1 - 7f6e 800 Series PCH USB 3.1 xHCI HC + 7f03 Q870 Chipset eSPI Controller + 7f04 Z890 Chipset eSPI Controller + 7f06 B860 Chipset eSPI Controller + 7f07 H810 Chipset eSPI Controller + 7f08 W880 Chipset eSPI Controller + 7f0c HM870 Chipset eSPI Controller + 7f0d WM880 Chipset eSPI Controller + 7f20 800 Series Chipset Family P2SB + 7f21 800 Series Chipset Family PMC + 7f23 800 Series Chipset Family SMBus + 7f24 800 Series Chipset Family SPI (flash) Controller + 7f26 800 Series Chipset Family Trace Hub + 7f27 800 Series Chipset Family Shared SRAM + 7f28 800 Series Chipset Family UART #0 + 7f29 800 Series Chipset Family UART #1 + 7f2a 800 Series Chipset Family GSPI #0 + 7f2b 800 Series Chipset Family GSPI #1 + 7f30 800 Series Chipset Family PCIe Root Port #9 + 7f31 800 Series Chipset Family PCIe Root Port #10 + 7f32 800 Series Chipset Family PCIe Root Port #11 + 7f33 800 Series Chipset Family PCIe Root Port #12 + 7f34 800 Series Chipset Family PCIe Root Port #13 + 7f35 800 Series Chipset Family PCIe Root Port #14 + 7f36 800 Series Chipset Family PCIe Root Port #15 + 7f37 800 Series Chipset Family PCIe Root Port #16 + 7f38 800 Series Chipset Family PCIe Root Port #1 + 7f39 800 Series Chipset Family PCIe Root Port #2 + 7f3a 800 Series Chipset Family PCIe Root Port #3 + 7f3b 800 Series Chipset Family PCIe Root Port #4 + 7f3c 800 Series Chipset Family PCIe Root Port #5 + 7f3d 800 Series Chipset Family PCIe Root Port #6 + 7f3e 800 Series Chipset Family PCIe Root Port #7 + 7f3f 800 Series Chipset Family PCIe Root Port #8 + 7f40 800 Series Chipset Family PCIe Root Port #17 + 7f41 800 Series Chipset Family PCIe Root Port #18 + 7f42 800 Series Chipset Family PCIe Root Port #19 + 7f43 800 Series Chipset Family PCIe Root Port #20 + 7f44 800 Series Chipset Family PCIe Root Port #21 + 7f45 800 Series Chipset Family PCIe Root Port #22 + 7f46 800 Series Chipset Family PCIe Root Port #23 + 7f47 800 Series Chipset Family PCIe Root Port #24 + 7f4c 800 Series Chipset Family I2C #0 + 7f4d 800 Series Chipset Family I2C #1 + 7f4e 800 Series Chipset Family I2C #2 + 7f4f 800 Series Chipset Family I2C #3 + 7f50 800 Series Chipset Family Audio Context Engine (ACE) + 7f58 800 Series Chipset Family Touch Host Controller (THC) #0 ID1 + 7f59 800 Series Chipset Family Touch Host Controller (THC) #0 ID2 + 7f5a 800 Series Chipset Family Touch Host Controller (THC) #1 ID1 + 7f5b 800 Series Chipset Family Touch Host Controller (THC) #1 ID2 + 7f5c 800 Series Chipset Family UART #2 + 7f5d 800 Series Chipset Family UART #3 + 7f5e 800 Series Chipset Family GSPI #2 + 7f5f 800 Series Chipset Family GSPI #3 + 7f62 800 Series Chipset Family SATA Controller (AHCI) + 7f66 800 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium + 7f68 800 Series Chipset Family CSME HECI #1 + 7f69 800 Series Chipset Family CSME HECI #2 + 7f6a 800 Series Chipset Family IDE Redirection (IDER-R) + 7f6b 800 Series Chipset Family Keyboard and Text (KT) Redirection + 7f6c 800 Series Chipset Family CSME HECI #3 + 7f6d 800 Series Chipset Family CSME HECI #4 + 7f6e 800 Series Chipset Family USB 3.1 xHCI HC + 7f6f 800 Series Chipset Family USB Device Controller (OTG) (xDCI) 7f70 Arrow Lake-S PCH CNVi WiFi 8086 0094 WiFi 6E AX211 160MHz - 7f7a 800 Series PCH I2C Controller #4 - 7f7b 800 Series PCH I2C Controller #5 + 7f78 800 Series Chipset Family Integrated Sensor Hub + 7f79 800 Series Chipset Family I3C + 7f7a 800 Series Chipset Family I2C #4 + 7f7b 800 Series Chipset Family I2C #5 + 7f7c 800 Series Chipset Family Silicon Security Engine HECI #1 + 7f7d 800 Series Chipset Family Silicon Security Engine HECI #2 + 7f7e 800 Series Chipset Family Silicon Security Engine HECI #3 8002 Trusted Execution Technology Registers 8003 Trusted Execution Technology Registers 8100 US15W/US15X SCH [Poulsbo] Host Bridge @@ -39492,38 +40038,56 @@ 1043 83ac Eee PC 1015PX 144d c072 Notebook N150P a013 Atom Processor D4xx/D5xx/N4xx/N5xx CHAPS counter - a082 Tiger Lake-LP LPC Controller - a0a3 Tiger Lake-LP SMBus Controller - a0a4 Tiger Lake-LP SPI Controller - a0a6 Tiger Lake-LP Trace Hub - a0a8 Tiger Lake-LP Serial IO UART Controller #0 - a0a9 Tiger Lake-LP Serial IO UART Controller #1 - a0ab Tiger Lake-LP Serial IO SPI Controller #1 - a0b0 Tiger Lake-LP PCI Express Root Port #9 - a0b1 Tiger Lake-LP PCI Express Root Port #10 - a0b3 Tiger Lake-LP PCI Express Root Port #12 - a0b8 Tiger Lake-LP PCI Express Root Port #0 - a0bb Tiger Lake-LP PCI Express Root Port #3 - a0bc Tiger Lake-LP PCI Express Root Port #5 - a0bd Tigerlake PCH-LP PCI Express Root Port #6 - a0be Tiger Lake-LP PCI Express Root Port #7 - a0bf Tiger Lake-LP PCI Express Root Port #8 - a0c5 Tiger Lake-LP Serial IO I2C Controller #4 - a0c6 Tiger Lake-LP Serial IO I2C Controller #5 - a0c8 Tiger Lake-LP Smart Sound Technology Audio Controller -# SATA controller on Intel Tiger Lake based mobile platforms in AHCI mode. Could be found on Panasonic Let's Note CF-SV2. - a0d3 Tiger Lake-LP SATA Controller - a0e0 Tiger Lake-LP Management Engine Interface - a0e3 Tiger Lake-LP Active Management Technology - SOL - a0e8 Tiger Lake-LP Serial IO I2C Controller #0 - a0e9 Tiger Lake-LP Serial IO I2C Controller #1 - a0ea Tiger Lake-LP Serial IO I2C Controller #2 - a0eb Tiger Lake-LP Serial IO I2C Controller #3 - a0ed Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller - a0ef Tiger Lake-LP Shared SRAM + a082 500 Series Chipset Family On-Package eSPI Controller + a0a0 500 Series Chipset Family On-Package Primary-to-Sideband Bridge (P2SB) + a0a1 500 Series Chipset Family On-Package Power Management Controller (PMC) + a0a3 500 Series Chipset Family On-Package System Management Bus (SMBus) + a0a4 500 Series Chipset Family On-Package SPI (flash) Controller + a0a6 500 Series Chipset Family On-Package Trace Hub + a0a8 500 Series Chipset Family On-Package UART Controller #0 + a0a9 500 Series Chipset Family On-Package UART Controller #1 + a0aa 500 Series Chipset Family On-Package Generic SPI (GSPI) #0 + a0ab 500 Series Chipset Family On-Package Generic SPI (GSPI) #1 + a0b0 500 Series Chipset Family On-Package PCI Express Root Port #9 + a0b1 500 Series Chipset Family On-Package PCI Express Root Port #10 + a0b2 500 Series Chipset Family On-Package PCI Express Root Port #11 + a0b3 500 Series Chipset Family On-Package PCI Express Root Port #12 + a0b8 500 Series Chipset Family On-Package PCI Express Root Port #1 + a0b9 500 Series Chipset Family On-Package PCI Express Root Port #2 + a0ba 500 Series Chipset Family On-Package PCI Express Root Port #3 + a0bb 500 Series Chipset Family On-Package PCI Express Root Port #4 + a0bc 500 Series Chipset Family On-Package PCI Express Root Port #5 + a0bd 500 Series Chipset Family On-Package PCI Express Root Port #6 + a0be 500 Series Chipset Family On-Package PCI Express Root Port #7 + a0bf 500 Series Chipset Family On-Package PCI Express Root Port #8 + a0c5 500 Series Chipset Family On-Package I2C Controller #4 + a0c6 500 Series Chipset Family On-Package I2C Controller #5 + a0c7 500 Series Chipset Family On-Package UART Controller #2 + a0c8 500 Series Chipset Family On-Package High Definition Audio (HD Audio) + a0d0 500 Series Chipset Family On-Package Touch Host Controller #0 + a0d1 500 Series Chipset Family On-Package Touch Host Controller #1 + a0d3 500 Series Chipset Family On-Package SATA Controller (AHCI) + a0d5 500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + a0d7 500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + a0da 500 Series Chipset Family On-Package UART Controller #3 + a0e0 500 Series Chipset Family On-Package CSME HECI #1 + a0e1 500 Series Chipset Family On-Package CSME HECI #2 + a0e2 500 Series Chipset Family On-Package CSME IDE Redirection (IDE-R) + a0e3 500 Series Chipset Family On-Package CSME Keyboard and Text (KT) Redirection + a0e4 500 Series Chipset Family On-Package CSME HECI #3 + a0e5 500 Series Chipset Family On-Package CSME HECI #4 + a0e8 500 Series Chipset Family On-Package I2C Controller #0 + a0e9 500 Series Chipset Family On-Package I2C Controller #1 + a0ea 500 Series Chipset Family On-Package I2C Controller #2 + a0eb 500 Series Chipset Family On-Package I2C Controller #3 + a0ed 500 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + a0ee 500 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + a0ef 500 Series Chipset Family On-Package Shared SRAM a0f0 Wi-Fi 6 AX201 8086 0244 Wi-Fi 6 AX101NGW - a0fc Tiger Lake-LP Integrated Sensor Hub + a0fb 500 Series Chipset Family On-Package Generic SPI (GSPI) #2 + a0fc 500 Series Chipset Family On-Package Integrated Sensor Hub + a0fd 500 Series Chipset Family On-Package Generic SPI (GSPI) #3 a102 Q170/Q150/B150/H170/H110/Z170/CM236 Chipset SATA Controller [AHCI Mode] 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING @@ -39534,65 +40098,65 @@ a106 Q170/H170/Z170/CM236 Chipset SATA Controller [RAID Mode] a107 HM170/QM170 Chipset SATA Controller [RAID Mode] a10f Sunrise Point-H SATA Controller [RAID mode] - a110 100 Series/C230 Series Chipset Family PCI Express Root Port #1 - a111 100 Series/C230 Series Chipset Family PCI Express Root Port #2 - a112 100 Series/C230 Series Chipset Family PCI Express Root Port #3 - a113 100 Series/C230 Series Chipset Family PCI Express Root Port #4 - a114 100 Series/C230 Series Chipset Family PCI Express Root Port #5 + a110 100/C230 Series Chipset Family PCIe Root Port #1 + a111 100/C230 Series Chipset Family PCIe Root Port #2 + a112 100/C230 Series Chipset Family PCIe Root Port #3 + a113 100/C230 Series Chipset Family PCIe Root Port #4 + a114 100/C230 Series Chipset Family PCIe Root Port #5 1043 8694 H110I-PLUS Motherboard - a115 100 Series/C230 Series Chipset Family PCI Express Root Port #6 - a116 100 Series/C230 Series Chipset Family PCI Express Root Port #7 - a117 100 Series/C230 Series Chipset Family PCI Express Root Port #8 - a118 100 Series/C230 Series Chipset Family PCI Express Root Port #9 + a115 100/C230 Series Chipset Family PCIe Root Port #6 + a116 100/C230 Series Chipset Family PCIe Root Port #7 + a117 100/C230 Series Chipset Family PCIe Root Port #8 + a118 100/C230 Series Chipset Family PCIe Root Port #9 1043 8694 H110I-PLUS Motherboard - a119 100 Series/C230 Series Chipset Family PCI Express Root Port #10 + a119 100/C230 Series Chipset Family PCIe Root Port #10 1043 8694 H110I-PLUS Motherboard - a11a 100 Series/C230 Series Chipset Family PCI Express Root Port #11 - a11b 100 Series/C230 Series Chipset Family PCI Express Root Port #12 - a11c 100 Series/C230 Series Chipset Family PCI Express Root Port #13 - a11d 100 Series/C230 Series Chipset Family PCI Express Root Port #14 - a11e 100 Series/C230 Series Chipset Family PCI Express Root Port #15 - a11f 100 Series/C230 Series Chipset Family PCI Express Root Port #16 - a120 100 Series/C230 Series Chipset Family P2SB - a121 100 Series/C230 Series Chipset Family Power Management Controller + a11a 100/C230 Series Chipset Family PCIe Root Port #11 + a11b 100/C230 Series Chipset Family PCIe Root Port #12 + a11c 100/C230 Series Chipset Family PCIe Root Port #13 + a11d 100/C230 Series Chipset Family PCIe Root Port #14 + a11e 100/C230 Series Chipset Family PCIe Root Port #15 + a11f 100/C230 Series Chipset Family PCIe Root Port #16 + a120 100/C230 Series Chipset Family P2SB + a121 100/C230 Series Chipset Family PMC 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING a122 Sunrise Point-H cAVS - a123 100 Series/C230 Series Chipset Family SMBus + a123 100/C230 Series Chipset Family SMBus 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a124 100 Series/C230 Series Chipset Family SPI Controller - a125 100 Series/C230 Series Chipset Family Gigabit Ethernet Controller - a126 100 Series/C230 Series Chipset Family Trace Hub - a127 100 Series/C230 Series Chipset Family Serial IO UART #0 - a128 100 Series/C230 Series Chipset Family Serial IO UART #1 - a129 100 Series/C230 Series Chipset Family Serial IO GSPI #0 - a12a 100 Series/C230 Series Chipset Family Serial IO GSPI #1 - a12f 100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller + a124 100/C230 Series Chipset Family SPI Controller + a125 100/C230 Series Chipset Family GbE Controller + a126 100/C230 Series Chipset Family Trace Hub + a127 100/C230 Series Chipset Family UART #0 + a128 100/C230 Series Chipset Family UART #1 + a129 100/C230 Series Chipset Family GSPI #0 + a12a 100/C230 Series Chipset Family GSPI #1 + a12f 100/C230 Series Chipset Family USB 3.0 xHCI Controller 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a130 100 Series/C230 Series Chipset Family USB Device Controller (OTG) - a131 100 Series/C230 Series Chipset Family Thermal Subsystem + a130 100/C230 Series Chipset Family USB Device Controller (OTG) + a131 100/C230 Series Chipset Family Thermal Subsystem 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1462 7994 H110M ECO/GAMING a133 Sunrise Point-H Northpeak ACPI Function - a135 100 Series/C230 Series Chipset Family Integrated Sensor Hub - a13a 100 Series/C230 Series Chipset Family MEI Controller #1 + a135 100/C230 Series Chipset Family ISH + a13a 100/C230 Series Chipset Family MEI #1 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a13b 100 Series/C230 Series Chipset Family MEI Controller #2 - a13c 100 Series/C230 Series Chipset Family IDE Redirection - a13d 100 Series/C230 Series Chipset Family KT Redirection - a13e 100 Series/C230 Series Chipset Family MEI Controller #3 + a13b 100/C230 Series Chipset Family MEI #2 + a13c 100/C230 Series Chipset Family IDE Redirection + a13d 100/C230 Series Chipset Family Keyboard and Text (KT) Redirection + a13e 100/C230 Series Chipset Family MEI #3 a140 Sunrise Point-H LPC Controller a141 Sunrise Point-H LPC Controller a142 Sunrise Point-H LPC Controller @@ -39629,19 +40193,19 @@ a15d Sunrise Point-H LPC Controller a15e Sunrise Point-H LPC Controller a15f Sunrise Point-H LPC Controller - a160 100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 + a160 100/C230 Series Chipset Family I2C #0 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv - a161 100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 + a161 100/C230 Series Chipset Family I2C #1 1028 06e4 XPS 15 9550 - a162 100 Series/C230 Series Chipset Family Serial IO I2C Controller #2 - a163 100 Series/C230 Series Chipset Family Serial IO I2C Controller #3 - a166 100 Series/C230 Series Chipset Family Serial IO UART Controller #2 - a167 100 Series/C230 Series Chipset Family PCI Express Root Port #17 - a168 100 Series/C230 Series Chipset Family PCI Express Root Port #18 - a169 100 Series/C230 Series Chipset Family PCI Express Root Port #19 - a16a 100 Series/C230 Series Chipset Family PCI Express Root Port #20 - a170 100 Series/C230 Series Chipset Family HD Audio Controller + a162 100/C230 Series Chipset Family I2C #2 + a163 100/C230 Series Chipset Family I2C #3 + a166 100/C230 Series Chipset Family UART #2 + a167 100/C230 Series Chipset Family PCIe Root Port #17 + a168 100/C230 Series Chipset Family PCIe Root Port #18 + a169 100/C230 Series Chipset Family PCIe Root Port #19 + a16a 100/C230 Series Chipset Family PCIe Root Port #20 + a170 100/C230 Series Chipset Family HD Audio 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 86c7 H110I-PLUS Motherboard @@ -39737,72 +40301,76 @@ a268 Lewisburg PCI Express Root Port a269 Lewisburg PCI Express Root Port a26a Lewisburg PCI Express Root Port - a282 200 Series PCH SATA controller [AHCI mode] + a282 200 Series/Z370 Chipset Family SATA Controller (AHCI) 1462 7a72 H270 PC MATE - a286 200 Series PCH SATA controller [RAID mode] - a290 200 Series PCH PCI Express Root Port #1 - a291 200 Series PCH PCI Express Root Port #2 - a292 200 Series PCH PCI Express Root Port #3 - a293 200 Series PCH PCI Express Root Port #4 - a294 200 Series PCH PCI Express Root Port #5 + a286 200 Series/Z370 Chipset Family SATA Controller (RAID) Premium + a28e 200 Series/Z370 Chipset Family SATA Controller (RST and Optane Technology) + a290 200 Series/Z370 Chipset Family PCIe Root Port #1 + a291 200 Series/Z370 Chipset Family PCIe Root Port #2 + a292 200 Series/Z370 Chipset Family PCIe Root Port #3 + a293 200 Series/Z370 Chipset Family PCIe Root Port #4 + a294 200 Series/Z370 Chipset Family PCIe Root Port #5 1462 7a72 H270 PC MATE - a295 200 Series PCH PCI Express Root Port #6 - a296 200 Series PCH PCI Express Root Port #7 + a295 200 Series/Z370 Chipset Family PCIe Root Port #6 + a296 200 Series/Z370 Chipset Family PCIe Root Port #7 1462 7a72 H270 PC MATE - a297 200 Series PCH PCI Express Root Port #8 - a298 200 Series PCH PCI Express Root Port #9 + a297 200 Series/Z370 Chipset Family PCIe Root Port #8 + a298 200 Series/Z370 Chipset Family PCIe Root Port #9 1462 7a72 H270 PC MATE - a299 200 Series PCH PCI Express Root Port #10 - a29a 200 Series PCH PCI Express Root Port #11 - a29b 200 Series PCH PCI Express Root Port #12 - a29c 200 Series PCH PCI Express Root Port #13 - a29d 200 Series PCH PCI Express Root Port #14 - a29e 200 Series PCH PCI Express Root Port #15 - a29f 200 Series PCH PCI Express Root Port #16 + a299 200 Series/Z370 Chipset Family PCIe Root Port #10 + a29a 200 Series/Z370 Chipset Family PCIe Root Port #11 + a29b 200 Series/Z370 Chipset Family PCIe Root Port #12 + a29c 200 Series/Z370 Chipset Family PCIe Root Port #13 + a29d 200 Series/Z370 Chipset Family PCIe Root Port #14 + a29e 200 Series/Z370 Chipset Family PCIe Root Port #15 + a29f 200 Series/Z370 Chipset Family PCIe Root Port #16 a2a0 200 Series/Z370 Chipset Family P2SB - a2a1 200 Series/Z370 Chipset Family Power Management Controller + a2a1 200 Series/Z370 Chipset Family PMC 1462 7a72 H270 PC MATE - a2a3 200 Series/Z370 Chipset Family SMBus Controller + a2a3 200 Series/Z370 Chipset Family SMBus 1462 7a72 H270 PC MATE a2a4 200 Series/Z370 Chipset Family SPI Controller - a2a5 200 Series/Z370 Chipset Family Gigabit Ethernet Controller + a2a5 200 Series/Z370 Chipset Family GbE Controller a2a6 200 Series/Z370 Chipset Family Trace Hub - a2a7 200 Series/Z370 Chipset Family Serial IO UART Controller #0 - a2a8 200 Series/Z370 Chipset Family Serial IO UART Controller #1 - a2a9 200 Series/Z370 Chipset Family Serial IO SPI Controller #0 - a2aa 200 Series/Z370 Chipset Family Serial IO SPI Controller #1 + a2a7 200 Series/Z370 Chipset Family UART #0 + a2a8 200 Series/Z370 Chipset Family UART #1 + a2a9 200 Series/Z370 Chipset Family GSPI #0 + a2aa 200 Series/Z370 Chipset Family GSPI #1 a2af 200 Series/Z370 Chipset Family USB 3.0 xHCI Controller 1462 7a72 H270 PC MATE - a2b1 200 Series PCH Thermal Subsystem + a2b0 200 Series/Z370 Chipset Family USB Device Controller (OTG) + a2b1 200 Series/Z370 Chipset Family Thermal Subsystem 1462 7a72 H270 PC MATE - a2ba 200 Series PCH CSME HECI #1 + a2b5 200 Series/Z370 Chipset Family ISH + a2ba 200 Series/Z370 Chipset Family MEI #1 1462 7a72 H270 PC MATE - a2bb 200 Series PCH CSME HECI #2 -# AMT serial over LAN - a2bd 200 Series Chipset Family KT Redirection - a2c4 200 Series PCH LPC Controller (H270) + a2bb 200 Series/Z370 Chipset Family MEI #2 + a2bc 200 Series/Z370 Chipset Family IDE Redirection + a2bd 200 Series/Z370 Chipset Family Keyboard and Text (KT) Redirection + a2be 200 Series/Z370 Chipset Family MEI #3 + a2c4 H270 Chipset LPC/eSPI Controller 1462 7a72 H270 PC MATE - a2c5 200 Series PCH LPC Controller (Z270) - a2c6 200 Series PCH LPC Controller (Q270) - a2c7 200 Series PCH LPC Controller (Q250) - a2c8 200 Series PCH LPC Controller (B250) + a2c5 Z270 Chipset LPC/eSPI Controller + a2c6 Q270 Chipset LPC/eSPI Controller + a2c7 Q250 Chipset LPC/eSPI Controller + a2c8 B250 Chipset LPC/eSPI Controller a2c9 Z370 Chipset LPC/eSPI Controller a2d2 X299 Chipset LPC/eSPI Controller a2d3 C422 Chipset LPC/eSPI Controller - a2e0 200 Series PCH Serial IO I2C Controller #0 - a2e1 200 Series PCH Serial IO I2C Controller #1 - a2e2 200 Series PCH Serial IO I2C Controller #2 - a2e3 200 Series PCH Serial IO I2C Controller #3 - a2e6 200 Series PCH Serial IO UART Controller #2 - a2e7 200 Series PCH PCI Express Root Port #17 - a2e8 200 Series PCH PCI Express Root Port #18 - a2e9 200 Series PCH PCI Express Root Port #19 - a2ea 200 Series PCH PCI Express Root Port #20 - a2eb 200 Series PCH PCI Express Root Port #21 - a2ec 200 Series PCH PCI Express Root Port #22 - a2ed 200 Series PCH PCI Express Root Port #23 - a2ee 200 Series PCH PCI Express Root Port #24 - a2f0 200 Series PCH HD Audio + a2e0 200 Series/Z370 Chipset Family I2C #0 + a2e1 200 Series/Z370 Chipset Family I2C #1 + a2e2 200 Series/Z370 Chipset Family I2C #2 + a2e3 200 Series/Z370 Chipset Family I2C #3 + a2e6 200 Series/Z370 Chipset Family UART #2 + a2e7 200 Series/Z370 Chipset Family PCIe Root Port #17 + a2e8 200 Series/Z370 Chipset Family PCIe Root Port #18 + a2e9 200 Series/Z370 Chipset Family PCIe Root Port #19 + a2ea 200 Series/Z370 Chipset Family PCIe Root Port #20 + a2eb 200 Series/Z370 Chipset Family PCIe Root Port #21 + a2ec 200 Series/Z370 Chipset Family PCIe Root Port #22 + a2ed 200 Series/Z370 Chipset Family PCIe Root Port #23 + a2ee 200 Series/Z370 Chipset Family PCIe Root Port #24 + a2f0 200 Series/Z370 Chipset Family HD Audio 1462 7a72 H270 PC MATE 1462 fa72 H270 PC MATE a303 H310 Chipset LPC/eSPI Controller @@ -39818,57 +40386,73 @@ a30d HM370 Chipset LPC/eSPI Controller a30e CM246 Chipset LPC/eSPI Controller a313 Cannon Lake LPC/eSPI Controller - a323 Cannon Lake PCH SMBus Controller + a320 300/C240 Series Chipset Family P2SB + a321 300/C240 Series Chipset Family PMC + a323 300/C240 Series Chipset Family SMBus 1028 0869 Vostro 3470 - a324 Cannon Lake PCH SPI Controller + a324 300/C240 Series Chipset Family SPI (Flash) Controller 1028 0869 Vostro 3470 - a328 Cannon Lake PCH Serial IO UART Host Controller - a32b Cannon Lake PCH SPI Host Controller - a32c Cannon Lake PCH PCI Express Root Port #21 - a32d Cannon Lake PCH PCI Express Root Port #22 - a32e Cannon Lake PCH PCI Express Root Port #23 - a32f Cannon Lake PCH PCI Express Root Port #24 - a330 Cannon Lake PCH PCI Express Root Port #9 - a331 Cannon Lake PCH PCI Express Root Port #10 - a332 Cannon Lake PCH PCI Express Root Port #11 - a333 Cannon Lake PCH PCI Express Root Port #12 - a334 Cannon Lake PCH PCI Express Root Port #13 - a335 Cannon Lake PCH PCI Express Root Port #14 - a336 Cannon Lake PCH PCI Express Root Port #15 - a337 Cannon Lake PCH PCI Express Root Port #16 - a338 Cannon Lake PCH PCI Express Root Port #1 - a339 Cannon Lake PCH PCI Express Root Port #2 - a33a Cannon Lake PCH PCI Express Root Port #3 - a33b Cannon Lake PCH PCI Express Root Port #4 - a33c Cannon Lake PCH PCI Express Root Port #5 - a33d Cannon Lake PCH PCI Express Root Port #6 - a33e Cannon Lake PCH PCI Express Root Port #7 - a33f Cannon Lake PCH PCI Express Root Port #8 - a340 Cannon Lake PCH PCI Express Root Port #17 - a341 Cannon Lake PCH PCI Express Root Port #18 - a342 Cannon Lake PCH PCI Express Root Port #19 - a343 Cannon Lake PCH PCI Express Root Port #20 + a326 300/C240 Series Chipset Family Trace Hub + a328 300/C240 Series Chipset Family UART #0 + a329 300/C240 Series Chipset Family UART #1 + a32a 300/C240 Series Chipset Family GSPI #0 + a32b 300/C240 Series Chipset Family GSPI #1 + a32c 300/C240 Series Chipset Family PCIe Root Port #21 + a32d 300/C240 Series Chipset Family PCIe Root Port #22 + a32e 300/C240 Series Chipset Family PCIe Root Port #23 + a32f 300/C240 Series Chipset Family PCIe Root Port #24 + a330 300/C240 Series Chipset Family PCIe Root Port #9 + a331 300/C240 Series Chipset Family PCIe Root Port #10 + a332 300/C240 Series Chipset Family PCIe Root Port #11 + a333 300/C240 Series Chipset Family PCIe Root Port #12 + a334 300/C240 Series Chipset Family PCIe Root Port #13 + a335 300/C240 Series Chipset Family PCIe Root Port #14 + a336 300/C240 Series Chipset Family PCIe Root Port #15 + a337 300/C240 Series Chipset Family PCIe Root Port #16 + a338 300/C240 Series Chipset Family PCIe Root Port #1 + a339 300/C240 Series Chipset Family PCIe Root Port #2 + a33a 300/C240 Series Chipset Family PCIe Root Port #3 + a33b 300/C240 Series Chipset Family PCIe Root Port #4 + a33c 300/C240 Series Chipset Family PCIe Root Port #5 + a33d 300/C240 Series Chipset Family PCIe Root Port #6 + a33e 300/C240 Series Chipset Family PCIe Root Port #7 + a33f 300/C240 Series Chipset Family PCIe Root Port #8 + a340 300/C240 Series Chipset Family PCIe Root Port #17 + a341 300/C240 Series Chipset Family PCIe Root Port #18 + a342 300/C240 Series Chipset Family PCIe Root Port #19 + a343 300/C240 Series Chipset Family PCIe Root Port #20 + a347 300/C240 Series Chipset Family UART #2 a348 Cannon Lake PCH cAVS 1028 0869 Vostro 3470 - a352 Cannon Lake PCH SATA AHCI Controller + a352 300/C240 Series Chipset Family SATA Controller (AHCI) 1028 0869 Vostro 3470 - a353 Cannon Lake Mobile PCH SATA AHCI Controller - a360 Cannon Lake PCH HECI Controller + a353 300/C240 Series Chipset Family SATA Controller (AHCI) + a355 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a356 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a357 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a35e 300/C240 Series Chipset Family SATA Controller Optane Memory + a360 300/C240 Series Chipset Family HECI #1 1028 0869 Vostro 3470 - a363 Cannon Lake PCH Active Management Technology - SOL - a364 Cannon Lake PCH HECI Controller #2 - a368 Cannon Lake PCH Serial IO I2C Controller #0 - a369 Cannon Lake PCH Serial IO I2C Controller #1 - a36a Cannon Lake PCH Serial IO I2C Controller #2 - a36b Cannon Lake PCH Serial IO I2C Controller #3 + a361 300/C240 Series Chipset Family HECI #2 + a362 300/C240 Series Chipset Family IDE Redirection (IDER-R) + a363 300/C240 Series Chipset Family Keyboard and Text (KT) Redirection + a364 300/C240 Series Chipset Family HECI #3 + a365 300/C240 Series Chipset Family HECI #4 + a368 300/C240 Series Chipset Family I2C Controller #0 + a369 300/C240 Series Chipset Family I2C Controller #1 + a36a 300/C240 Series Chipset Family I2C Controller #2 + a36b 300/C240 Series Chipset Family I2C Controller #3 a36d Cannon Lake PCH USB 3.1 xHCI Host Controller 1028 0869 Vostro 3470 - a36f Cannon Lake PCH Shared SRAM + a36e 300/C240 Series Chipset Family USB Device Controller (Dual Role) + a36f 300/C240 Series Chipset Family Shared SRAM a370 Cannon Lake PCH CNVi WiFi 1a56 1552 Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW) 8086 0034 Wireless-AC 9560 - a379 Cannon Lake PCH Thermal Controller + a379 300/C240 Series Chipset Family Thermal Subsystem 1028 0869 Vostro 3470 + a37b 300/C240 Series Chipset Family SPI #2 + a37c 300/C240 Series Chipset Family Integrated Sensor Hub a382 400 Series Chipset Family SATA AHCI Controller a394 Comet Lake PCI Express Root Port #05 a397 Comet Lake PCI Express Root Port #08 @@ -39946,7 +40530,7 @@ a83b Lunar Lake-M PCI Express Root Port #4 a83c Lunar Lake-M PCI Express Root Port #5 a83d Lunar Lake-M PCI Express Root Port #6 - a840 BE201 320MHz + a840 BE200 Series Wi-Fi 7 a845 Lunar Lake-M Integrated Sensor Hub a847 Lunar Lake-M UFS Controller a848 Lunar Lake-M Touch Host Controller #0 ID1 @@ -40077,8 +40661,8 @@ e216 Battlemage G21 [Intel Graphics] e220 Battlemage G31 [Intel Graphics] e221 Battlemage G31 [Intel Graphics] - e222 Battlemage G31 [Intel Graphics] - e223 Battlemage G31 [Intel Graphics] + e222 Battlemage G31 [Arc Pro B65] + e223 Battlemage G31 [Arc Pro B70] e302 Panther Lake LPC/eSPI Controller e322 Panther Lake SMBus Controller e323 Panther Lake SPI(flash) Controller @@ -40125,6 +40709,13 @@ e37c Panther Lake I3C Host Controller #1 e37d Panther Lake USB 3.2 xHCI Controller e37f Panther Lake Shared SRAM + e431 Panther Lake USB 3.2 xHCI Controller + e434 Panther Lake Thunderbolt 4 NHI #2 + e440 Panther Lake PCH CNVi WiFi + 8086 0114 Wi-Fi 7 BE211 320MHz + e476 Panther Lake Bluetooth PCI Enumerator + e47b Serial IO I2C Host Controller - E47B + e47d Panther Lake USB 3.2 xHCI Controller f1a5 SSD 600P Series 8086 390a SSDPEKKW256G7 256GB f1a6 SSD DC P4101/Pro 7600p/760p/E 6100p Series @@ -40261,6 +40852,7 @@ 8510 0013 GB02-PCIe-C25-HH 8510 0014 GB2064-VPX-V40 8510 0201 GB2062-PUB-DDR + 0301 GenBu03 Series GPU # nee ScaleMP 8686 SAP 1010 vSMP Foundation controller [vSMP CTL] diff --git a/hwdb.d/pnp_id_registry.csv b/hwdb.d/pnp_id_registry.csv index 92806997f1924..2f40ebfca721f 100644 --- a/hwdb.d/pnp_id_registry.csv +++ b/hwdb.d/pnp_id_registry.csv @@ -1,2558 +1,2558 @@ Company,"PNP ID","Approved On Date" -"21ST CENTURY ENTERTAINMENT",BUT,04/25/2002 -"2-Tel B.V",TTL,03/20/1999 -"3Com Corporation",TCM,11/29/1996 -"3D Perception",TDP,05/16/2002 -3M,VSD,10/16/1998 -"3NOD Digital Technology Co. Ltd.",NOD,12/11/2014 -"A D S Exports",NGS,07/16/1998 -"A Plus Info Corporation",API,11/29/1996 -"A&R Cambridge Ltd.",ACG,06/13/2007 -"A/Vaux Electronics",AVX,08/29/2012 -"A+V Link",APV,01/27/2010 -"Aashima Technology B.V.",TRU,05/08/1998 -"Aava Mobile Oy",AAM,08/13/2013 -"ABBAHOME INC.",ABA,11/08/1999 -"Abeam Tech Ltd.",MEG,11/29/1996 -"Ably-Tech Corporation",ATC,11/29/1996 -"AboCom System Inc.",ABC,03/28/1997 -"ACC Microelectronics",WTC,11/29/1996 -"Access Works Comm Inc",AWC,11/29/1996 -"Acco UK Ltd.",PKA,05/12/2003 -"Accton Technology Corporation",ACC,11/29/1996 -Acculogic,ACU,11/29/1996 -"AccuScene Corporation Ltd",ASL,06/13/2007 -"Ace CAD Enterprise Company Ltd",ANT,11/29/1996 -"Acer Inc",CHE,11/29/1996 -"Acer Labs",ALI,11/29/1996 -"Acer Netxus Inc",ANX,11/29/1996 -"Acer Technologies",ACR,11/29/1996 -Acksys,ACK,11/29/1996 -"Acnhor Datacomm",ADC,11/29/1996 -Acon,CAL,11/29/1996 -"Acrolink Inc",ALK,03/12/1997 -"Acroloop Motion Control Systems Inc",ACM,03/26/1998 -"ACT Labs Ltd",LAB,09/02/1997 -"Actek Engineering Pty Ltd",ACE,11/29/1996 -"Actiontec Electric Inc",AEI,11/29/1996 -"ActivCard S.A",ACV,05/08/1998 -"Aculab Ltd",ACB,11/29/1996 -"Acutec Ltd.",ALM,11/08/1999 -"AD electronics",GLE,04/19/2000 -"Ad Lib MultiMedia Inc",ADM,04/23/1998 -"Adaptec Inc",ADP,11/29/1996 -"Adax Inc",ADX,11/29/1996 -ADC-Centre,RSH,11/08/1999 -"Add Value Enterpises (Asia) Pte Ltd",AVE,01/10/1999 -"Addi-Data GmbH",ADA,11/29/1996 -"ADI Systems Inc",ADI,11/29/1996 -"ADPM Synthesis sas",DPM,08/10/2000 -"Adrienne Electronics Corporation",AXB,10/07/1997 -Adtek,ADT,11/29/1996 -"Adtek System Science Company Ltd",ADK,11/29/1996 -"ADTI Media, Inc",FLE,09/15/2009 -"Adtran Inc",AND,11/29/1996 -"Advan Int'l Corporation",AGM,05/26/1998 -"Advance Computer Corporation",AVN,06/10/2010 -"Advanced Digital Systems",MSM,11/29/1996 -"Advanced Electronic Designs, Inc.",AED,07/12/2004 -"Advanced Engineering",RJS,06/25/1998 -"Advanced Gravis",GRV,11/29/1996 -"Advanced Integ. Research Inc",AIR,11/29/1996 -"Advanced Logic",ALR,11/29/1996 -"Advanced Micro Devices Inc",ADV,11/29/1996 -"Advanced Micro Peripherals Ltd",EVE,11/18/2011 -"Advanced Optics Electronics, Inc.",AOE,04/20/2004 -"Advanced Peripheral Devices Inc",ADD,11/29/1996 -"Advanced Research Technology",ABV,01/16/1997 -"Advanced Signal Processing Technologies",PSA,09/13/1999 -"Advantech Co., Ltd.",AHC,06/13/2007 -"Aerodata Holdings Ltd",ADH,11/11/1997 -"Aetas Peripheral International",AEP,11/08/1999 -"Aethra Telecomunicazioni S.r.l.",AET,12/13/1996 -"Agentur Chairos",CHS,03/15/2001 -"Agilent Technologies",AGT,10/08/2001 -"Ahead Systems",ASI,11/29/1996 -"AIMS Lab Inc",AIM,03/13/1998 -"Airlib, Inc",AYR,02/21/2000 -"Aironet Wireless Communications, Inc",AWL,08/11/1998 -"Aiwa Company Ltd",AIW,11/29/1996 -"AJA Video Systems, Inc.",AJA,10/11/2007 -"AKAMI Electric Co.,Ltd",AKE,09/03/2010 -"Akebia Ltd",AKB,11/29/1996 -"AKIA Corporation",AKI,12/23/1998 -"AL Systems",ALH,01/20/1999 -"Alacron Inc",ALA,11/29/1996 -"Alana Technologies",ALN,01/13/2000 -Alcatel,AOT,11/06/2001 -"Alcatel Bell",ABE,11/29/1996 -Aldebbaron,ADB,03/15/2001 -"Alenco BV",ALE,05/20/2014 -"ALEXON Co.,Ltd.",ALX,09/13/1999 -"Alfa Inc",AFA,11/29/1996 -"Algolith Inc.",ALO,05/02/2005 -"AlgolTek, Inc.",AGO,10/23/2013 -"Alien Internet Services",AIS,06/21/2001 -"Allen Bradley Company",ABD,11/29/1996 -"Alliance Semiconductor Corporation",ALL,11/29/1996 -"Allied Telesis KK",ATI,11/29/1996 -"Allied Telesyn International (Asia) Pte Ltd",ATA,11/10/1997 -"Allied Telesyn Int'l",ATK,11/29/1996 -"Allion Computer Inc.",ACO,10/23/2000 -"Alpha Data",XAD,10/08/2009 -"Alpha Telecom Inc",ATD,09/26/1997 -"Alpha-Top Corporation",ATP,12/04/1996 -"AlphaView LCD",ALV,11/01/2008 -"ALPS ALPINE CO., LTD.",APE,01/22/2013 -"ALPS ALPINE CO., LTD.",ALP,11/29/1996 -"ALPS ALPINE CO., LTD.",AUI,11/29/1996 -"Alta Research Corporation",ARC,11/29/1996 -"Altec Corporation",ALC,08/04/1998 -"Altec Lansing",ALJ,01/13/2000 -"ALTINEX, INC.",AIX,04/24/2001 -"Altmann Industrieelektronik",AIE,11/29/1996 -"Altos Computer Systems",ACS,11/29/1996 -"Altos India Ltd",AIL,11/29/1996 -"Alvedon Computers Ltd",CNC,11/06/1998 -"Ambient Technologies, Inc.",AMB,05/16/1999 -Altra,ALT,11/29/1996 -"Amdek Corporation",AMD,11/29/1996 -"America OnLine",AOL,11/29/1996 -"American Biometric Company",YOW,05/16/1999 -"American Express",AXP,07/16/1999 -"American Magnetics",AXI,03/15/2001 -"American Megatrends Inc",AMI,11/29/1996 -"American Nuclear Systems Inc",MCA,02/12/1997 -"American Power Conversion",CNB,03/15/2001 -"American Power Conversion",APC,11/29/1996 -"Amimon LTD.",AMN,06/13/2007 -"Amino Technologies PLC and Amino Communications Limited",AMO,12/09/2011 -"AMiT Ltd",AKL,12/02/1997 -"AMP Inc",AMP,11/29/1996 -"Amptron International Inc.",AII,05/24/2000 -"AMT International Industry",AMT,11/29/1996 -"AmTRAN Technology Co., Ltd.",AMR,06/10/2013 -"AMX LLC",AMX,07/06/2008 -Anakron,ANA,11/08/1999 -"Analog & Digital Devices Tel. Inc",ADN,03/14/1997 -"Analog Devices Inc",ADS,11/29/1996 -"Analog Way SAS",ANW,01/22/2014 -"Analogix Semiconductor, Inc",ANL,10/10/2005 -"Anchor Bay Technologies, Inc.",ABT,02/14/2006 -"Anatek Electronics Inc.",AAE,05/25/2004 -"Ancor Communications Inc",ACI,11/29/1996 -Ancot,ANC,11/29/1996 -"Anderson Multimedia Communications (HK) Limited",AML,01/03/2003 -"Andrew Network Production",ANP,11/29/1996 -"Anigma Inc",ANI,11/29/1996 -"Anko Electronic Company Ltd",ANK,03/24/1998 -"Ann Arbor Technologies",AAT,04/24/2001 -"an-najah university",BBB,03/15/2001 -"Anorad Corporation",ANO,01/13/2000 -"ANR Ltd",ANR,11/29/1996 -"Ansel Communication Company",ANS,11/29/1996 -"Antex Electronics Corporation",AEC,11/29/1996 -"AOpen Inc.",AOA,11/06/2001 -"AP Designs Ltd",APX,12/08/1997 -"Apache Micro Peripherals Inc",DNG,11/11/1997 -"Aplicom Oy",APL,05/02/2005 -"Appian Tech Inc",APN,11/29/1996 -"Apple Computer Inc",APP,11/29/1996 -AppliAdata,APD,11/29/1996 -"Applied Creative Technology",ACT,11/29/1996 -"Applied Memory Tech",APM,11/29/1996 -"Apricot Computers",ACL,11/29/1996 -"Aprilia s.p.a.",APR,02/22/1999 -"ArchiTek Corporation",ATJ,01/22/2014 -"Archtek Telecom Corporation",ACH,01/15/1997 -"Arcus Technology Ltd",ATL,11/29/1996 -"AREC Inc.",ARD,07/08/2013 -"Arescom Inc",ARS,11/29/1996 -Argolis,AGL,03/15/2001 -"Argosy Research Inc",ARI,02/24/1997 -"Argus Electronics Co., LTD",ARG,06/04/2004 -"Ariel Corporation",ACA,12/13/1996 -Arima,ARM,04/07/2004 -"Arithmos, Inc.",ADE,07/16/1999 -"Ark Logic Inc",ARK,11/29/1996 -"Arlotto Comnet Inc",ARL,04/29/1997 -"ARMSTEL, Inc.",AMS ,02/25/2011 -"Arnos Insturments & Computer Systems",AIC,11/29/1996 -"ARRIS Group, Inc.",ARR,01/27/2015 -"ART s.r.l.",IMB,01/27/2012 -"Artish Graphics Inc",AGI,11/29/1996 -Arvanics,NPA,03/05/2015 -"Asahi Kasei Microsystems Company Ltd",AKM,11/29/1996 -"Asante Tech Inc",ASN,11/29/1996 -"Ascom Business Systems",HER,01/20/1999 -"Ascom Strategic Technology Unit",ASC,11/29/1996 -"ASEM S.p.A.",ASM,03/15/2001 -"ASEM S.p.A.",AEM,11/29/1996 -"AseV Display Labs",ASE,10/16/1998 -"Ashton Bentley Concepts",ASH,09/20/2013 -"Asia Microelectronic Development Inc",AMA,09/24/1997 -"Ask A/S",ASK,11/29/1996 -"Askey Computer Corporation",DYN,07/22/1997 -"ASP Microelectronics Ltd",ASP,11/29/1996 -"Askey Computer Corporation",AKY,04/02/1997 -"Aspen Tech Inc",ACP,11/29/1996 -"AST Research Inc",AST,11/29/1996 -"Astec Inc",JAC,11/29/1996 -"ASTRA Security Products Ltd",ADL,07/30/1997 -"ASTRO DESIGN, INC.",ATO,06/06/2003 -"Asuscom Network Inc",ASU,11/29/1996 -AT&T,ATT,11/29/1996 -"AT&T Global Info Solutions",GIS,11/29/1996 -"AT&T Microelectronics",HSM,11/29/1996 -"AT&T Microelectronics",TME,11/29/1996 -"AT&T Paradyne",PDN,11/29/1996 -"Atelier Vision Corporation",AVJ,02/24/2015 -"Athena Informatica S.R.L.",ATH,01/29/1997 -"Athena Smartcard Solutions Ltd.",ATN,09/13/1999 -"Athenix Corporation",ATX,11/29/1996 -"ATI Tech Inc",BUJ,11/29/1996 -Atlantis,CFG,11/29/1996 -"ATM Ltd",ATM,11/29/1996 -"Atom Komplex Prylad",AKP,10/23/2000 -"Attachmate Corporation",AMC,11/29/1996 -"Attero Tech, LLC",FWA,04/20/2010 -"Audio Processing Technology Ltd",APT,03/18/1997 -AudioScience,ASX,11/29/1996 -"August Home, Inc.",AUG,06/11/2014 -"Auravision Corporation",AVC,11/29/1996 -"Aureal Semiconductor",AUR,11/29/1996 -"Autologic Inc",APS,11/29/1996 -"automated computer control systems",CLT,09/13/1999 -"Autotime Corporation",AUT,10/08/2001 -"Auvidea GmbH",AUV,04/21/2014 -"Avalue Technology Inc.",AVL,11/18/2011 -"Avance Logic Inc",ALS,11/29/1996 -"Avaya Communication",AVA,03/15/2001 -Avencall,AEN,01/27/2012 -"AVer Information Inc.",AVR,05/07/2010 -"Avid Electronics Corporation",AVD,11/29/1996 -"AVM GmbH",AVM,11/29/1996 -"Avolites Ltd",AAA,02/17/2012 -"Avocent Corporation",AVO,10/23/2000 -"Avtek (Electronics) Pty Ltd",AVT,11/29/1996 -"AWETA BV",ACD,01/20/1998 -Axel,AXL,11/29/1996 -"AXIOMTEK CO., LTD.",AXC,05/02/2005 -"Axonic Labs LLC",AXO,06/21/2012 -"Axtend Technologies Inc",AXT,12/01/1997 -"Axxon Computer Corporation",AXX,11/29/1996 -"AXYZ Automation Services, Inc",AXY,08/11/1998 -"Aydin Displays",AYD,06/13/2007 -"AZ Middelheim - Radiotherapy",AZM,11/14/2003 -"Aztech Systems Ltd",AZT,11/29/1996 -B&Bh,BBH,01/17/2003 -"B.& V. s.r.l.",SMR,03/21/1997 -"B.F. Engineering Corporation",BFE,11/29/1996 -"B.U.G., Inc.",BUG,08/30/2011 -"Bang & Olufsen",BNO,05/16/2003 -"Banksia Tech Pty Ltd",BNK,11/29/1996 -Banyan,BAN,11/29/1996 -BARC,BRC,08/10/2000 -"Barco Display Systems",BDS,09/13/1999 -"Barco GmbH",BCD,03/07/2011 -"Barco Graphics N.V",BGB,11/29/1996 -"Barco, N.V.",BPS,09/12/2000 -"Barco, N.V.",DDS,10/23/2000 -"Baug & Olufsen",BEO,11/29/1996 -"Beaver Computer Corporaton",BCC,11/29/1996 -"Beckhoff Automation",BEC,04/25/2002 -"Beckworth Enterprises Inc",BEI,07/16/1997 -"Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd.",AGC,06/21/2001 -"Beijing AnHeng SecoTech Information Technology Co., Ltd.",AHS,03/24/2015 -"Beijing ANTVR Technology Co., Ltd.",ANV,08/24/2015 -"Beijing Northern Radiantelecom Co.",NRT,03/20/1999 -"Beko Elektronik A.S.",BEK,06/15/2005 -"Beltronic Industrieelektronik GmbH",BEL,09/05/2006 -"Benson Medical Instruments Company",BMI,12/04/1996 -"B&R Industrial Automation GmbH",BUR,11/29/1996 -"Best Buy",INZ,06/04/2004 -"Best Buy",VPR,05/16/2002 -"Best Power",BPU,11/29/1996 -"Biamp Systems Corporation",BIA,05/14/2015 -"BICC Data Networks Ltd",ICC,11/29/1996 -"Big Island Communications",BIC,05/13/1997 -"Billion Electric Company Ltd",BIL,12/11/1996 -"BioLink Technologies",BLN,08/10/2000 -"BioLink Technologies International, Inc.",BIO,05/24/2000 -"BIOMED Lab",BML,05/22/1997 -"Biomedical Systems Laboratory",BSL,10/16/1997 -BIOMEDISYS,BMS,05/24/2000 -"Biometric Access Corporation",BAC,05/19/1998 -"BioTao Ltd",BTO,03/21/2012 -"Bit 3 Computer",BIT,11/29/1996 -"Bit 3 Computer",BTC,11/29/1996 -"Bitfield Oy",BTF,11/29/1996 -"BitHeadz, Inc.",BHZ,09/29/2003 -"Bitworks Inc.",BWK,07/10/2003 -"Blackmagic Design",BMD,09/13/2012 -"Blonder Tongue Labs, Inc.",BDR,09/16/2008 -"Bloomberg L.P.",BLP,09/16/2008 -"Boca Research Inc",ZZZ,02/13/1997 -"Boca Research Inc",BRI,11/29/1996 -"BodySound Technologies, Inc.",BST,03/12/2008 -"Boeckeler Instruments Inc",BII,10/17/1996 -"Booria CAD/CAM systems",BCS,05/11/2005 -BOS,BOS,07/03/1997 -"Bose Corporation",BSE,09/05/2006 -"Boulder Nonlinear Systems",BNS,03/12/2008 -"Braemac Pty Ltd",BRA,11/18/2010 -"Braemar Inc",BRM,10/07/1997 -"Brahler ICS",BDO,06/04/1998 -"Brain Boxes Limited",BBL,10/02/2001 -"Bridge Information Co., Ltd",BRG,08/11/1998 -"BRIGHTSIGN, LLC",BSN,02/28/2012 -"Brilliant Technology",BTE,11/29/1996 -"Broadata Communications Inc.",BCI,11/19/2013 -Broadcom,BCM,04/01/2004 -"BROTHER INDUSTRIES,LTD.",BRO,02/21/2000 -"BTC Korea Co., Ltd",NFC,02/25/2002 -"Budzetron Inc",BGT,11/29/1996 -Bull,BUL,02/03/1998 -"Bull AB",BNE,10/06/1998 -Busicom,BLI,08/11/1998 -"BusTech Inc",BTI,11/29/1996 -BusTek,BUS,11/29/1996 -"Butterfly Communications",FLY,05/05/1997 -"Buxco Electronics",BXE,11/29/1996 -"byd:sign corporation",BYD,04/10/2008 -"C3PO S.L.",XMM,03/03/1998 -"CA & F Elettronica",CAC,05/16/1999 -"Cabletime Ltd",CBT,05/04/2010 -"Cabletron System Inc",CSI,11/29/1996 -Cache,CCI,11/29/1996 -CalComp,CAG,11/29/1996 -CalComp,CDP,11/29/1996 -"Calibre UK Ltd",CUK,09/15/2005 -"California Institute of Technology",CSO,03/20/1999 -"Cambridge Audio",CAM,08/09/2008 -"Cambridge Electronic Design Ltd",CED,11/29/1996 -"Cambridge Research Systems Ltd",CMR,04/25/2002 -"Canon Inc",CNN,11/29/1996 -"Canon Inc.",CAI,11/06/2001 -"Canonical Ltd.",UBU,05/24/2013 -"Canopus Company Ltd",CAN,11/29/1996 -"Capella Microsystems Inc.",CPM,05/09/2012 -"Capetronic USA Inc",CCP,11/29/1996 -"Capstone Visua lProduct Development",DJE,10/09/2008 -"Cardinal Company Ltd",CAR,11/29/1996 -"Cardinal Technical Inc",CRD,11/29/1996 -CardLogix,CLX,03/15/2001 -"Carina System Co., Ltd.",CKJ,09/03/2010 -"Carl Zeiss AG",CZE,06/03/2009 -"CASIO COMPUTER CO.,LTD",CAS,10/06/1998 -"Castles Automation Co., Ltd",CAA,01/13/2000 -"Cavium Networks, Inc",CAV,02/02/2011 -"C-C-C Group Plc",FVX,05/04/1998 -CCL/ITRI,CCL,03/31/1997 -"C-Cube Microsystems",CCC,11/29/1996 -C-DAC,CEP,11/29/1996 -"Cebra Tech A/S",CBR,11/29/1996 -"Cefar Digital Vision",CEF,02/19/1997 -"Centurion Technologies P/L",CEN,10/23/2000 -"Century Corporation",TCE,11/29/1996 -"Cerevo Inc.",CRV,07/13/2010 -Ceronix,CER,09/02/2008 -"Ceton Corporation",TOM,05/08/2014 -"CH Products",CHP,04/24/1997 -"ChangHong Electric Co.,Ltd",CHD,11/30/2001 -"Chase Research PLC",CHA,11/29/1996 -"Cherry GmbH",CHY,05/16/1999 -"Chi Mei Optoelectronics corp.",CMO,03/15/2001 -"CHIC TECHNOLOGY CORP.",CHM,07/16/1999 -"Chicony Electronics Company Ltd",CEC,11/29/1996 -"Chimei Innolux Corporation",CMN,09/02/2010 -"China Hualu Group Co., Ltd.",HLG,05/13/2013 -Chloride-R&D,CHL,11/29/1996 -"Christie Digital Systems Inc",CDG,04/24/2001 -"Chromatec Video Products Ltd",CVP,08/09/2013 -"Chrontel Inc",CHI,11/29/1996 -"Chunghwa Picture Tubes,LTD.",CHT,03/15/2001 -"Chunghwa Telecom Co., Ltd.",CTE,05/16/2002 -"Chunichi Denshi Co.,LTD.",KCD,12/23/2010 -"Chuomusen Co., Ltd.",QQQ,08/07/2002 -"Chyron Corp",CGS,11/13/2008 -Cine-tal,CNE,06/13/2007 -"Cipher Systems Inc",PTG,11/29/1996 -"Ciprico Inc",CIP,11/29/1996 -"Ciprico Inc",CPC,11/29/1996 -"Cirel Systemes",FPX,11/29/1996 -"Cirque Corporation",CRQ,11/29/1996 -"Cirrus Logic Inc",CIR,11/29/1996 -"Cirrus Logic Inc",CLI,11/29/1996 -"Cirtech (UK) Ltd",SNS,08/20/1997 -"CIS Technology Inc",WSC,11/29/1996 -"Cisco Systems Inc",CIS,11/29/1996 -"Citicom Infotech Private Limited",CIL,08/10/2000 -"Citifax Limited",CIT,07/16/1997 -"Citron GmbH",CIN,07/28/2005 -"Clarion Company Ltd",CLA,11/29/1996 -"Clarity Visual Systems",CVS,01/13/2000 -"Classe Audio",CLE,02/16/2006 -"Clevo Company",CLV,01/30/1998 -"Clinton Electronics Corp.",PPM,10/01/2003 -"Clone Computers",CLO,11/29/1996 -"Cloudium Systems Ltd.",CSL,02/14/2013 -"CMC Ltd",CMC,11/29/1996 -"C-Media Electronics",CMI,11/29/1996 -"CNet Technical Inc",JQE,11/29/1996 -"COBY Electronics Co., Ltd",COB,06/13/2007 -"CODAN Pty. Ltd.",COD,10/23/2000 -"Codec Inc.",COI,11/30/2001 -"Codenoll Technical Corporation",CDN,11/29/1996 -"COINT Multimedia Systems",CNT,03/20/1999 -Colin.de,CDE,01/18/2005 -"Colorado MicroDisplay, Inc.",CMD,03/20/1999 -"Colorado Video, Inc.",CVI,08/15/2012 -"COM 1",MVX,11/29/1996 -"Comex Electronics AB",CMX,05/28/2004 -"Comm. Intelligence Corporation",CIC,11/29/1996 -"COMMAT L.t.d.",CLD,08/10/2000 -"Communications Specialies, Inc.",SDH,09/06/2005 -"Communications Supply Corporation (A division of WESCO)",INX,11/07/2012 -"Compal Electronics Inc",CPL,11/29/1996 -"Compaq Computer Company",CPQ,11/29/1996 -"Compound Photonics",CPP,10/01/2013 -CompuAdd,CPD,11/29/1996 -"CompuMaster Srl",CMS,02/22/1999 -"Computer Diagnostic Systems",CDS,03/15/2001 -"Computer Peripherals Inc",CPI,11/29/1996 -"Computer Technology Corporation",CTP,03/26/1998 -"ComputerBoards Inc",CBI,02/03/1998 -"Computerm Corporation",CTM,11/29/1996 -"Computone Products",CTN,11/29/1996 -Comrex,COX,10/18/2011 -"Comtec Systems Co., Ltd.",CTS,04/25/2002 -"Comtime GmbH",CMM,09/23/2002 -"Comtrol Corporation",COM,11/29/1996 -"Concept Development Inc",CDI,11/29/1996 -"Concept Solutions & Engineering",CSE,12/11/1996 -"Concepts Inc",DCI,11/29/1996 -"Conexant Systems",CXT,01/20/1999 -"congatec AG",CGT,06/16/2011 -"Connect Int'l A/S",CNI,11/29/1996 -"Connectware Inc",CWR,11/29/1996 -"CONRAC GmbH",CRC,04/20/2004 -"Consultancy in Advanced Technology",CAT,09/19/1997 -"Consumer Electronics Association",CEA,09/05/2006 -"CONTEC CO.,LTD.",CCJ,08/10/2000 -"Contec Company Ltd",CON,11/29/1996 -"Contemporary Research Corp.",CRH,02/24/2015 -"Control4 Corporation",CTR,05/28/2014 -"Convergent Data Devices",CDD,02/27/2004 -"Convergent Design Inc.",CDV,09/05/2006 -"Core Dynamics Corporation",CDC,11/29/1996 -"Corion Industrial Corporation",ART,11/29/1996 -"Core Technology Inc",COT,04/19/2000 -CoreLogic,CLG,11/27/1998 -"Cornerstone Imaging",CRN,11/29/1996 -"Corollary Inc",COR,12/13/1996 -"Cosmic Engineering Inc.",CSM,04/18/2012 -"CoStar Corporation",COS,11/29/1996 -"CoSystems Inc",CTA,10/24/1998 -"Covia Inc.",CVA,05/11/2010 -cPATH,CPT,03/09/1998 -"CRALTECH ELECTRONICA, S.L.",CRA,03/24/2015 -"Cray Communications",CDK,11/29/1996 -"CRE Technology Corporation",IOA,06/30/1997 -"Creative Labs Inc",CRE,11/29/1996 -"Creative Logic  ",CRL,10/16/1997 -"Creative Technology Ltd",CTL,11/29/1996 -"Creatix Polymedia GmbH",CTX,11/29/1996 -"Crescendo Communication Inc",CRS,11/29/1996 -"Cresta Systems Inc",CSD,08/01/1997 -"Crestron Electronics, Inc.",CEI,05/08/2006 -"Crio Inc.",CRI,09/13/1999 -"Cromack Industries Inc",CII,01/22/1997 -"Crystal Computer",XTL,11/29/1996 -"Crystal Semiconductor",CSC,11/29/1996 -"CrystaLake Multimedia",CLM,11/29/1996 -"CSS Laboratories",CSS,01/02/1997 -"CSTI Inc",CST,11/29/1996 -"CTC Communication Development Company Ltd",CTC,10/21/1997 -"Cubix Corporation",CUB,11/29/1996 -"Curtiss-Wright Controls, Inc.",CWC,04/05/2013 -Cyberlabs,CYL,04/14/1998 -CyberVision,CYB,05/13/1997 -Cyberware,CYW,02/21/2000 -"Cybex Computer Products Corporation",CBX,11/08/1999 -"Cyclades Corporation",CYD,05/07/2001 -"Cylink Corporation",CYC,11/29/1996 -"Cyrix Corporation",CYX,10/21/1997 -"Cyrix Corporation",CRX,03/21/1997 -"Cytechinfo Inc",CYT,03/13/1998 -"Cyviz AS",CYV,04/25/2002 -"D&M Holdings Inc, Professional Business Company",DMP,09/05/2006 -"D.N.S. Corporation",OPI,11/29/1996 -"DA2 Technologies Corporation",DDA,03/13/2006 -"DA2 Technologies Inc",DAW,09/06/2005 -"Daewoo Electronics Company Ltd",DWE,11/29/1996 -"Dai Telecom S.p.A.",TLT,06/04/2003 -"Daintelecom Co., Ltd",DIN,11/08/1999 -"DAIS SET Ltd.",DAI,02/21/2000 -Daktronics,DAK,06/23/2004 -"Dale Computer Corporation",DCC,11/29/1996 -"Dancall Telecom A/S",DCT,08/12/1997 -"Danelec Marine A/S",DAN,12/24/2009 -"Danka Data Devices",DDD,11/29/1996 -"Daou Tech Inc",DAU,11/29/1996 -DAT,HCA,03/15/2001 -"Data Apex Ltd",DAX,11/29/1996 -"Data Display AG",DDI,07/17/2002 -"Data Expert Corporation",DXP,11/29/1996 -"Data Export Corporation",EXP,11/29/1996 -"Data Modul AG",DMO,12/03/2013 -"Data Price Informatica",EBH,05/24/2001 -"Data Race Inc",DRI,07/30/1997 -"Data Ray Corp.",DRC,11/30/2001 -"Data Translation",DTX,11/29/1996 -"Data Video",DVT,02/13/2007 -"Databook Inc",DBK,11/29/1996 -"Datacast LLC",DCD,12/02/1997 -"Datacommunicatie Tron B.V.",TRN,11/29/1996 -"Datacube Inc",DQB,11/29/1996 -"Datadesk Technologies Inc",DDT,11/27/1998 -"Datakey Inc",DKY,04/06/1998 -"Datalogic Corporation",LJX,11/29/1996 -"Datang Telephone Co",DTN,09/23/1998 -"Dataq Instruments Inc",DII,11/29/1996 -"Datasat Digital Entertainment",DDE,11/18/2011 -"Datatronics Technology Inc",DCV,01/02/1997 -"Datel Inc",DAT,11/29/1996 -"Datenerfassungs- und Informationssysteme",MSD,03/16/1998 -"Davicom Semiconductor Inc",DAV,01/15/1997 -"DAVIS AS",DAS,02/03/1998 -"DB Networks Inc",DBN,12/01/1997 -"DBA Hans Wedemeyer",HWC,03/20/1999 -"DCM Data Products",DCM,11/29/1996 -"Dearborn Group Technology",DGT,11/11/1997 -"DECIMATOR DESIGN PTY LTD",DXD,03/06/2012 -"Decros Ltd",DCR,11/29/1996 -"Deep Video Imaging Ltd",MLD,08/14/2003 -"DEI Holdings dba Definitive Technology",DFT,12/09/2011 -"Deico Electronics",DEI,11/29/1996 -"Dell Inc",DLL,03/27/2009 -"Dell Inc.",DEL,12/09/2009 -"Delphi Automotive LLP",DPH,10/15/2013 -"Delta Electronics Inc",DPC,11/29/1996 -"Delta Information Systems, Inc",DDV,01/03/2012 -DELTATEC,DTA,03/13/2009 -"Deltec Corporation",FPS,11/29/1996 -"DENON, Ltd.",DON,04/01/2004 -"Dension Audio Systems",DHD,03/04/2013 -"Densitron Computers Ltd",DEN,09/13/1999 -"Design & Test Technology, Inc.",DTT,09/30/2010 -"Design Technology",LPI,11/29/1996 -"Deterministic Networks Inc.",DNI,04/19/2000 -"Deutsche Telekom Berkom GmbH",BCQ,08/12/1997 -"Deutsche Thomson OHG",DTO,06/14/2007 -"Devolo AG",DVL,05/30/2002 -"Dextera Labs Inc",DXL,12/09/2009 -DFI,DFI,11/29/1996 -"DH Print",DHP,11/29/1996 -Diadem,DIA,11/29/1996 -"Diagsoft Inc",DGS,11/29/1996 -"Dialogue Technology Corporation",DCO,06/16/2004 -"Diamond Computer Systems Inc",DCS,11/29/1996 -"Diamond Lane Comm. Corporation",DLC,11/29/1996 -DiCon,DNV,12/15/2004 -"Dictaphone Corporation",DVD,04/03/1998 -"Diebold Inc.",DBD,09/05/2006 -"Digatron Industrie Elektronik GmbH",DAE,02/24/1997 -"DIGI International",DGI,11/29/1996 -"DigiBoard Inc",DBI,11/29/1996 -"Digicom S.p.A.",DIG,11/29/1996 -"Digicom Systems Inc",DMB,03/13/1998 -"Digicorp European sales S.A.",DGP,05/22/1997 -"Digiital Arts Inc",DGA,06/14/2007 -"Digipronix Control Systems",DXC,07/16/1999 -"Digital Acoustics Corporation",DAC,05/24/2000 -"Digital Audio Labs Inc",DAL,11/29/1996 -"Digital Communications Association",DCA,11/29/1996 -"Digital Discovery",SHR,09/24/1997 -"Schneider Electric Japan Holdings, Ltd.",PRF,01/02/2003 -"Digital Equipment Corporation",DEC,11/29/1996 -"Digital Processing Systems",DPS,11/29/1996 -"Digital Projection Limited",DPL,07/09/2002 -"DIGITAL REFLECTION INC.",DRD,02/21/2000 -"Digital Video System",DVS,11/29/1996 -"DigiTalk Pro AV",DPA,10/23/2000 -"Digital-Logic GmbH",DLG,09/02/2003 -"Digitan Systems Inc",DSI,11/29/1996 -"Digitelec Informatique Park Cadera",DLT,11/29/1996 -"Dimension Technologies, Inc.",DTE,05/03/2010 -"Dimond Multimedia Systems Inc",DMM,11/29/1996 -"Diseda S.A.",DIS,11/29/1996 -"Distributed Management Task Force, Inc. (DMTF)",DMT,03/31/2009 -"Diversified Technology, Inc.",DTI,11/29/1996 -"D-Link Systems Inc",ABO,11/29/1996 -"D-Link Systems Inc",DLK,11/29/1996 -"DNA Enterprises, Inc.",DNA,09/01/1998 -"DO NOT USE - AUO",AUO,09/16/2008 -"DO NOT USE - LPL",LPL,09/16/2008 -"DO NOT USE - PHI",PHI,11/29/1996 -"DO NOT USE - PTW",PTW,09/09/2009 -"DO NOT USE - PVC",PVC,09/09/2009 -"DO NOT USE - RTK",RTK,09/09/2009 -"DO NOT USE - SEG",SEG,09/09/2009 -"DO NOT USE - TNJ",TNJ,09/09/2009 -"DO NOT USE - UND",UND,11/29/1996 -"DO NOT USE - UNE",UNE,11/29/1996 -"DO NOT USE - UNF",UNF,11/29/1996 -"DO NOT USE - WAN",WAN,09/09/2009 -"DO NOT USE - XER",XER,09/09/2009 -"DO NOT USE - XOC",XOC,09/09/2009 -"Doble Engineering Company",DBL,11/29/1996 -DocuPoint,DPI,11/29/1996 -"Dolby Laboratories Inc.",DLB,01/27/2010 -"Dolman Technologies Group Inc",DOL,11/11/1997 -"Domain Technology Inc",DSP,11/29/1996 -"DOME imaging systems",DMS,10/23/2000 -"Dome Imaging Systems",DOM,11/29/1996 -"Dongguan Alllike Electronics Co., Ltd.",AIK,04/11/2015 -"Dosch & Amand GmbH & Company KG",DUA,12/02/1997 -"Dotronic Mikroelektronik GmbH",DOT,06/28/2002 -"dPict Imaging, Inc.",DIM,02/12/2008 -"DpiX, Inc.",DPX,09/23/1998 -DPT,DPT,11/29/1996 -"Dr. Bott KG",DRB,04/25/2002 -"Dr. Neuhous Telekommunikation GmbH",DNT,11/29/1996 -"Dragon Information Technology",DIT,11/29/1996 -"DRS Defense Solutions, LLC",DRS,10/18/2011 -"DS Multimedia Pte Ltd",DSD,02/14/2006 -"DSM Digital Services GmbH",DSM,11/29/1996 -"dSPACE GmbH",DCE,12/16/1996 -"DTC Tech Corporation",DTC,11/29/1996 -"DugoTech Co., LTD",DGK,06/14/2007 -"Dune Microsystems Corporation",DMC,11/29/1996 -"Dycam Inc",DYC,01/08/1998 -"Dymo-CoStar Corporation",DYM,12/28/1998 -"Dynamic Controls Ltd",DCL,05/24/2000 -"Dynax Electronics (HK) Ltd",DTK,11/29/1996 -"Dynax Electronics (HK) Ltd",DYX,11/29/1996 -"e.Digital Corporation",EDC,10/23/2000 -"E.E.P.D. GmbH",EEP,06/14/2007 -"Eagle Technology",EGL,11/29/1996 -"Eastman Kodak Company",KOD,05/24/2000 -"Eastman Kodak Company",EKC,11/29/1996 -"Easytel oy",TWI,07/16/1999 -"EBS Euchner Büro- und Schulsysteme GmbH",EBS,02/05/2013 -"Echo Speech Corporation",ECO,11/29/1996 -"Eclipse Tech Inc",ETI,11/29/1996 -"E-Cmos Tech Corporation",ECM,11/29/1996 -"Eden Sistemas de Computacao S/A",ESC,11/29/1996 -"Edimax Tech. Company Ltd",EDI,11/29/1996 -EDMI,EDM,07/16/1998 -"Edsun Laboratories",ELI,11/29/1996 -"EE Solutions, Inc.",EES,04/16/2003 -"EEH Datalink GmbH",EEH,07/03/1997 -"Efficient Networks",ENI,11/29/1996 -"Egenera, Inc.",EGN,10/08/2002 -"Eicon Technology Corporation",EIC,11/29/1996 -"EIZO GmbH Display Technologies",EGD,02/13/2009 -"Eizo Nanao Corporation",ENC,12/28/1998 -"EKSEN YAZILIM",EKS,04/25/2002 -"ELAD srl",ELA,04/25/2002 -"ELAN MICROELECTRONICS CORPORATION",ETD,11/03/2009 -"ELAN MICROELECTRONICS CORPORATION",TSH,11/14/2014 -"Elbit Systems of America",ESA,06/15/2009 -"ELCON Systemtechnik GmbH",ESG,07/16/1999 -"ELEA CardWare",LXS,06/25/1998 -"Elecom Company Ltd",ECP,11/29/1996 -"Elecom Company Ltd",ELE,11/29/1996 -"Electro Cam Corp.",ECA,08/10/2000 -"Electro Scientific Ind",ELC,11/29/1996 -"Electronic Measurements",MMM,11/29/1996 -"Electronic Trade Solutions Ltd",ETS,08/20/2002 -"Electronic-Design GmbH",EDG,08/12/1997 -"Electrosonic Ltd",ELL,09/13/1999 -"Element Labs, Inc.",ELT,10/11/2007 -"Elgato Systems LLC",EGA,02/08/2011 -"Elitegroup Computer Systems Company Ltd",ECS,11/29/1996 -"Elitegroup Computer Systems Company Ltd",UEG,11/29/1996 -"Elmeg GmbH Kommunikationstechnik",ELG,11/29/1996 -"Elmic Systems Inc",ELM,11/29/1996 -"ELMO COMPANY, LIMITED",EMO,06/26/2012 -"Elo TouchSystems Inc",ELO,11/29/1996 -"Elonex PLC",ELX,11/29/1996 -"El-PUSK Co., Ltd.",LPE,08/14/2001 -"ELSA GmbH",ELS,11/29/1996 -"ELTEC Elektronik AG",EAG,11/25/2014 -"Embedded computing inc ltd",EMB,02/25/2002 -"Embedded Solution Technology",EST,05/24/2000 -"Embrionix Design Inc.",EMD,07/24/2013 -"Emcore Corporation",EMK,05/31/2012 -"Emerging Display Technologies Corp",EDT,08/18/2009 -"EMG Consultants Inc",EMG,11/29/1996 -"EMiNE TECHNOLOGY COMPANY, LTD.",EME,06/16/2005 -Empac,EPC,12/04/1996 -"Emulex Corporation",EMU,11/29/1996 -"Enciris Technologies",ECI,11/01/2008 -"Enciris Technologies",ECT,11/01/2008 -"ENE Technology Inc.",ENE,03/15/2001 -"e-Net Inc",DTL,10/16/1997 -Enhansoft,EHN,11/16/2010 -"ENIDAN Technologies Ltd",END,04/19/2000 -"Ensemble Designs, Inc",ESD,12/09/2009 -"Ensoniq Corporation",ENS,11/29/1996 -"Enterprise Comm. & Computing Inc",ENT,11/29/1996 -"Envision Peripherals, Inc",EPI,02/22/1999 -"Eon Instrumentation, Inc.",EON,01/15/2015 -"EPiCON Inc.",EPN,09/23/1998 -"Epiphan Systems Inc. ",EPH ,03/14/2011 -"Epson Research",EHJ,11/29/1996 -"Equinox Systems Inc",EQX,11/29/1996 -"Equipe Electronics Ltd.",EQP,07/14/2005 -"Ergo Electronics",EGO,11/29/1996 -"Ergo System",ERG,11/29/1996 -"Ericsson Mobile Communications AB",ERI,10/22/1997 -"Ericsson Mobile Networks B.V.",EUT,04/14/1998 -"Ericsson, Inc.",ERN,09/23/1998 -ES&S,ESK,11/08/1999 -eSATURNUS,ESN,02/21/2012 -"Escort Insturments Corporation",ERT,05/02/1997 -"ESS Technology Inc",ESS,11/29/1996 -"ESSential Comm. Corporation",ECC,11/29/1996 -"Esterline Technologies",ESL,01/06/2012 -ScioTeq,ESB,01/15/2015 -"E-Systems Inc",ESY,11/29/1996 -"ET&T Technology Company Ltd",EEE,05/04/1998 -"E-Tech Inc",ETT,11/29/1996 -"eTEK Labs Inc.",ETK,07/16/1998 -"Etherboot Project",ETH,07/09/2010 -"Eugene Chukhlomin Sole Proprietorship, d.b.a.",ECK,05/03/2008 -"Euraplan GmbH",ERP,11/29/1996 -"Evans and Sutherland Computer",EAS,01/28/2003 -Everex,EVX,11/29/1996 -"Everton Technology Company Ltd",ETC,04/10/1997 -"Evertz Microsystems Ltd.",ETL,06/14/2007 -"eviateg GmbH",EVI,02/21/2000 -"Ex Machina Inc",EMI,11/29/1996 -"Exacom SA",YHW,11/29/1996 -"Exatech Computadores & Servicos Ltda",EXT,09/23/1998 -"Excel Company Ltd",ECL,05/27/1997 -"Excession Audio",EXC,11/06/1998 -"EXFO Electro Optical Engineering",XFO,04/29/1998 -"Exide Electronics",EXI,11/29/1996 -"Extended Systems, Inc.",ESI,07/16/1999 -"Exterity Ltd",EXY,02/12/2009 -"Extraordinary Technologies PTY Limited",CRO,04/11/2005 -"Exxact GmbH",EXX,11/29/1996 -"eyefactive Gmbh",EYF,07/07/2015 -"eyevis GmbH",EYE,11/18/2011 -"EzE Technologies",EZE,02/21/2005 -"F.J. Tieman BV",FJT,06/25/1998 -"Fairfield Industries",FFI,11/29/1996 -"Fantalooks Co., Ltd.",FAN,03/12/2014 -"Fanuc LTD",FNC,01/29/1997 -"Farallon Computing",FAR,11/29/1996 -"FARO Technologies",FRO,09/21/2012 -"Faroudja Laboratories",FLI,06/02/2004 -"Fast Multimedia AG",FMA,11/29/1996 -"FastPoint Technologies, Inc.",FTI,06/21/2001 -"Feature Integration Technology Inc.",FIT,08/11/2009 -"Fellowes & Questec",FEL,11/29/1996 -"Fellowes, Inc.",FMI,07/05/2001 -"Fen Systems Ltd.",FEN,05/04/2010 -"Ferranti Int'L",FER,11/29/1996 -"Ferrari Electronic GmbH",TLA,12/04/1996 -FHLP,FHL,11/29/1996 -"Fibernet Research Inc",FRI,11/29/1996 -"Finecom Co., Ltd.",FIN,11/27/1998 -"Fingerprint Cards AB",FPC,06/14/2013 -"First Industrial Computer Inc",PCG,11/29/1996 -"First International Computer Inc",LEO,09/19/1997 -"First International Computer Ltd",FCG,04/10/1997 -"First Virtual Corporation",FVC,11/29/1996 -"Flat Connections Inc",FWR,11/29/1996 -"FlightSafety International",SSD,08/10/2000 -"FLY-IT Simulators",FIS,09/08/1997 -"FocalTech Systems Co., Ltd.",FTS,07/23/2013 -"Focus Enhancements, Inc.",FCS,12/12/2002 -"Fokus Technologies GmbH",FOK,10/22/2013 -"FOR-A Company Limited",FOA,12/06/2008 -"Force Computers",FRC,11/29/1996 -"Ford Microelectronics Inc",FMC,03/11/1997 -"Fore Systems Inc",FSI,11/29/1996 -"Forefront Int'l Ltd",FIL,11/29/1996 -"Formosa Industrial Computing Inc",FIC,11/29/1996 -Formoza-Altair,FMZ,04/25/2003 -"Forth Dimension Displays Ltd",FDD,07/07/2015 -"Forvus Research Inc",FRE,04/24/1997 -"Foss Tecator",FOS,10/22/1997 -"Founder Group Shenzhen Co.",FZC,11/08/1999 -"Fountain Technologies Inc",FTN,11/29/1996 -"Fraunhofer Heinrich-Hertz-Institute",HHI,07/27/2012 -"Freedom Scientific BLV",FRD,06/15/2007 -"FREEMARS Heavy Industries",TCX,03/15/2001 -"Frontline Test Equipment Inc.",FTE,01/20/1999 -"FTG Data Systems",FTG,11/29/1996 -"Fuji Xerox",FXX,11/29/1996 -"FUJIFILM Corporation",FFC,08/22/2011 -"Fujitsu Display Technologies Corp.",FDT,10/23/2002 -"Fujitsu General Limited.",FGL,02/21/2000 -"Fujitsu Ltd",FUJ,11/29/1996 -"Fujitsu Microelect Ltd",FML,11/29/1996 -"Fujitsu Peripherals Ltd",FPE,08/19/1997 -"Fujitsu Siemens Computers GmbH",FUS,01/13/2000 -"Fujitsu Spain",FJS,11/29/1996 -"FCL COMPONENTS LIMITED",FJC,05/16/1999 -"FUJITSU TEN LIMITED",FTL,12/20/2011 -"Funai Electric Co., Ltd.",FNI,01/18/2005 -"Furukawa Electric Company Ltd",FCB,11/29/1996 -"FURUNO ELECTRIC CO., LTD.",FEC,11/29/1996 -"Future Designs, Inc.",FDI,09/29/2014 -"Future Domain",FDC,11/29/1996 -"Future Systems Consulting KK",FSC,11/29/1996 -"Futuretouch Corporation",FTC,11/29/1996 -"FZI Forschungszentrum Informatik",FZI,08/12/1997 -"G&W Instruments GmbH",SPH,02/25/2002 -"G. Diehl ISDN GmbH",GDI,11/29/1996 -"Gadget Labs LLC",GLS,11/29/1996 -"Gage Applied Sciences Inc",GAG,11/29/1996 -"GAI-Tronics, A Hubbell Company",HUB,03/26/2009 -"Galil Motion Control",GAL,11/29/1996 -"Garmin International",GRM,12/09/2011 -"Garnet System Company Ltd",GTM,11/29/1996 -"Gateway 2000",GWY,11/29/1996 -"Gateway Comm. Inc",GCI,11/29/1996 -"Gateworks Corporation",GWK,07/31/2013 -"Gaudi Co., Ltd.",GAU,03/31/2003 -"GCC Technologies Inc",GCC,06/05/1997 -GDS,GDS,06/23/2004 -"GE Fanuc Embedded Systems",GEF,06/14/2007 -"Abaco Systems, Inc.",GEH,09/03/2010 -"Gefen Inc.",GFN,10/11/2007 -"Gem Plus",GEM,02/27/1998 -"GEMINI 2000 Ltd",GMN,10/23/2000 -"General Datacom",GDC,11/29/1996 -"General Dynamics C4 Systems",GED,01/09/2013 -"General Information Systems",GML,01/13/2000 -"General Inst. Corporation",GIC,11/29/1996 -"General Standards Corporation",GSC,07/16/1998 -"General Touch Technology Co., Ltd.",GTT,11/21/2002 -"Genesys ATE Inc",GEN,11/29/1996 -"Genesys Logic",GLM,11/08/1999 -"Gennum Corporation",GND,09/05/2006 -"GEO Sense",GEO,11/29/1996 -"Geotest Marvin Test Systems Inc",GTS,02/24/1998 -"GERMANEERS GmbH",GER,12/20/2011 -"GES Singapore Pte Ltd",GES,03/15/2001 -"Getac Technology Corporation",GET,05/11/2010 -"GFMesstechnik GmbH",GFM,03/15/2001 -"GI Provision Ltd",GIP,02/08/2012 -"Global Data SA",PST,11/29/1996 -"Global Village Communication",GVL,11/29/1996 -"GMK Electronic Design GmbH",GMK,01/18/2008 -"GMM Research Inc",GMM,11/29/1996 -"GMX Inc",GMX,11/29/1996 -"GN Nettest Inc",GNN,07/30/1997 -"GOEPEL electronic GmbH",GOE,06/24/2013 -"Goldmund - Digital Audio SA",GLD,02/06/2012 -"GOLD RAIN ENTERPRISES CORP.",GRE,06/04/2003 -"Goldstar Company Ltd",GSM,11/29/1996 -Goldtouch,GTI,08/06/1997 -"Google Inc.",GGL,05/26/2010 -"GoPro, Inc.",GPR,01/15/2015 -"Granch Ltd",GRH,09/23/2002 -"Grand Junction Networks",GJN,11/29/1996 -"Grandstream Networks, Inc.",GSN,03/03/2014 -"Graphic SystemTechnology",GST,11/29/1996 -"Graphica Computer",GRA,11/29/1996 -"Graphtec Corporation",GTC,11/29/1996 -"Grass Valley Germany GmbH",TGV,06/14/2007 -"Grey Cell Systems Ltd",GCS,04/29/1997 -"Grossenbacher Systeme AG",GSY,04/19/2000 -"G-Tech Corporation",GTK,11/29/1996 -"Guillemont International",GIM,10/29/1997 -"GUNZE Limited",GZE,05/02/2005 -"Gunze Ltd",GNZ,11/29/1996 -"Guntermann & Drunck GmbH",GUD,03/10/2003 -"Guzik Technical Enterprises",GUZ,11/29/1996 -"GVC Corporation",GVC,11/29/1996 -"H.P.R. Electronics GmbH",HPR,08/29/2007 -"Hagiwara Sys-Com Company Ltd",HSC,11/29/1996 -"GW Instruments",GWI,11/29/1996 -"Haider electronics",HAE,07/05/2001 -"Haivision Systems Inc.",HAI,11/15/2007 -Halberthal,HAL,02/10/1998 -"Hall Research",HRI,05/10/2012 -"HAMAMATSU PHOTONICS K.K.",HPK,12/20/2006 -"Hampshire Company, Inc.",HTI,01/20/1999 -"Hanchang System Corporation",HAN,06/21/2003 -"HannStar Display Corp",HSD,08/11/2009 -"HannStar Display Corp",HSP,08/11/2009 -"HardCom Elektronik & Datateknik",HDC,04/14/1998 -"Harman International Industries, Inc",HII,01/09/2015 -"Harris & Jeffries Inc",HJI,11/29/1996 -"Harris Canada Inc",HWA,03/13/1998 -"Harris Corporation",HAR,12/20/2011 -"Harris Semiconductor",HRS,01/02/1997 -"Hauppauge Computer Works Inc",HCW,11/29/1996 -"Hayes Microcomputer Products Inc",HAY,11/29/1996 -"HCL America Inc",HCL,11/29/1996 -"HCL Peripherals",HCM,10/02/2001 -"HD-INFO d.o.o.",HDI,10/08/2001 -"Headplay, Inc.",HPI,04/30/2007 -"Heng Yu Technology (HK) Limited",HYT,10/23/2000 -Hercules,HRC,03/15/2001 -HERCULES,HRT,03/15/2001 -"HETEC Datensysteme GmbH",HET,02/03/2004 -"Hewlett Packard",HWP,03/15/2001 -"Hewlett Packard",HPD,05/02/1997 -"Hewlett-Packard Co.",HPC,08/10/2000 -"Hewlett-Packard Co.",HPQ,07/12/2004 -"Hexium Ltd.",HXM,04/15/2008 -"Hibino Corporation",HIB,07/09/2003 -"Highwater Designs Ltd",HWD,11/29/1996 -"Hikom Co., Ltd.",HIK,10/13/2003 -"Hilevel Technology",HIL,11/29/1996 -"HIRAKAWA HEWTECH CORP.",HHC,05/20/2008 -"Hitachi America Ltd",HIT,11/29/1996 -"Hitachi Consumer Electronics Co., Ltd",HCE,05/15/2009 -"Hitachi Information Technology Co., Ltd.",HIC,04/19/2000 -"Hitachi Ltd",HTC,11/29/1996 -"Hitachi Maxell, Ltd.",MXL,01/13/2000 -"Hitachi Micro Systems Europe Ltd",HEL,07/09/1997 -"Hitex Systementwicklung GmbH",HTX,01/30/1998 -"hmk Daten-System-Technik BmbH",HMK,09/30/1997 -"HOB Electronic GmbH",HOB,11/29/1996 -"Holoeye Photonics AG",HOL,02/02/2005 -"Holografika kft.",HDV,03/31/2005 -"Holtek Microelectronics Inc",HTK,11/29/1996 -"Home Row Inc",INC,11/29/1996 -"HON HAI PRECISON IND.CO.,LTD.",FOX,08/02/2010 -"HONKO MFG. CO., LTD.",HKA,12/01/2004 -"Hope Industrial Systems, Inc.",HIS,01/13/2014 -"Horner Electric Inc",APG,11/29/1996 -"Horsent Technology Co., Ltd.",HST,04/11/2015 -"Hosiden Corporation",HOE,08/05/1997 -"HTBLuVA Mödling",HTL,02/17/2014 -"Hualon Microelectric Corporation",HMC,11/29/1996 -"HUALONG TECHNOLOGY CO., LTD",EBT,06/15/2007 -"Hughes Network Systems",HNS,11/29/1996 -"HUMAX Co., Ltd.",HMX,02/14/2006 -"HYC CO., LTD.",HYO,04/12/2006 -"Hydis Technologies.Co.,LTD",HYD,11/22/2010 -"Hynix Semiconductor",HYV,11/29/2008 -"Hypercope Gmbh Aachen",HYC,12/01/1997 -"Hypertec Pty Ltd",HYR,11/29/1996 -"Hyphen Ltd",HYP,11/29/1996 -"I&T Telecom.",ITT,11/08/1999 -"I/OTech Inc",IOT,11/29/1996 -"IAT Germany GmbH",IAT,11/29/1996 -"IBM Brasil",IBM,11/29/1996 -"IBM Corporation",CDT,11/29/1996 -"IBP Instruments GmbH",IBP,09/23/1998 -"IBR GmbH",IBR,01/16/1998 -"IC Ensemble",ICE,09/19/1997 -"ICA Inc",ICA,05/20/2002 -"ICCC A/S",ICX,11/29/1996 -"ICD Inc",ICD,06/09/1997 -"ICET S.p.A.",ARE,05/16/1999 -"ICP Electronics, Inc./iEi Technology Corp.",ICP,09/07/2012 -ICSL,IUC,08/14/1997 -"Icuiti Corporation",XTD,06/14/2007 -"Icuiti Corporation",IWR,03/06/2007 -"Id3 Semiconductors",ISC,03/15/2001 -"IDE Associates",IDE,11/29/1996 -"IDEO Product Development",IDO,09/30/1997 -"idex displays",DEX,04/25/2002 -"IDEXX Labs",IDX,11/29/1996 -"IDK Corporation",IDK,04/16/2003 -"Idneo Technologies",IDN,07/05/2012 -IDTECH,ITS,06/17/2002 -IEE,IEE,06/21/2001 -"IGM Communi",IGM,11/29/1996 -"IINFRA Co., Ltd",IIN,05/09/2003 -"Iiyama North America",IVM,11/29/1996 -"Ikegami Tsushinki Co. Ltd.",IKE,11/14/2014 -"Ikos Systems Inc",IKS,11/29/1996 -ILC,IND,06/16/2004 -"Image Logic Corporation",ILC,11/29/1996 -"Image Stream Medical",ISM,05/27/2010 -"IMAGENICS Co., Ltd.",IMG,09/05/2006 -"IMAGEQUEST Co., Ltd",IQT,10/08/2002 -Imagraph,IME,12/04/1996 -Imagraph,IMA,11/29/1996 -"ImasDe Canarias S.A.",IMD,07/03/1997 -"IMC Networks",IMC,11/29/1996 -"Immersion Corporation",IMM,07/16/1997 -"IMP Electronics Ltd.",HUM,06/16/2004 -Impinj,IMP,08/14/2012 -"Impossible Production",IMN,08/10/2000 -"In Focus Systems Inc",IFS,11/29/1996 -"In4S Inc",ALD,12/05/1997 -INBINE.CO.LTD,IBI,11/06/2001 -"Indtek Co., Ltd.",INK,03/26/2007 -"IneoQuest Technologies, Inc",IQI,02/18/2011 -"Industrial Products Design, Inc.",IPD,07/16/1999 -"Ines GmbH",INS,11/29/1996 -"Infineon Technologies AG",IFX,04/19/2000 -"Infinite Z",IFZ,01/04/2012 -"Informatik Information Technologies",IIT,08/14/2013 -Informtech,IFT,11/29/1996 -"Infotek Communication Inc",ICI,11/29/1996 -"Infotronic America, Inc.",ITR,06/21/2001 -"Inframetrics Inc",INF,11/29/1996 -"Ingram Macrotron",VSN,08/10/2000 -"Ingram Macrotron Germany",VID,05/24/2000 -"InHand Electronics",IHE,04/20/2010 -"Initio Corporation",INI,11/29/1996 -"Inmax Technology Corporation",IMT,02/12/2003 -"Innolab Pte Ltd",INO,01/20/1999 -"InnoLux Display Corporation",INL,12/15/2004 -"InnoMedia Inc",INM,11/29/1996 -"Innotech Corporation",ILS,10/23/2000 -"Innovate Ltd",ATE,11/29/1996 -"Innovent Systems, Inc.",INN,04/19/2000 -"Innoware Inc",WII,01/30/1998 -"Inovatec S.p.A.",inu,03/15/2001 -"Inside Contactless",ICV,11/04/2010 -"Inside Out Networks",ION,12/28/1998 -"Insignia Solutions Inc",ISG,11/29/1996 -"INSIS Co., LTD.",ISR,02/12/2003 -"Institut f r angewandte Funksystemtechnik GmbH",IAF,03/20/1999 -"Integraph Corporation",ING,11/29/1996 -"Integrated Business Systems",IBC,11/29/1996 -"Integrated Device Technology, Inc.",IDP,01/27/2010 -"Integrated Tech Express Inc",ITE,11/29/1996 -"Integrated Tech Express Inc",SRC,11/29/1996 -"integrated Technology Express Inc",ITX,06/25/1997 -"Integration Associates, Inc.",IAI,03/17/2004 -"Intel Corp",ICO,08/10/2000 -"Intelligent Instrumentation",III,11/29/1996 -"Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell)",IPI,05/24/2000 -"Intelliworxx, Inc.",IWX,05/16/1999 -"Intellix Corp.",SVC,01/18/2008 -"Interaction Systems, Inc",TCH,03/20/1999 -"Interactive Computer Products Inc",PEN,01/15/1997 -"Intercom Inc",ITC,11/29/1996 -"Interdigital Sistemas de Informacao",IDS,10/28/1997 -"Interface Corporation",FBI,11/29/1996 -"Interface Solutions",ISI,11/29/1996 -"Intergate Pty Ltd",IGC,11/29/1996 -"Interlace Engineering Corporation",IEC,11/29/1996 -"Interlink Electronics",IEI,10/16/1998 -"International Datacasting Corporation",IDC,02/25/1997 -"International Display Technology",IDT,05/16/2002 -"International Integrated Systems,Inc.(IISI)",ISY,08/10/2000 -"International Microsystems Inc",IMI,11/29/1996 -"International Power Technologies",IPT,04/11/1997 -"Internet Technology Corporation",ITD,12/05/1997 -"Interphase Corporation",INP,11/29/1996 -"Interphase Corporation",INT,11/29/1996 -"Intersil Corporation",LSD,03/14/2012 -"Intersolve Technologies",IST,03/20/1999 -Inter-Tel,ITL,03/21/1997 -"Intertex Data AB",IXD,11/29/1996 -"Intervoice Inc",IVI,11/29/1996 -"Intevac Photonics Inc.",IVS,02/16/2011 -"Intracom SA",ICM,08/03/1998 -"Intrada-SDD Ltd",SDD,11/21/2007 -"IntreSource Systems Pte Ltd",ISP,08/27/1997 -"Intuitive Surgical, Inc.",SRG,02/16/2006 -"Inventec Corporation",INA,09/13/2013 -"Inventec Electronics (M) Sdn. Bhd.",INE,07/21/1998 -"Inviso, Inc.",INV,10/23/2000 -"I-O Data Device Inc",IOD,11/29/1996 -"i-O Display System",IOS,03/15/2001 -Iomega,IOM,11/29/1996 -"IP Power Technologies GmbH",IPP,12/06/2010 -"IP3 Technology Ltd.",IPQ,11/11/2013 -"IPC Corporation",IPC,11/29/1996 -"IPM Industria Politecnica Meridionale SpA",IPM,09/23/1998 -"IPS, Inc. (Intellectual Property Solutions, Inc.)",IPS,09/05/2001 -"IPWireless, Inc",IPW,03/15/2001 -"ISIC Innoscan Industrial Computers A/S",IIC,07/23/2003 -"Isolation Systems",ISL,11/29/1996 -"ISS Inc",ISS,11/29/1996 -"Itausa Export North America",ITA,11/29/1996 -"Ithaca Peripherals",IPR,07/01/1997 -"ITK Telekommunikation AG",ITK,11/29/1996 -"ITM inc.",ITM,04/24/2001 -"IT-PRO Consulting und Systemhaus GmbH",ITP,10/23/2000 -"Jace Tech Inc",JCE,11/29/1996 -"Jaeik Information & Communication Co., Ltd.",JIC,10/23/2000 -"Jan Strapko - FOTO",XFG,05/07/2001 -"Janich & Klass Computertechnik GmbH",JUK,10/08/2002 -"Janz Automationssysteme AG",JAS,11/03/2009 -"Japan Aviation Electronics Industry, Limited",JAE,03/15/2001 -"Japan Digital Laboratory Co.,Ltd.",JDL,04/19/2000 -"Japan Display Inc.",JDI,04/18/2013 -"Jaton Corporation",JAT,09/24/1997 -"JET POWER TECHNOLOGY CO., LTD.",JET,03/15/2001 -"Jetway Information Co., Ltd",JWY,09/22/2003 -"jetway security micro,inc",JTY,11/11/2009 -"Jiangsu Shinco Electronic Group Co., Ltd",SHI,08/10/2004 -"Jones Futurex Inc",JFX,11/29/1996 -"Jongshine Tech Inc",LTI,11/29/1996 -"Josef Heim KG",HKG,11/29/1996 -"JPC Technology Limited",JPC,10/23/2000 -"JS DigiTech, Inc",JSD,10/23/2000 -"JS Motorsports",JTS,12/05/1997 -Junnila,TPJ,03/15/2001 -"Jupiter Systems",JUP,09/05/2006 -"Jupiter Systems, Inc.",JSI,06/14/2007 -JVC,JVC,10/23/2000 -"JVC KENWOOD Corporation",JKC,03/08/2012 -"JWSpencer & Co.",JWS,07/16/1999 -"Kansai Electric Company Ltd",SGE,12/04/1996 -"Kaohsiung Opto Electronics Americas, Inc.",HIQ,03/14/2012 -"Karn Solutions Ltd.",KSL,05/08/2006 -Karna,KAR,02/21/2000 -"Katron Tech Inc",KTN,11/29/1996 -"Kayser-Threde GmbH",KTG,11/29/1996 -"KDDI Technology Corporation",KDT,05/22/2012 -KDE,KDE,08/14/2001 -"KDS USA",KDS,11/29/1996 -"KEISOKU GIKEN Co.,Ltd.",KGL,04/17/2012 -"Kensington Microware Ltd",KML,11/29/1996 -"Kenwood Corporation",KWD,02/22/2008 -KEPS,EPS,11/29/1996 -"Kesa Corporation",KES,11/29/1996 -"Key Tech Inc",KEY,11/29/1996 -"Key Tronic Corporation",KTK,11/29/1996 -"Keycorp Ltd",KCL,05/20/1997 -KeyView,KVX,08/13/2012 -"Kidboard Inc",KBI,04/24/1997 -"KIMIN Electronics Co., Ltd.",KME,02/15/2011 -"Kinetic Systems Corporation",KSC,11/29/1996 -"King Phoenix Company",KPC,11/29/1996 -"King Tester Corporation",KSX,07/16/1998 -"Kingston Tech Corporation",KTC,11/29/1996 -"Kionix, Inc.",KIO,12/23/2013 -"KiSS Technology A/S",KIS,06/16/2005 -"Klos Technologies, Inc.",PVP,08/10/2000 -"Kobil Systems GmbH",KBL,03/15/2001 -"Kobil Systems GmbH",KOB,03/15/2001 -"Kodiak Tech",KDK,11/29/1996 -"Kofax Image Products",KFX,11/29/1996 -"Kollmorgen Motion Technologies Group",KOL,11/29/1996 -"KOLTER ELECTRONIC",KOE,03/15/2001 -"Komatsu Forest",KFE,04/20/2010 -"Konica corporation",KNC,08/05/1997 -"Konica Technical Inc",KTI,11/29/1996 -"Kontron Electronik",TWE,11/29/1996 -"Kontron Embedded Modules GmbH",KEM,08/29/2007 -"Kontron Europe GmbH",KEU,02/20/2014 -"Korea Data Systems Co., Ltd.",KDM,12/18/2003 -"KOUZIRO Co.,Ltd.",KOU,07/27/2012 -"KOWA Company,LTD.",KOW,03/12/2008 -"Kramer Electronics Ltd. International",KMR,07/10/2013 -"Krell Industries Inc.",KRL,08/03/2004 -"Kroma Telecom",KRM,05/05/2010 -"Kroy LLC",KRY,07/16/1998 -K-Tech,KTE,03/31/2003 -"KUPA China Shenzhen Micro Technology Co., Ltd. Gold Institute",KSG,04/22/2014 -"Kurta Corporation",KUR,11/29/1996 -"Kvaser AB",KVA,01/24/1997 -"KYE Syst Corporation",KYE,11/29/1996 -"Kyocera Corporation",KYC,11/29/1996 -"Kyushu Electronics Systems Inc",KEC,01/12/1998 -"K-Zone International",KZN,06/21/2001 -"K-Zone International co. Ltd.",KZI,08/10/2000 -"L-3 Communications",LLL,05/11/2010 -"La Commande Electronique",LCE,11/29/1996 -"Labcal Technologies",LCT,11/08/1999 -"Labtec Inc",LTC,12/08/1997 -"Labway Corporation",LWC,12/04/1996 -LaCie,LAC,12/28/1998 -"Laguna Systems",LAG,11/29/1996 -"Land Computer Company Ltd",LND,11/29/1996 -"LANETCO International",LNT,05/02/2003 -"Lanier Worldwide",LWW,11/29/1996 -"Lars Haagh ApS",LHA,01/09/1997 -"LASAT Comm. A/S",LAS,11/29/1996 -"Laser Master",LMT,11/29/1996 -"Laserdyne Technologies",LDN,10/16/2013 -"Lasergraphics, Inc.",LGX,02/21/2000 -"Latitude Comm.",LCM,11/29/1996 -"Lava Computer MFG Inc",LAV,04/14/1997 -LCI,LCC,08/10/2000 -"Lectron Company Ltd",LEC,03/27/1997 -"Leda Media Products",LMP,05/11/1998 -"Legerity, Inc",LEG,01/18/2005 -"Leitch Technology International Inc.",LTV,12/09/2003 -Lenovo,LNV,07/14/2005 -"Lenovo Beijing Co. Ltd.",LIN,05/22/2012 -"Lenovo Group Limited",LEN,06/03/2005 -"Lexical Ltd",LEX,11/29/1996 -LEXICON,LCN,03/01/2005 -"Leutron Vision",PRS,11/29/1996 -"Lexmark Int'l Inc",LMI,11/29/1996 -"LG Semicom Company Ltd",LGS,11/29/1996 -LGIC,MAN,02/21/2000 -"LifeSize Communications",LSC,02/14/2006 -"Lighthouse Technologies Limited",LHT,05/04/2010 -"Lightware Visual Engineering",LWR,02/04/2009 -"Lightware, Inc",LTW,10/16/1998 -"Lightwell Company Ltd",LZX,12/02/1997 -"Likom Technology Sdn. Bhd.",LKM,04/23/1998 -"Linear Systems Ltd.",LNR,10/11/2007 -"Link Tech Inc",LNK,11/29/1996 -"Linked IP GmbH",LIP,07/19/2010 -"Lisa Draexlmaier GmbH",FGD,02/22/1999 -"Litelogic Operations Ltd",LOL,12/09/2011 -"Lite-On Communication Inc",LCI,11/29/1996 -"Lithics Silicon Technology",LIT,03/15/2001 -"Litronic Inc",LTN,02/03/1998 -"Locamation B.V.",LOC,01/09/2004 -"Loewe Opta GmbH",LOE,05/02/2005 -"Logic Ltd",LGC,04/02/1994 -"Logical Solutions",LSL,11/29/1996 -"Logicode Technology Inc",LOG,11/29/1996 -"Logitech Inc",LGI,11/29/1996 -"LogiDataTech Electronic GmbH",LDT,03/15/2001 -"Logos Design A/S",SGO,04/24/2001 -"Long Engineering Design Inc",LED,11/29/1996 -"Longshine Electronics Company",LCS,11/29/1996 -"Loughborough Sound Images",LSI,11/29/1996 -"LSI Japan Company Ltd",LSJ,11/29/1996 -"LSI Systems Inc",LSY,11/29/1996 -"LTS Scale LLC",LTS,11/15/2007 -Lubosoft,LBO,04/24/2001 -"Lucent Technologies",LUC,04/19/2000 -"Lucent Technologies",LMG,01/13/1997 -"Lucidity Technology Company Ltd",LTK,05/18/1998 -"Lumagen, Inc.",LUM,08/12/2004 -"Lung Hwa Electronics Company Ltd",LHE,06/12/1998 -Luxeon,LXN,03/15/2001 -"Luxxell Research Inc",LUX,06/09/1997 -"LVI Low Vision International AB",LVI,01/21/2011 -"LXCO Technologies AG",LXC,01/11/2012 -"MAC System Company Ltd",MAC,09/26/1997 -"Mac-Eight Co., LTD.",MEJ,01/19/2011 -"Macraigor Systems Inc",OCD,03/23/1998 -"Macrocad Development Inc.",VHI,04/19/2000 -"Macronix Inc",MXI,11/29/1996 -"Madge Networks",MDG,11/29/1996 -"Maestro Pty Ltd",MAE,12/04/1996 -"MAG InnoVision",MAG,11/29/1996 -"Magic Leap",MLP,11/14/2014 -"Magni Systems Inc",MCP,11/29/1996 -"MagTek Inc.",EKA,02/14/2006 -"Magus Data Tech",MDT,11/29/1996 -"Mainpine Limited",MPN,06/30/2007 -"Mainpine Limited",MUK,09/13/1999 -"Many CNC System Co., Ltd.",PAK,03/12/2004 -"Maple Research Inst. Company Ltd",MPL,11/29/1996 -"MARANTZ JAPAN, INC.",MJI,10/23/2000 -"Marconi Instruments Ltd",MIL,11/29/1996 -"Marconi Simulation & Ty-Coch Way Training",MRC,11/29/1996 -"Marina Communicaitons",MCR,11/29/1996 -"Mark Levinson",MLN,02/28/2005 -"Mark of the Unicorn Inc",MTU,03/21/1997 -"Marseille, Inc.",MNI,02/27/2013 -"Marshall Electronics",MBM,03/13/2006 -"Mars-Tech Corporation",MTC,11/29/1996 -"Maruko & Company Ltd",MRK,11/29/1996 -"MASPRO DENKOH Corp.",MSR,10/25/2012 -"Mass Inc.",MAS,02/25/2002 -"Matelect Ltd.",MEQ,05/30/2002 -Matrox,MTX,11/29/1996 -"Mat's Computers",MCQ,07/22/2004 -"Matsushita Communication Industrial Co., Ltd.",WPA,03/15/2001 -"Panasonic Connect Co.,Ltd.",MAT,04/01/2022 -"MaxCom Technical Inc",MTI,11/29/1996 -"MaxData Computer AG",VOB,02/21/2000 -"MaxData Computer GmbH & Co.KG",MXD,04/19/2000 -"Maxpeed Corporation",MXP,02/19/1997 -"Maxtech Corporation",MXT,11/29/1996 -"MaxVision Corporation",MXV,07/16/1999 -"Maygay Machines, Ltd",DJP,08/10/2000 -"Maynard Electronics",MAY,11/29/1996 -"MAZeT GmbH",MAZ,08/11/1998 -MBC,MBC,11/29/1996 -"McDATA Corporation",MCD,11/29/1996 -"McIntosh Laboratory Inc.",MLI,01/18/2008 -"MCM Industrial Technology GmbH",MIT,10/29/2004 -"MEC Electronics GmbH",CEM,04/19/2000 -"Medar Inc",MDR,12/11/1996 -"Media Technologies Ltd.",MTB,01/05/2009 -"Media Tek Inc.",MKC,06/14/2007 -"Media Vision Inc",MVI,11/29/1996 -"Media4 Inc",MDA,03/20/1997 -"Mediacom Technologies Pte Ltd",OWL,11/29/1996 -"Mediaedge Corporation",MEK,11/19/2013 -"MediaFire Corp.",MFR,12/28/1998 -Mediasonic,FTR,11/29/1996 -"MediaTec GmbH",MTE,12/13/1996 -"Mediatek Corporation",MDK,03/13/1997 -"Mediatrix Peripherals Inc",MPI,04/24/1997 -"Medikro Oy",MRO,09/19/1997 -"Mega System Technologies Inc",MEC,12/29/1997 -"Mega System Technologies, Inc.",MGA,12/28/1998 -"Megasoft Inc",MSK,11/29/1996 -"Megatech R & D Company",MGT,11/29/1996 -"Meld Technology",MEP,08/16/2012 -"MEN Mikroelectronik Nueruberg GmbH",MEN,05/23/1997 -"Mentor Graphics Corporation",MGC,07/30/2009 -MEPCO,RLD,03/15/2001 -MEPhI,PPD,11/27/1998 -"Merging Technologies",MRT,11/29/1996 -"Meridian Audio Ltd",MAL,02/04/2009 -"Messeltronik Dresden GmbH",MED,11/29/1996 -"MET Development Inc",MDV,11/29/1996 -"Meta Watch Ltd",MTA,08/29/2013 -"Metheus Corporation",MET,11/29/1996 -"Metricom Inc",MCM,11/29/1996 -"Metronics Inc",QCH,11/29/1996 -"Mettler Toledo",NET,11/29/1996 -"Metz-Werke GmbH & Co KG",MCE,06/30/2005 -"M-G Technology Ltd",MGL,10/29/1997 -"Micom Communications Inc",MIC,05/05/1997 -"Micomsoft Co., Ltd.",MSX,04/10/2008 -"Micro Computer Systems",MCS,11/29/1996 -"Micro Design Inc",MDI,01/20/1998 -"Micro Display Systems Inc",MDS,11/29/1996 -"Micro Firmware",MFI,12/30/1997 -"Micro Industries",MCC,04/21/2003 -"Micro Solutions, Inc.",BPD,04/19/2000 -"Micro Systemation AB",MSA,11/08/1999 -"Micro Technical Company Ltd",JMT,11/29/1996 -"Microbus PLC",MBD,08/13/2002 -Microcom,MNP,11/29/1996 -"MicroDatec GmbH",MDX,09/13/1999 -"MicroDisplay Corporation",MRD,06/14/2007 -"Microdyne Inc",MDY,12/18/1996 -"MicroField Graphics Inc",MFG,11/29/1996 -Microlab,MPJ,05/23/1997 -Microline,LAF,09/13/1999 -"Micrologica AG",MLG,10/06/1998 -"Micromed Biotecnologia Ltd",MMD,12/11/1996 -"Micromedia AG",MMA,04/24/1997 -"Micron Electronics Inc",MCN,02/20/1997 -"Micronics Computers",MCI,11/29/1996 -micronpc.com,MIP,08/10/2000 -"Micronyx Inc",MYX,11/29/1996 -"Micropix Technologies, Ltd.",MPX,10/08/2001 -"MicroSlate Inc.",MSL,05/16/1999 -Microsoft,PNP,03/05/2004 -Microsoft,MSH,11/29/1996 -Microsoft,PNG,11/29/1996 -MicroSoftWare,WBN,01/14/1998 -Microstep,MSI,11/29/1996 -Microtec,MCT,11/29/1996 -"Micro-Tech Hearing Instruments",MTH,12/15/1997 -"MICROTEK Inc.",MKT,07/14/2005 -"Microtek International Inc.",MTK,02/25/2002 -"MicroTouch Systems Inc",MSY,08/10/2000 -Microvision,MVS,02/13/2009 -"Microvitec PLC",MVD,11/29/1996 -"Microway Inc",MWY,11/29/1996 -"Midori Electronics",MDC,11/29/1996 -"Mikroforum Ring 3",SFT,11/02/2004 -"Milestone EPE",MLS,08/11/1998 -"Millennium Engineering Inc",MLM,11/29/1996 -"Millogic Ltd.",MLL,01/09/2014 -"Millson Custom Solutions Inc.",MCX,10/17/2013 -"Miltope Corporation",VTM,09/23/2009 -"Mimio – A Newell Rubbermaid Company",MIM,07/31/2012 -"MindTech Display Co. Ltd",MTD,06/14/2007 -"MindTribe Product Engineering, Inc.",FTW,02/14/2011 -"Mini Micro Methods Ltd",MNC,11/29/1996 -"Minicom Digital Signage",MIN,08/13/2010 -"MiniMan Inc",MMN,11/29/1996 -"Minnesota Mining and Manufacturing",MMF,03/15/2001 -"Miranda Technologies Inc",MRA,11/29/1996 -Miratel,MRL,10/16/1998 -"Miro Computer Prod.",MIR,11/29/1996 -"miro Displays",MID,03/20/1999 -"Mistral Solutions [P] Ltd.",MSP,09/23/1998 -"Mitec Inc",MII,11/29/1996 -"Mitel Corporation",MTL,08/01/1997 -"Mitron computer Inc",MTR,11/29/1996 -"Mitsubishi Electric Corporation",MEL,11/29/1996 -"Mitsubishi Electric Engineering Co., Ltd.",MEE,10/03/2005 -"Mitsumi Company Ltd",KMC,11/29/1996 -"MJS Designs",MJS,11/29/1996 -"MK Seiko Co., Ltd.",MKS,06/18/2013 -"M-Labs Limited",OHW,11/27/2013 -"MMS Electronics",MMS,02/24/1998 -"Modesto PC Inc",FST,02/27/1997 -MODIS,MDD,11/08/1999 -"Modular Industrial Solutions Inc",MIS,11/29/1996 -"Modular Technology",MOD,06/09/1997 -"Momentum Data Systems",MOM,01/18/2008 -"Monorail Inc",MNL,02/18/1997 -Monydata,MYA,11/29/1996 -"Moreton Bay",MBV,01/13/2000 -"Moses Corporation",MOS,11/29/1996 -"Mosgi Corporation",MSV,11/29/1996 -"Motion Computing Inc.",MCO,05/30/2002 -Motium,MTM,06/19/2012 -motorola,MSU,03/15/2001 -"Motorola Communications Israel",MCL,07/02/2002 -"Motorola Computer Group",MCG,08/14/1997 -"Motorola UDS",MOT,11/29/1996 -"Mouse Systems Corporation",MSC,11/29/1996 -"M-Pact Inc",MPC,11/29/1996 -"mps Software GmbH",MPS,11/29/1996 -"MS Telematica",MST,04/28/1997 -"MSC Vertriebs GmbH",MEX,06/04/2012 -"MSI GmbH",MSG,09/13/1999 -"M-Systems Flash Disk Pioneers",MSF,12/17/1997 -"Mtron Storage Technology Co., Ltd.",MTN,06/17/2008 -"Multi-Dimension Institute",MUD,10/23/2000 -Multimax,MMI,11/29/1996 -"Multi-Tech Systems",MTS,11/29/1996 -"Multiwave Innovation Pte Ltd",MWI,11/29/1996 -"Mutoh America Inc",MAI,09/13/1999 -mware,MWR,04/24/2001 -"Mylex Corporation",MLX,11/29/1996 -"Myriad Solutions Ltd",MYR,11/29/1996 -"Myse Technology",WYS,11/29/1996 -"N*Able Technologies Inc",NBL,04/28/1998 -"NAD Electronics",NAD,06/14/2007 -"Naitoh Densei CO., LTD.",NDK,04/12/2006 -"Najing CEC Panda FPD Technology CO. ltd",NCP,02/24/2015 -"Nakano Engineering Co.,Ltd.",NAK,07/22/2009 -"Nakayo Relecommunications, Inc.",NYC,08/10/2000 -"Nanomach Anstalt",SCS,11/29/1996 -"Nasa Ames Research Center",ADR,11/29/1996 -"National DataComm Corporaiton",NDC,11/29/1996 -"National Display Systems",NDI,08/08/2003 -"National Instruments Corporation",NIC,11/29/1996 -"National Key Lab. on ISN",NBS,07/16/1998 -"National Semiconductor Corporation",NSC,11/29/1996 -"National Semiconductor Japan Ltd",TTB,04/14/1997 -"National Transcomm. Ltd",NTL,11/29/1996 -"Nationz Technologies Inc.",ZIC,03/12/2009 -"Natural Micro System",NMS,11/29/1996 -"NaturalPoint Inc.",NAT,09/03/2010 -"Navatek Engineering Corporation",NVT,03/02/1998 -"Navico, Inc.",NME,11/28/2012 -"Navigation Corporation",NAV,02/22/1999 -"Naxos Tecnologia",NAX,12/12/1997 -"NCR Corporation",DUN,04/25/2002 -"NCR Corporation",NCC,11/29/1996 -"NCR Electronics",NCR,11/29/1996 -"NDF Special Light Products B.V.",NDF,09/18/2014 -"NDS Ltd",DMV,06/25/1997 -"NEC Corporation",NEC,05/24/2000 -"NEC CustomTechnica, Ltd.",NCT,10/23/2002 -"NEC-Mitsubishi Electric Visual Systems Corporation",NMV,02/25/2002 -"NEO TELECOM CO.,LTD.",NEO,11/08/1999 -Neomagic,NMX,11/29/1996 -"NeoTech S.R.L",NTC,11/11/1997 -"Netaccess Inc",NTX,02/07/1997 -"NetComm Ltd",NCL,11/29/1996 -"NetVision Corporation",NVC,11/29/1996 -"Network Alchemy",NAL,09/30/1997 -"Network Designers",NDL,11/29/1996 -"Network General",NGC,08/26/1997 -"Network Info Technology",NIT,11/29/1996 -"Network Peripherals Inc",NPI,11/29/1996 -"Network Security Technology Co",NST,02/22/1999 -"Networth Inc",NTW,11/29/1996 -"NeuroSky, Inc.",NSA,08/28/2013 -"NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA",NEU,03/15/2001 -"New Tech Int'l Company",NTI,11/29/1996 -"NewCom Inc",NCI,01/09/1997 -"Newisys, Inc.",NWS,10/08/2002 -"Newport Systems Solutions",NSS,11/29/1996 -Nexgen,NXG,11/29/1996 -"Nexgen Mediatech Inc.,",NEX,11/11/2003 -"Nexiq Technologies, Inc.",NXQ,10/08/2001 -"Next Level Communications",NLC,11/29/1996 -"NextCom K.K.",NXC,11/29/1996 -"NingBo Bestwinning Technology CO., Ltd",NBT,09/05/2006 -"NINGBO BOIGLE DIGITAL TECHNOLOGY CO.,LTD",BOI,11/25/2009 -"Nippon Avionics Co.,Ltd",AVI,10/23/2000 -"NIPPONDENCHI CO,.LTD",GSB,05/24/2000 -"NISSEI ELECTRIC CO.,LTD",NSI,01/13/2000 -"Nissei Electric Company",NIS,11/29/1996 -"Nits Technology Inc.",NTS,12/19/2006 -"Nixdorf Company",NCA,11/29/1996 -NNC,NNC,11/29/1996 -"Nokia Data",NDS,11/29/1996 -"Nokia Display Products",NOK,11/29/1996 -"Nokia Mobile Phones",NMP,11/29/1996 -"Norand Corporation",NOR,03/19/1997 -"Norcent Technology, Inc.",NCE,06/20/2007 -"NordicEye AB",NOE,09/23/2009 -"North Invent A/S",NOI,05/04/2010 -"Northgate Computer Systems",NCS,11/29/1996 -"Not Limited Inc",NOT,01/30/1998 -"NovaWeb Technologies Inc",NWP,06/12/1998 -"Novell Inc",NVL,11/29/1996 -"Nspire System Inc.",NSP,02/13/2007 -"N-trig Innovative Technologies, Inc.",NTR,10/03/2005 -"NTT Advanced Technology Corporation",NTT,08/19/2004 -"NU Inc.",NUI,08/29/2007 -"NU Technology, Inc.",NUG,04/16/2004 -"Number Five Software",NFS,02/22/1999 -"Nutech Marketing PTL",KNX,11/29/1996 -"NuVision US, Inc.",NVI,09/05/2006 -"Nuvoton Technology Corporation",NTN,10/09/2008 -Nvidia,NVD,11/29/1996 -N-Vision,JEN,10/23/2000 -"NXP Semiconductors bv.",NXP,06/14/2007 -"NW Computer Engineering",NWC,02/03/1997 -"Oak Tech Inc",OAK,11/29/1996 -"Oasys Technology Company",OAS,11/29/1996 -"OBJIX Multimedia Corporation",OMC,11/29/1996 -"OCTAL S.A.",PCB,02/24/1998 -"Oculus VR, Inc.",OVR,10/19/2012 -Odrac,ODR,06/21/2001 -"Office Depot, Inc.",ATV,06/13/2007 -"OKI Electric Industrial Company Ltd",OKI,11/29/1996 -"Oksori Company Ltd",OQI,11/29/1996 -"Oksori Company Ltd",OSR,11/29/1996 -Olfan,OCN,11/29/1996 -"Olicom A/S",OLC,11/29/1996 -"Olidata S.p.A.",OLD,03/13/2006 -"Olitec S.A.",OLT,11/29/1996 -"Olitec S.A.",OLV,11/29/1996 -Olivetti,OLI,11/29/1996 -"OLYMPUS CORPORATION",OLY,05/02/2005 -OmniTek,OTK,09/19/2013 -Omnitel,OMN,04/28/1998 -"Omron Corporation",OMR,11/29/1996 -"On Systems Inc",ONS,11/29/1996 -"Oneac Corporation",ONE,04/14/1998 -"ONKYO Corporation",ONK,06/16/2005 -"OnLive, Inc",ONL,09/03/2010 -"OOO Technoinvest",TIV,08/05/1997 -"Opcode Inc",OPC,11/29/1996 -"Open Connect Solutions",OCS,09/13/1999 -"OPEN Networks Ltd",ONW,04/25/2003 -"Open Stack, Inc.",OSI,07/22/2013 -"OPPO Digital, Inc.",OPP,06/19/2012 -"OPTi Inc",OPT,11/29/1996 -"Optibase Technologies",OBS,11/01/2010 -"Optical Systems Design Pty Ltd",OSD,06/03/2013 -"Option Industrial Computers",OIC,05/07/2001 -"Option International",OIN,10/23/2000 -"Option International",OIM,01/30/1997 -"OPTI-UPS Corporation",OSP,07/01/1997 -"Optivision Inc",OPV,11/29/1996 -"OPTO22, Inc.",OTT,10/06/1998 -"Optoma Corporation          ",OTM,04/20/2010 -"Optum Engineering Inc.",OEI,08/02/2010 -"Orchid Technology",OTI,11/29/1996 -"ORGA Kartensysteme GmbH",ORG,10/24/1998 -"Orion Communications Co., Ltd.",TOP,04/30/2007 -"ORION ELECTRIC CO., LTD.",ORN,01/19/2005 -"ORION ELECTRIC CO.,LTD",OEC,01/13/2000 -"OSAKA Micro Computer, Inc.",OSA,09/05/2003 -"OSR Open Systems Resources, Inc.",ORI,01/20/1999 -OSRAM,OOS,04/25/2002 -"OUK Company Ltd",OUK,11/29/1996 -outsidetheboxstuff.com,OTB,09/03/2010 -"Oxus Research S.A.",OXU,11/29/1996 -"OZ Corporation",OZC,08/07/2012 -"Pacific Avionics Corporation",PAC,11/29/1996 -"Pacific CommWare Inc",PCW,11/29/1996 -"Pacific Image Electronics Company Ltd",PIE,10/21/1997 -"Packard Bell Electronics",PBL,11/29/1996 -"Packard Bell NEC",PBN,11/29/1996 -"PACSGEAR, Inc.",PGI,08/13/2012 -"Padix Co., Inc.",QFF,09/13/1999 -"Pan Jit International Inc.",PJT,08/03/2004 -Panasonic,MDO,11/29/1996 -"Panasonic Avionics Corporation",PLF,08/13/2010 -"Panasonic Industry Company",MEI,11/29/1996 -"Panelview, Inc.",PNL,08/04/2003 -"Pantel Inc",PTL,11/29/1996 -"PAR Tech Inc.",PTA,01/26/2011 -"Parade Technologies, Ltd.",PRT,04/06/2012 -"Paradigm Advanced Research Centre",PGM,06/16/2005 -"Parallan Comp Inc",PAR,11/29/1996 -"Parallax Graphics",PLX,11/29/1996 -"Parc d'Activite des Bellevues",RCE,11/29/1996 -Parrot,POT,11/25/2014 -"Pathlight Technology Inc",PTH,11/29/1996 -"PC Xperten",PCX,02/24/1998 -PCBANK21,PCK,02/13/2007 -"PCM Systems Corporation",PCM,03/25/1997 -"PC-Tel Inc",PCT,05/02/1997 -"PD Systems International Ltd",PDS,03/20/1999 -"PDTS - Prozessdatentechnik und Systeme",PDT,02/10/1998 -"Pegatron Corporation",PEG,08/27/2013 -"PEI Electronics Inc",PEI,04/06/1998 -"Penta Studiotechnik GmbH",PVM,05/05/2010 -"pentel.co.,ltd",PCL,02/25/2002 -"Peppercon AG",PEP,04/12/2006 -"Perceptive Pixel Inc.",PPX,05/04/2010 -"Perceptive Signal Technologies",PER,05/13/1997 -PerComm,PRC,04/24/2001 -"Performance Concepts Inc.,",PCO,09/24/2002 -"Performance Technologies",IPN,02/24/2004 -"Perle Systems Limited",PSL,02/22/1999 -"Perpetual Technologies, LLC",PON,01/13/2000 -"Peter Antesberger Messtechnik",PAM,04/28/1998 -"Peus-Systems GmbH",PSD,11/29/1996 -"Philips BU Add On Card",PCA,11/29/1996 -"Philips Communication Systems",PHS,11/29/1996 -"Philips Consumer Electronics Company",PHL,11/29/1996 -"Philips Medical Systems Boeblingen GmbH",PHE,04/20/2010 -"Philips Semiconductors",PSC,11/29/1996 -"Phoenix Contact",PXC,02/27/2008 -"Phoenix Technologies, Ltd.",PNX,11/08/1999 -"Phoenixtec Power Company Ltd",PPC,05/16/1999 -"Photonics Systems Inc.",PHO,06/03/2002 -PhotoTelesis,RSC,03/16/1998 -"Phylon Communications",PHY,11/29/1996 -PicPro,PPR,10/18/2004 -"Pijnenburg Beheer N.V.",PHC,04/24/2001 -"Pioneer Computer Inc",PCI,11/29/1996 -"Pioneer Electronic Corporation",PIO,07/16/1997 -"Pitney Bowes",PBV,09/13/1999 -"Pitney Bowes",PBI,11/29/1996 -"Pixel Qi",PQI,06/24/2009 -"Pixel Vision",PVN,11/29/1996 -"PIXELA CORPORATION",PXE,11/21/2007 -"Pixie Tech Inc",PIX,11/29/1996 -"Plain Tree Systems Inc",PTS,11/29/1996 -"Planar Systems, Inc.",PNR,08/11/2003 -"PLUS Vision Corp.",PLV,07/05/2001 -"PMC Consumer Electronics Ltd",PMC,12/11/1996 -"pmns GmbH",SPR,10/08/2002 -"Point Multimedia System",PMM,06/09/1997 -"Polycom Inc.",PLY,06/19/2002 -"PolyComp (PTY) Ltd.",POL,02/14/2006 -"Polycow Productions",COW,03/15/2001 -"Portalis LC",POR,11/01/2008 -"Poso International B.V.",ARO,08/01/1997 -"POTRANS Electrical Corp.",PEC,07/16/1999 -"PowerCom Technology Company Ltd",PCC,09/02/1997 -"Powermatic Data Systems",CPX,11/29/1996 -"Practical Electronic Tools",PET,02/22/1999 -"Practical Peripherals",PPI,11/29/1996 -"Practical Solutions Pte., Ltd.",PSE,10/06/1998 -"Praim S.R.L.",PRD,11/29/1996 -"Primax Electric Ltd",PEL,11/29/1996 -"Prime Systems, Inc.",SYX,10/21/2003 -"Prime view international Co., Ltd",PVI,07/06/2009 -"Princeton Graphic Systems",PGS,11/29/1996 -"Prism, LLC",PIM,07/24/2007 -"Priva Hortimation BV",PRI,10/22/1997 -PRO/AUTOMATION,PRA,07/16/1999 -"Procomp USA Inc",PCP,11/29/1996 -"Prodea Systems Inc.",PSY,02/04/2013 -"Prodrive B.V.",PDV,01/18/2005 -Projecta,PJA,01/29/1997 -"Projectavision Inc",DHT,01/14/1998 -"Projectiondesign AS",PJD,09/23/2002 -"PROLINK Microsystems Corp.",PLM,02/25/2002 -"Pro-Log Corporation",PLC,11/29/1996 -"Promate Electronic Co., Ltd.",PMT,01/13/2003 -Prometheus,PRM,11/29/1996 -"Promise Technology Inc",PTI,01/02/1997 -"Promotion and Display Technology Ltd.",PAD,04/24/2001 -"Promotion and Display Technology Ltd.",TEL,04/24/2001 -"propagamma kommunikation",PGP,04/19/2000 -Prosum,PSM,11/29/1996 -Proteon,PRO,11/29/1996 -"Proview Global Co., Ltd",PVG,10/08/2002 -"Proxim Inc",PXM,09/19/1997 -"Proxima Corporation",PRX,11/29/1996 -"PS Technology Corporation",PTC,01/29/1997 -"Psion Dacom Plc.",PDM,11/08/1999 -"PSI-Perceptive Solutions Inc",PSI,11/29/1996 -"PT Hartono Istana Teknologi",PLT,05/05/2010 -"Pulse-Eight Ltd",PUL,09/12/2012 -"Pure Data Inc",PDR,11/29/1996 -"Purup Prepress AS",PPP,11/29/1996 -"Qingdao Haier Electronics Co., Ltd.",HRE,04/12/2006 -Q-Logic,QLC,11/29/1996 -"Qtronix Corporation",QTR,11/29/1996 -Quadram,DHQ,11/29/1996 -Quadram,QDM,11/29/1996 -"Quadrant Components Inc",QCL,04/03/1997 -"QuakeCom Company Ltd",QCC,03/23/1998 -"Qualcomm Inc",QCP,05/16/1999 -"Quanta Computer Inc",QCI,11/29/1996 -"Quanta Display Inc.",QDS,04/25/2002 -Quantum,QTM,11/29/1996 -"Quantum 3D Inc",QTD,05/23/1997 -"Quantum Data Incorporated",QDI,03/15/2001 -Quartics,QVU,11/04/2010 -"Quatographic AG",QUA,01/13/2000 -"Questech Ltd",QTH,01/13/2000 -"Questra Consulting",QUE,01/30/1998 -"Quick Corporation",QCK,11/29/1996 -"Quickflex, Inc",QFI,08/04/1998 -"Quicknet Technologies Inc",QTI,11/29/1996 -"R Squared",RSQ,11/08/1999 -R.P.T.Intergroups,RPT,11/29/1996 -"Racal Interlan Inc",RII,11/29/1996 -"Racal-Airtech Software Forge Ltd",TSF,11/29/1996 -"Racore Computer Products Inc",RAC,11/29/1996 -"Radicom Research Inc",RRI,12/02/1997 -"Radio Consult SRL",RCN,09/24/2002 -"RADIODATA GmbH",RDN,07/25/2012 -"RadioLAN Inc",RLN,11/29/1996 -"Radiospire Networks, Inc.",RSN,06/14/2007 -"Radisys Corporation",RAD,11/29/1996 -"Radius Inc",RDS,03/07/1997 -"RAFI GmbH & Co. KG",RFI,08/24/2015 -"Rainbow Displays, Inc.",RDI,09/23/1998 -"Rainbow Technologies",RNB,11/29/1996 -"Raintree Systems",RTS,10/02/2001 -"Rainy Orchard",BOB,02/21/2000 -"Rampage Systems Inc",RSI,11/29/1996 -"Rancho Tech Inc",RAN,11/29/1996 -"Rancho Tech Inc",RTI,11/29/1996 -"Rapid Tech Corporation",RSX,11/29/1996 -"Raritan Computer, Inc",RMC,11/27/1998 -"Raritan, Inc.",RAR,06/14/2007 -"RAScom Inc",RAS,11/29/1996 -"RATOC Systems, Inc.",REX,01/06/2012 -"Raylar Design, Inc.",RAY,01/13/2000 -"RC International",RCI,11/29/1996 -"Reach Technology Inc",RCH,02/09/1998 -"Reakin Technolohy Corporation",RKC,03/15/2001 -"Real D",REA,11/15/2007 -"Realtek Semiconductor Company Ltd",RTL,11/29/1996 -"Realtek Semiconductor Corp.",ALG,10/25/2002 -"Realvision Inc",RVI,11/29/1996 -ReCom,REC,05/16/1999 -"Red Wing Corporation",RWC,01/08/1998 -"Redfox Technologies Inc.",RFX,01/14/2014 -"Reflectivity, Inc.",REF,04/19/2000 -"Rehan Electronics Ltd.",REH,02/15/2012 -"Relia Technologies",RTC,11/29/1996 -"Reliance Electric Ind Corporation",REL,11/29/1996 -"Renesas Technology Corp.",REN,06/14/2007 -Rent-A-Tech,RAT,02/22/1999 -"Research Electronics Development Inc",RED,12/02/1997 -"Research Machines",RMP,11/29/1996 -"ResMed Pty Ltd",RES,02/21/2000 -"Resonance Technology, Inc.",RET,02/09/2011 -"Restek Electric Company Ltd",WTS,11/29/1996 -"Reveal Computer Prod",RVL,11/29/1996 -"Revolution Display, Inc.",REV,03/19/2014 -"RGB Spectrum",RGB,11/14/2012 -"RGB Systems, Inc. dba Extron Electronics",EXN,07/06/2008 -"RICOH COMPANY, LTD.",RIC,05/13/2010 -"RightHand Technologies",RHD,05/01/2012 -"Rios Systems Company Ltd",RIO,11/29/1996 -"Ritech Inc",RIT,04/14/1998 -"Rivulet Communications",RIV,07/19/2007 -"Robert Bosch GmbH",BSG,05/15/2014 -"Robert Gray Company",GRY,03/31/1998 -"Robertson Geologging Ltd",RGL,08/10/2000 -"Robust Electronics GmbH",ROB,01/18/2008 -"Rockwell Automation/Intecolor",RAI,03/13/1998 -"Rockwell Collins",RCO,09/10/2010 -"Rockwell Collins / Airshow Systems",ASY,12/02/2004 -"Rockwell Collins, Inc.",COL,06/14/2007 -"Rockwell International",ROK,11/29/1996 -"Rockwell Semiconductor Systems",RSS,11/29/1996 -"Rogen Tech Distribution Inc",MAX,11/29/1996 -"Rohde & Schwarz",ROS,01/20/2012 -"Rohm Co., Ltd.",ROH,06/16/2004 -"Rohm Company Ltd",RHM,05/13/1997 -"Roland Corporation",RJA,11/29/1996 -"RoomPro Technologies",RPI,07/09/2010 -"Roper International Ltd",ROP,05/16/1999 -"Roper Mobile",RMT,07/02/2010 -"Ross Video Ltd",RSV,06/11/2012 -"Royal Information",TRL,11/29/1996 -"Rozsnyó, s.r.o.",RZS,03/24/2014 -"RSI Systems Inc",RVC,04/28/1998 -"RUNCO International",RUN,04/01/2004 -"S&K Electronics",SNK,02/21/2000 -"S3 Inc",TLV,01/07/1997 -"S3 Inc",SIM,11/29/1996 -"S3 Inc",SSS,11/29/1996 -"Saab Aerotech",SAE,06/14/2007 -"Sage Inc",SAI,07/16/1997 -SAGEM,SGM,09/05/2003 -SAIT-Devlonics,SDK,11/29/1996 -"Saitek Ltd",SAK,05/16/1999 -"Salt Internatioinal Corp.",SLT,09/05/2006 -"Samsung Electric Company",SAM,11/29/1996 -"Samsung Electro-Mechanics Company Ltd",SKT,11/29/1996 -"Samsung Electronics America",STN,08/10/2000 -"Samsung Electronics America Inc",KYK,02/24/1998 -"Samsung Electronic Co.",SSE,08/10/2000 -"Samsung Electronics Company Ltd",SEM,11/29/1996 -"Samtron Displays Inc",SDI,11/29/1996 -"SANKEN ELECTRIC CO., LTD",JSK,09/13/1999 -"Sankyo Seiki Mfg.co., Ltd",SSJ,01/28/2003 -"Sanritz Automation Co.,Ltd.",SAA,02/25/2002 -"SANTAK CORP.",STK,11/27/1998 -"Santec Corporation",SOC,01/12/2015 -"Sanyo Electric Co.,Ltd.",SAN,11/08/1999 -"Sanyo Electric Company Ltd",SCD,11/29/1996 -"Sanyo Electric Company Ltd",SIB,11/29/1996 -"Sanyo Electric Company Ltd",TSC,11/29/1996 -"Sanyo Icon",ICN,11/29/1996 -"Sapience Corporation",SPN,11/29/1996 -"SAT (Societe Anonyme)",SDA,11/29/1996 -"SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.)",AVV,12/17/2002 -"SBS-or Industrial Computers GmbH",SBS,12/28/1998 -"Scan Group Ltd",SGI,11/29/1996 -"Scanport, Inc.",SCN,08/05/2002 -"SCD Tech",KFC,10/23/2002 -"Sceptre Tech Inc",SPT,11/29/1996 -Schlumberger,SMB,07/16/1999 -"Schlumberger Cards",SCH,04/28/1998 -"Schlumberger Technology Corporate",SLR,08/10/2000 -"Schneider & Koch",SKD,11/29/1996 -"Schneider Electric S.A.",MGE,11/29/1996 -"Schnick-Schnack-Systems GmbH",SLS,05/06/2009 -"SCI Systems Inc.",REM,08/10/2000 -"SCM Microsystems Inc",SCM,11/29/1996 -"Scriptel Corporation",SCP,06/14/2007 -"SDR Systems",SDR,03/15/2001 -"SDS Technologies",STY,11/08/1999 -"SDX Business Systems Ltd",SDX,11/29/1996 -"Seanix Technology Inc",NIX,04/09/2007 -"Seanix Technology Inc.",SEA,02/24/1998 -Sedlbauer,SAG,11/29/1996 -"SeeColor Corporation",SEE,11/29/1996 -"SeeCubic B.V.",SCB,11/02/2012 -"SeeReal Technologies GmbH",SRT,06/27/2005 -"Seiko Epson Corporation",SEC,11/29/1996 -"Seiko Instruments Information Devices Inc",SID,12/16/1996 -"Seiko Instruments USA Inc",SIU,11/29/1996 -"Seitz & Associates Inc",SEI,01/30/1998 -"Sejin Electron Inc",SJE,08/20/1997 -"SELEX GALILEO",SXG,10/01/2012 -"Semtech Corporation",STH,11/30/2001 -"SendTek Corporation",SET,11/08/1999 -"Senseboard Technologies AB",SBT,09/03/2002 -Sencore,SEN,05/23/1997 -"Sentelic Corporation",STU,07/27/2012 -"SEOS Ltd",SEO,02/20/2003 -"Sentronic International Corp.",SNC,10/23/2000 -"SEP Eletronica Ltda.",SEP,05/07/2001 -"Sequent Computer Systems Inc",SQT,11/29/1996 -"Session Control LLC",SES,09/03/2010 -Setred,SRD,09/05/2006 -"SEVIT Co., Ltd.",SVT,06/25/2002 -SGEG,SVA,02/21/2000 -"Seyeon Tech Company Ltd",SYT,12/02/1997 -"SGS Thomson Microelectronics",STM,11/11/1997 -"Shadow Systems",OYO,11/29/1996 -"Shanghai Bell Telephone Equip Mfg Co",SBC,04/30/1998 -"Shanghai Guowei Science and Technology Co., Ltd.",SGW,01/28/2011 -"SHANGHAI SVA-DAV ELECTRONICS CO., LTD",XQU,07/24/2003 -"Sharedware Ltd",SWL,08/11/1998 -"Shark Multimedia Inc",SMM,11/29/1996 -"SharkTec A/S",DFK,02/14/2006 -"Sharp Corporation",SHP,11/29/1996 -"SHARP TAKAYA ELECTRONIC INDUSTRY CO.,LTD.",SXT,06/24/2010 -"Shenzhen ChuangZhiCheng Technology Co., Ltd.",CZC,10/23/2013 -"Shenzhen Inet Mobile Internet Technology Co., LTD",IXN,11/04/2014 -"Shenzhen MTC Co., Ltd",SZM,08/09/2013 -"Shenzhen Ramos Digital Technology Co., Ltd",RMS,10/29/2014 -"Shenzhen South-Top Computer Co., Ltd.",SSL,12/06/2013 -"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",AZH,09/17/2013 -"Shenzhen Zhuona Technology Co., Ltd.",XYE,10/01/2013 -"Shenzhen ZhuoYi HengTong Computer Technology Limited",HTR,12/13/2013 -"Shenzhen Zowee Technology Co., LTD",ZWE,05/26/2015 -"Sherwood Digital Electronics Corporation",SDE,11/29/1996 -"ShibaSoku Co., Ltd.",SHC,05/26/2005 -"Shin Ho Tech",SHT,11/29/1996 -"Shlumberger Ltd",SLB,11/29/1996 -"Shuttle Tech",SAT,11/29/1996 -"Sichuan Changhong Electric CO, LTD.",CHG,02/26/2003 -"Sichuang Changhong Corporation",CHO,11/30/2001 -Siemens,SIE,11/29/1996 -"Siemens AG",SDT,02/14/2006 -"SIEMENS AG",SIA,03/15/2001 -"Siemens Microdesign GmbH",SNI,11/29/1996 -"Siemens Nixdorf Info Systems",SNP,11/29/1996 -"Sierra Semiconductor Inc",SSC,11/29/1996 -"Sierra Wireless Inc.",SWI,07/10/2003 -"Sigma Designs Inc",SIG,11/29/1996 -"Sigma Designs, Inc.",SGD,02/14/2006 -"Sigmacom Co., Ltd.",SCL,04/25/2002 -"SigmaTel Inc",STL,03/03/1997 -Signet,DXS,10/23/2000 -"SII Ido-Tsushin Inc",STE,04/03/1997 -"Silcom Manufacturing Tech Inc",SMT,11/29/1996 -"Silex technology, Inc.",SXD,03/12/2009 -"Silicom Multimedia Systems Inc",SMS,12/04/1996 -"Silicon Graphics Inc",SGX,11/29/1996 -"Silicon Image, Inc.",SII,01/13/2000 -"Silicon Integrated Systems Corporation",SIS,11/29/1996 -"Silicon Laboratories, Inc",SIL,07/16/1998 -"Silicon Library Inc.",SLH,11/01/2008 -"Silicon Optix Corporation",SOI,07/28/2005 -"Silitek Corporation",SLK,07/16/1997 -"SIM2 Multimedia S.P.A.",SPU,09/05/2002 -"Simple Computing",SMP,11/29/1996 -"Simplex Time Recorder Co.",SPX,03/15/2001 -"Singular Technology Co., Ltd.",SIN,11/08/1999 -"SINOSUN TECHNOLOGY CO., LTD",SNO,06/27/2005 -"Sirius Technologies Pty Ltd",SIR,03/13/1998 -"sisel muhendislik",FUN,04/25/2002 -"SITECSYSTEM CO., LTD.",STS,03/16/2005 -Sitintel,SIT,11/29/1996 -"SKYDATA S.P.A.",SKY,09/19/1997 -"Smart Card Technology",SCT,08/10/2000 -"SMART Modular Technologies",SMA,04/04/1997 -"Smart Silicon Systems Pty Ltd",SPL,08/10/2000 -"Smart Tech Inc",STI,11/29/1996 -"SMART Technologies Inc.",SBI,06/14/2007 -"SMK CORPORATION",SMK,02/21/2000 -"Snell & Wilcox",SNW,04/25/2002 -"SOBO VISION",MVM,06/14/2007 -"Socionext Inc.",SCX,05/14/2015 -"Sodeman Lancom Inc",LAN,11/29/1996 -"SODIFF E&T CO., Ltd.",SDF,06/01/2007 -"Soft & Hardware development Goldammer GmbH",SHG,11/29/1996 -"Softbed - Consulting & Development Ltd",SBD,12/23/1997 -"Software Café",SWC,11/29/1996 -"Software Technologies Group,Inc.",SWT,11/29/2008 -"Solitron Technologies Inc",SOL,11/29/1996 -"Solomon Technology Corporation",SLM,01/16/1998 -SolutionInside,SXL,05/08/2001 -"SOMELEC Z.I. Du Vert Galanta",ONX,11/29/1996 -Sonitronix,HON,02/03/2011 -"Sonix Comm. Ltd",SNX,11/29/1996 -Sony,SNY,11/29/1996 -Sony,SON,11/29/1996 -"Sony Ericsson Mobile Communications Inc.",SER,04/16/2004 -"SORCUS Computer GmbH",SCO,01/13/2000 -"Sorcus Computer GmbH",SOR,11/29/1996 -"SORD Computer Corporation",SCC,11/29/1996 -"Sotec Company Ltd",SOT,05/21/1997 -"South Mountain Technologies, LTD",FRS,02/14/2006 -"SOYO Group, Inc",SOY,12/18/2006 -"SPACE-I Co., Ltd.",SPI,05/11/2005 -"SpaceLabs Medical Inc",SMI,11/29/1996 -"SPEA Software AG",SPE,11/29/1996 -SpeakerCraft,SPK,04/20/2010 -Specialix,SLX,11/29/1996 -"Spectragraphics Corporation",SGC,11/29/1996 -"Spectrum Signal Proecessing Inc",SSP,11/29/1996 -"SR-Systems e.K.",SRS,11/19/2012 -"S-S Technology Inc",SSI,11/29/1996 -"ST Electronics Systems Assembly Pte Ltd",STA,12/28/1998 -"STAC Electronics",STC,11/29/1996 -"Standard Microsystems Corporation",SMC,11/29/1996 -"Star Paging Telecom Tech (Shenzhen) Co. Ltd.",STT,09/23/1998 -"Starflight Electronics",STF,05/23/1997 -"Stargate Technology",SGT,11/29/1996 -StarLeaf,SLF,11/01/2010 -"Starlight Networks Inc",STR,11/29/1996 -"Starwin Inc.",STW,04/24/2001 -Static,SWS,05/16/1999 -"STB Systems Inc",STB,11/29/1996 -"STD Computer Inc",STD,11/29/1996 -"StereoGraphics Corp.",STG,10/02/2001 -ST-Ericsson,STX,12/09/2011 -STMicroelectronics,SMO,06/14/2007 -"Stollmann E+V GmbH",STO,03/27/1997 -"Stores Automated Systems Inc",SAS,03/19/1997 -"Storm Technology",EZP,10/17/1996 -"StreamPlay Ltd",STP,02/04/2009 -"Stryker Communications",SYK,10/10/2005 -"Subspace Comm. Inc",SUB,11/29/1996 -"Sumitomo Metal Industries, Ltd.",SML,09/13/1999 -"Summagraphics Corporation",SUM,11/29/1996 -"Sun Corporation",SCE,11/29/1996 -"Sun Electronics Corporation",SUN,11/29/1996 -"Sun Microsystems",SVI,01/13/2003 -"SUNNY ELEKTRONIK",SNN,11/14/2014 -"SunRiver Data System",SDS,11/29/1996 -"Super Gate Technology Company Ltd",SGL,12/30/1997 -"SuperNet Inc",SNT,04/23/1998 -"Supra Corporation",SUP,11/29/1996 -"Surenam Computer Corporation",SUR,11/29/1996 -"Surf Communication Solutions Ltd",SRF,03/23/1998 -"SVD Computer",SVD,04/14/1998 -SVSI,SVS,08/09/2008 -"SY Electronics Ltd",SYE,09/20/2010 -"Sylvania Computer Products",SYL,06/12/1998 -"Symbios Logic Inc",SLI,11/29/1996 -"Symbol Technologies",ISA,06/02/1997 -"Symicron Computer Communications Ltd.",SYM,11/29/1996 -"Synaptics Inc",SYN,11/29/1996 -"Synopsys Inc",SPS,11/29/1996 -Syntax-Brillian,SXB,05/08/2006 -"SYPRO Co Ltd",SYP,11/27/1998 -"Sysgration Ltd",SYS,04/28/1997 -"Syslogic Datentechnik AG",SLC,01/20/1999 -"Sysmate Company",SME,09/02/1997 -"Sysmate Corporation",SIC,05/05/1997 -Sysmic,SYC,11/29/1996 -"Systec Computer GmbH",SGZ,10/02/1997 -"System Craft",SCI,11/29/1996 -"system elektronik GmbH",SEB,04/19/2000 -"Systeme Lauer GmbH&Co KG",SLA,03/20/1999 -"Systems Enhancement",UPS,11/29/1996 -"SystemSoft Corporation",SST,11/29/1996 -"Systran Corporation",SCR,11/29/1996 -"SYVAX Inc",SYV,11/29/1996 -"T+A elektroakustik GmbH",TUA,01/05/2011 -"Taicom Data Systems Co., Ltd.",TCD,10/08/2001 -"Taicom International Inc",TMR,11/29/1996 -"Taiko Electric Works.LTD",TKC,03/15/2001 -"Taiwan Video & Monitor Corporation",TVM,11/29/1996 -"Takahata Electronics Co.,Ltd.",KTD,07/22/2009 -"Tamura Seisakusyo Ltd",TAM,07/17/1997 -Tandberg,TAA,10/21/2003 -"Tandberg Data Display AS",TDD,11/29/1996 -"Tandem Computer Europe Inc",TDM,11/29/1996 -"Tandon Corporation",TCC,11/29/1996 -"Tandy Electronics",TDY,11/29/1996 -"Taskit Rechnertechnik GmbH",TAS,12/15/1997 -"Tatung Company of America Inc",TCS,11/29/1996 -"Tatung UK Ltd",VIB,07/16/1999 -"Taugagreining hf",NRV,11/29/1996 -"Taxan (Europe) Ltd",TAX,03/13/1997 -"TDK USA Corporation",PMD,11/29/1996 -TDT,TDT,11/29/1996 -"TDVision Systems, Inc.",TDV,01/18/2008 -"TEAC System Corporation",TEA,11/29/1996 -"TEC CORPORATION",CET,07/16/1998 -"TEAC America Inc",TCJ,11/29/1996 -"Tech Source Inc.",TEZ,08/14/2013 -"Techmedia Computer Systems Corporation",TMC,02/10/1998 -"Technical Concepts Ltd",TCL,11/29/1996 -"Technical Illusions Inc.",TIL,02/14/2014 -"TechniSat Digital GmbH",TSD,07/14/2005 -"Technology Nexus Secure Open Systems AB",NXS,05/08/1998 -"Technology Power Enterprises Inc",TPE,11/29/1996 -"TechnoTrend Systemtechnik GmbH",TTS,11/29/1996 -"Tecmar Inc",TEC,11/29/1996 -"Tecnetics (PTY) Ltd",TCN,11/29/1996 -"TECNIMAGEN SA",TNM,05/02/2005 -Tecnovision,TVD,03/13/2006 -"Tectona SoftSolutions (P) Ltd.,",RXT,06/02/2004 -"Teknor Microsystem Inc",TKN,11/29/1996 -"Tekram Technology Company Ltd",TRM,11/29/1996 -"Tektronix Inc",TEK,05/16/1999 -"TEKWorx Limited",TWX,12/24/2009 -"Telecom Technology Centre Co. Ltd.",TCT,07/16/1999 -"Telecommunications Techniques Corporation",TTC,11/29/1996 -"Teleforce.,co,ltd",TLF,11/19/2012 -"Teleliaison Inc",TAT,04/29/1997 -"Telelink AG",TLK,09/01/1998 -"Teleprocessing Systeme GmbH",TPS,01/24/1997 -"Teles AG",TAG,11/29/1996 -"Teleste Educational OY",TLS,11/29/1996 -"TeleVideo Systems",TSI,11/29/1996 -"Telia ProSoft AB",PFT,09/13/1999 -Telindus,TLD,11/29/1996 -"Telxon Corporation",TLX,11/29/1996 -"Tennyson Tech Pty Ltd",TNY,11/29/1996 -Teradici,TDC,10/11/2007 -"TerraTec Electronic GmbH",TER,03/21/1997 -"Texas Insturments",TXN,11/29/1996 -"Texas Microsystem",TMI,11/29/1996 -"Textron Defense System",TXT,11/29/1996 -"The Concept Keyboard Company Ltd",CKC,06/02/1997 -"The Linux Foundation",LNX,04/04/2014 -"The Moving Pixel Company",PXL,11/24/2003 -"The NTI Group",ITN,11/29/1996 -"The OPEN Group",TOG,09/13/1999 -"The Panda Project",PAN,11/29/1996 -"The Phoenix Research Group Inc",PRG,09/19/1997 -"The Software Group Ltd",TSG,11/29/1996 -"Thermotrex Corporation",TMX,11/29/1996 -Thinklogical,TLL,06/01/2015 -"Thomas-Conrad Corporation",TCO,11/29/1996 -"Thomson Consumer Electronics",TCR,08/20/1998 -"Thruput Ltd",TPT,06/16/2010 -"Thundercom Holdings Sdn. Bhd.",THN,03/21/1997 -"Tidewater Association",TWA,11/29/1996 -"Time Management, Inc.",TMM,03/20/1999 -"TimeKeeping Systems, Inc.",TKS,08/31/1998 -"Times (Shanghai) Computer Co., Ltd.",TPD,12/12/2013 -"TIPTEL AG",TIP,02/24/1998 -"Tixi.Com GmbH",TIX,10/16/1998 -"T-Metrics Inc.",TMT,02/21/2000 -"TNC Industrial Company Ltd",TNC,02/27/1998 -"Todos Data System AB",TAB,08/20/1997 -"TOEI Electronics Co., Ltd.",TOE,10/02/2001 -TONNA,TON,03/14/2012 -"Top Victory Electronics ( Fujian ) Company Ltd",TPV,05/16/1999 -"TOPRE CORPORATION",TPK,02/13/2009 -"Topro Technology Inc",TPR,05/08/1998 -"Topson Technology Co., Ltd.",TTA,09/23/1998 -"TORNADO Company",SFM,04/15/1997 -"Torus Systems Ltd",TGS,11/29/1996 -"Torus Systems Ltd",TRS,11/29/1996 -"Toshiba America Info Systems Inc",TAI,11/29/1996 -"Toshiba America Info Systems Inc",TSB,11/29/1996 -"Dynabook Inc.",TOS,11/29/1996 -"Toshiba Corporation",TTP,07/07/2015 -"Toshiba Global Commerce Solutions, Inc.",TGC,06/26/2012 -"Toshiba Matsushita Display Technology Co., Ltd",LCD,05/24/2000 -"TOSHIBA PERSONAL COMPUTER SYSTEM CORPRATION",PCS,06/22/2010 -"TOSHIBA TELI CORPORATION",TLI,01/18/2008 -"Totoku Electric Company Ltd",TTK,11/29/1996 -"Tottori Sanyo Electric",TSE,11/29/1996 -"Tottori SANYO Electric Co., Ltd.",TSL,11/06/2001 -"Touch Panel Systems Corporation",TPC,09/02/1997 -"TouchKo, Inc.",TKO,01/12/2006 -"Touchstone Technology",TOU,05/07/2001 -TouchSystems,TSY,01/18/2008 -"TOWITOKO electronics GmbH",TWK,04/14/1998 -"Transtex SA",CSB,03/15/2001 -"Transtream Inc",TST,04/29/1997 -TRANSVIDEO,TSV,05/04/2010 -Tremetrics,TRE,04/24/1997 -"Tremon Enterprises Company Ltd",RDM,11/29/1996 -"Trenton Terminals Inc",TTI,11/29/1996 -"Trex Enterprises",TRX,02/21/2000 -"Tribe Computer Works Inc",OZO,11/29/1996 -"Tricord Systems",TRI,11/29/1996 -"Tri-Data Systems Inc",TDS,11/29/1996 -"TRIDELITY Display Solutions GmbH",TTY,07/19/2010 -"Trident Microsystem Inc",TRD,11/29/1996 -"Trident Microsystems Ltd",TMS,07/15/2002 -"TriGem Computer Inc",TGI,11/29/1996 -"TriGem Computer,Inc.",TGM,07/05/2001 -"Trigem KinfoComm",TIC,02/26/2003 -"Trioc AB",TRC,01/13/2000 -"Triple S Engineering Inc",TBB,09/26/1997 -"Tritec Electronic AG",TRT,01/11/2012 -"TriTech Microelectronics International",TRA,01/24/1997 -"Triumph Board a.s.",TRB,09/27/2013 -"Trivisio Prototyping GmbH",TRV,11/18/2011 -"Trixel Ltd",TXL,08/10/2000 -"Trtheim Technology",MKV,03/17/1997 -Truevision,TVI,11/29/1996 -"TTE, Inc.",TTE,01/18/2005 -"Tulip Computers Int'l B.V.",TCI,11/29/1996 -"Turbo Communication, Inc",TBC,09/01/1998 -"Turtle Beach System",TBS,11/29/1996 -"Tut Systems",TUT,08/19/1997 -"TV Interactive Corporation",TVR,11/29/1996 -"TV One Ltd",TVO,09/02/2008 -"TV1 GmbH",TVV,02/06/2012 -"TVS Electronics Limited",TVS,05/20/2008 -"Twinhead International Corporation",TWH,11/29/1996 -"Tyan Computer Corporation",TYN,11/29/1996 -"U. S. Electronics Inc.",USE,10/28/2013 -"U.S. Naval Research Lab",NRL,11/29/1996 -"U.S. Navy",TSP,10/17/2002 -"U.S. Digital Corporation",USD,11/29/1996 -"U.S. Robotics Inc",USR,11/29/1996 -"Ubinetics Ltd.",UBL,05/23/2002 -"Ueda Japan Radio Co., Ltd.",UJR,07/09/2003 -"UFO Systems Inc",UFO,11/29/1996 -"Ultima Associates Pte Ltd",UAS,01/02/1997 -"Ultima Electronics Corporation",UEC,09/01/1998 -"Ultra Network Tech",ULT,11/29/1996 -"Umezawa Giken Co.,Ltd",UMG,04/10/2008 -"Ungermann-Bass Inc",UBI,11/29/1996 -Unicate,UNY,07/21/1998 -"Uniden Corporation",UDN,10/18/2004 -"Uniform Industrial Corporation",UIC,11/29/1996 -"Uniform Industry Corp.",UNI,11/06/2001 -UNIGRAF-USA,UFG,10/09/2008 -"Unisys Corporation",UNB,11/29/1996 -"Unisys Corporation",UNC,11/29/1996 -"Unisys Corporation",UNM,11/29/1996 -"Unisys Corporation",UNO,11/29/1996 -"Unisys Corporation",UNS,11/29/1996 -"Unisys Corporation",UNT,11/29/1996 -"Unisys DSD",UNA,11/29/1996 -"Uni-Take Int'l Inc.",WKH,06/17/2002 -"United Microelectr Corporation",UMC,11/29/1996 -Unitop,UNP,11/06/2001 -"Universal Electronics Inc",UEI,08/20/1997 -"Universal Empowering Technologies",UET,09/26/1997 -"Universal Multimedia",UMM,10/08/2001 -"Universal Scientific Industrial Co., Ltd.",USI,11/04/2003 -"University College",JGD,11/29/1996 -"Uniwill Computer Corp.",UWC,04/16/2004 -"Up to Date Tech",UTD,11/29/1996 -UPPI,UPP,04/14/1998 -"Ups Manufactoring s.r.l.",RUP,03/15/2001 -"USC Information Sciences Institute",ASD,04/08/1997 -"Utimaco Safeware AG",USA,05/04/1998 -"Vaddio, LLC",VAD,11/30/2012 -Vadem,VDM,11/29/1996 -"VAIO Corporation",VAI,04/18/2014 -"Valence Computing Corporation",VAL,11/29/1996 -"Valley Board Ltda",VBT,03/15/2001 -"ValleyBoard Ltda.",VLB,04/05/1998 -"Valve Corporation",VLV,03/06/2013 -"VanErum Group",ITI,10/01/2013 -"Varian Australia Pty Ltd",VAR,04/19/2000 -"VATIV Technologies",VTV,04/12/2006 -"VBrick Systems Inc.",VBR,08/19/2009 -VCONEX,VCX,06/15/2005 -"VDC Display Systems",VDC,04/29/2009 -"Vector Informatik GmbH",VEC,09/10/1997 -"Vector Magnetics, LLC",VCM,04/12/2006 -Vektrex,VEK,12/13/1996 -"VeriFone Inc",VFI,05/29/1998 -"Vermont Microsystems",VMI,11/29/1996 -"Vestax Corporation",VTX,02/14/2012 -"Vestel Elektronik Sanayi ve Ticaret A. S.",VES,09/19/1997 -"Via Mons Ltd.",VIM,08/29/2012 -"VIA Tech Inc",VIA,11/29/1996 -"Victor Company of Japan, Limited",VCJ,02/06/2009 -"Victor Data Systems",VDA,05/24/2000 -"Victron B.V.",VIC,11/29/1996 -"Video & Display Oriented Corporation",VDO,11/29/1996 -"Video Computer S.p.A.",URD,02/24/1998 -"Video International Inc.",JWD,02/21/2000 -"Video Products Inc",VPI,05/04/2010 -"VideoLan Technologies",VLT,10/17/1997 -VideoServer,VSI,06/25/1997 -"Videotechnik Breithaupt",VTB,07/23/2013 -"VIDEOTRON CORP.",VTN,05/04/2010 -"Vidisys GmbH & Company",VDS,11/29/1996 -"Viditec, Inc.",VDT,11/08/1999 -"ViewSonic Corporation",VSC,11/29/1996 -"Viewteck Co., Ltd.",VTK,10/08/2001 -"Viking Connectors",VIK,11/29/1996 -"Vinca Corporation",VNC,11/29/1996 -"Vinci Labs",NHT,03/03/2006 -"Vine Micros Limited",VML,06/16/2004 -"Vine Micros Ltd",VIN,04/19/2000 -"Virtual Computer Corporation",VCC,11/29/1996 -"Virtual Resources Corporation",VRC,11/29/1996 -"Vision Quest",VQ@,10/26/2009 -"Vision Systems GmbH",VSP,11/29/1996 -Visioneer,VIS,11/29/1996 -"Visitech AS",VIT,09/05/2006 -"Vislink International Ltd",VLK,08/27/2012 -"VistaCom Inc",VCI,11/29/1996 -"Visual Interface, Inc",VIR,11/27/1998 -"Vivid Technology Pte Ltd",VTL,11/29/1996 -"VIZIO, Inc",VIZ,06/06/2012 -"VLSI Tech Inc",VTI,11/29/1996 -"VMware Inc.,",VMW,10/18/2011 -"Voice Technologies Group Inc",VTG,04/24/1997 -"Vortex Computersysteme GmbH",GDT,11/29/1996 -"VPixx Technologies Inc.",VPX,12/05/2013 -"VRmagic Holding AG",VRM,04/12/2013 -"V-Star Electronics Inc.",VSR,02/21/2000 -"VTech Computers Ltd",VTS,11/29/1996 -"VTel Corporation",VTC,11/29/1996 -"Vutrix (UK) Ltd",VUT,07/22/2003 -"Vweb Corp.",VWB,03/12/2004 -"Wacom Tech",WAC,11/29/1996 -"Wallis Hamilton Industries",JPW,07/16/1999 -"Wanlida Group Co., Ltd.",MLT,04/24/2014 -"Wave Access",WAL,12/13/1996 -"Wave Systems",AWS,11/29/1996 -"Wave Systems Corporation",WVM,12/05/1997 -Wavephore,WAV,11/29/1996 -"Way2Call Communications",SEL,03/20/1997 -"WB Systemtechnik GmbH",WBS,09/08/1997 -W-DEV,WEL ,11/01/2010 -"Wearnes Peripherals International (Pte) Ltd",WPI,03/31/1998 -"Wearnes Thakral Pte",WTK,11/29/1996 -"WebGear Inc",WEB,01/30/1998 -"Westermo Teleindustri AB",WMO,01/13/2000 -"Western Digital",WDC,11/29/1996 -"Westinghouse Digital Electronics",WDE,05/23/2003 -"WEY Design AG",WEY,10/18/2004 -"Whistle Communications",WHI,10/24/1998 -"Wildfire Communications Inc",WLD,02/13/1997 -"WillNet Inc.",WNI,04/19/2000 -"Winbond Electronics Corporation",WEC,11/29/1996 -"Diebold Nixdorf Systems GmbH",WNX,09/20/2004 -"Winmate Communication Inc",WMT,03/15/2001 -"Winnov L.P.",WNV,03/07/1997 -"WiNRADiO Communications",WRC,09/11/1997 -"Wintop Technology Inc",WIN,12/29/1996 -"Wipotec Wiege- und Positioniersysteme GmbH",WWP,04/08/2014 -"WIPRO Information Technology Ltd",WIL,11/29/1996 -"Wipro Infotech",WIP,01/09/2004 -"Wireless And Smart Products Inc.",WSP,03/20/1999 -"Wisecom Inc",WCI,11/29/1996 -"Wistron Corporation",WST,09/03/2010 -"Wolfson Microelectronics Ltd",WML,07/30/1997 -"WolfVision GmbH",WVV,09/18/2012 -"Woodwind Communications Systems Inc",WCS,11/29/1996 -"Wooyoung Image & Information Co.,Ltd.",WYT,01/18/2008 -"WorkStation Tech",WTI,11/29/1996 -"World Wide Video, Inc.",WWV,10/24/1998 -"Woxter Technology Co. Ltd",WXT,09/03/2010 -"X-10 (USA) Inc",XTN,02/24/1997 -"X2E GmbH",XTE,09/23/2009 -"XAC Automation Corp",XAC,02/22/1999 -"XDM Ltd.",XDM,11/22/2010 -"Xedia Corporation",MAD,11/29/1996 -"Xilinx, Inc.",XLX,08/01/2007 -"Xinex Networks Inc",XIN,02/07/1997 -"Xiotech Corporation",XIO,05/29/1998 -"Xircom Inc",XRC,11/29/1996 -"Xitel Pty ltd",XIT,11/29/1996 -"Xirocm Inc",XIR,11/29/1996 -"XN Technologies, Inc.",XNT,07/14/2003 -XOCECO,UHB,11/27/1998 -"XORO ELECTRONICS (CHENGDU) LIMITED",XRO,05/23/2005 -"XS Technologies Inc",XST,01/20/1998 -"Xscreen AS",XSN,02/14/2006 -XSYS,XSY,04/23/1998 -"Yamaha Corporation",YMH,11/29/1996 -"Xycotec Computer GmbH",XYC,09/03/2002 -"Yasuhiko Shirai Melco Inc",BUF,11/29/1996 -"Y-E Data Inc",YED,11/29/1996 -"Yokogawa Electric Corporation",YHQ,11/29/1996 -"Ypoaz Systems Inc",TPZ,11/29/1996 -"Z Microsystems",ZMZ,08/10/2005 -"Z3 Technology",ZTT,12/14/2010 -"Zalman Tech Co., Ltd.",ZMT,05/07/2007 -"Zandar Technologies plc",ZAN,12/03/2003 -"ZeeVee, Inc.",ZAZ,01/18/2008 -"Zebra Technologies International, LLC",ZBR,09/15/2003 -"Zefiro Acoustics",ZAX,11/29/1996 -"ZeitControl cardsystems GmbH",ZCT,01/20/1999 -"ZENIC Inc.",ZEN,04/17/2015 -"Zenith Data Systems",ZDS,11/29/1996 -"Zenith Data Systems",ZGT,11/29/1996 -"Zenith Data Systems",ZSE,11/29/1996 -"Zetinet Inc",ZNI,11/29/1996 -"Zhejiang Tianle Digital Electric Co., Ltd.",TLE,01/17/2014 -"Zhong Shan City Richsound Electronic Industrial Ltd.",RSR,01/27/2015 -"Znyx Adv. Systems",ZNX,11/29/1996 -"Zoom Telephonics Inc",ZTI,11/29/1996 -"Zoran Corporation",ZRN,03/31/2005 -"Zowie Intertainment, Inc",ZOW,02/22/1999 -"ZT Group Int'l Inc.",ZTM,06/14/2007 -"ZTE Corporation",ZTE,09/03/2010 -"Zuniq Data Corporation",SIX,11/29/1996 -"Zydacron Inc",ZYD,04/10/1997 -"ZyDAS Technology Corporation",ZTC,05/24/2000 -"Zypcom Inc",ZYP,03/19/1997 -"Zytex Computers",ZYT,11/29/1996 -"Zytor Communications",HPA,07/02/2010 -"Alpha Electronics Company",AEJ,11/29/1996 -BOE,BOE,12/02/2004 -"Chaplet Systems Inc",FIR,11/29/1996 -"Chenming Mold Ind. Corp.",CMG,11/14/2003 -"coolux GmbH",COO,09/30/2010 -"Data General Corporation",DGC,11/29/1996 -Exabyte,EXA,11/29/1996 -"Herolab GmbH",HRL,03/17/1998 -"Hitachi Computer Products Inc",HCP,11/29/1996 -"Integrated Circuit Systems",ICS,11/29/1996 -Irdata,IRD,04/24/2001 -"Jewell Instruments, LLC",JWL,06/21/2001 -"MultiQ Products AB",MQP,03/20/1999 -"Ncast Corporation",NAC,02/14/2006 -"ODME Inc.",ODM,09/23/1998 -Photomatrix,PMX,11/29/1996 -"Quantum Solutions, Inc.",QSI,01/13/2000 -"Red Hat, Inc.",RHT,02/17/2011 -Zyxel,ZYX,11/29/1996 -"Carrera Computer Inc",JAZ,01/01/1994 -"Chunghwa Picture Tubes, LTD",CGA,01/01/1994 -"eMicro Corporation",EMC,01/01/1994 -"Hisense Electric Co., Ltd.",HEC,01/01/1994 -PanaScope,PNS,01/01/1994 -"SpinCore Technologies, Inc",SPC,01/01/1994 -"Sensics, Inc.",SVR,08/27/2015 -"IAdea Corporation",IAD,09/10/2015 -"Express Industrial, Ltd.",ELU,09/10/2015 -"Hewlett Packard Enterprise",HPE,09/22/2015 -"Klipsch Group, Inc",KGI,09/22/2015 -"Tek Gear",TKG,10/16/2015 -"HangZhou ZMCHIVIN",ZMC,10/16/2015 -"HTC Corportation",HVR,10/16/2015 -"Zebax Technologies",ZBX,10/16/2015 -"Guangzhou Shirui Electronics Co., Ltd.",SWO,10/16/2015 -"Picturall Ltd.",PIC,11/13/2015 -"Guangzhou Teclast Information Technology Limited",SKM,11/18/2015 -"GreenArrays, Inc.",GAC,11/18/2015 -"Thales Avionics",TAV,11/18/2015 -"Explorer Inc.",EXR,11/18/2015 -"Avegant Corporation",AVG,12/02/2015 -"MicroImage Video Systems",MIV,12/08/2015 -"ASUSTek COMPUTER INC",AUS,12/21/2015 -"Synthetel Corporation",STQ,12/21/2015 -"HP Inc.",HPN,12/21/2015 -"MicroTechnica Co.,Ltd.",MTJ,01/04/2016 -"Gechic Corporation",GEC,01/04/2016 -"MPL AG, Elektronik-Unternehmen",MEU,01/15/2016 -"Display Solution AG",DSA,02/03/2016 -"UEFI Forum",PRP,02/03/2016 -"Taitex Corporation",TTX,02/03/2016 -"EchoStar Corporation",ECH,02/26/2016 -"TCL Corporation",TOL,03/30/2016 -"ADDER TECHNOLOGY LTD",ADZ,03/30/2016 -"HKC OVERSEAS LIMITED",HKC,03/30/2016 -"KEYENCE CORPORATION",KYN,03/30/2016 -"TETRADYNE CO., LTD.",TET,04/27/2016 -"Abaco Systems, Inc.",ABS,04/27/2016 -"Meta Company",MVN,05/25/2016 -"Eizo Rugged Solutions",ERS,05/25/2016 -"VersaLogic Corporation",VLC,05/25/2016 -"CYPRESS SEMICONDUCTOR CORPORATION",CYP,05/25/2016 -"MILDEF AB",MDF,06/23/2016 -"FOVE INC",FOV,07/01/2016 -INNES,NES,07/01/2016 -"Hoffmann + Krippner GmbH",HUK,07/01/2016 -"Axell Corporation",AXE,08/03/2016 -UltiMachine,UMT,08/11/2016 -"TPK Holding Co., Ltd",KPT,08/16/2016 -"AAEON Technology Inc.",AAN,09/01/2016 -"Six15 Technologies",TDG,09/14/2016 -"Inlife-Handnet Co., Ltd.",IVR,01/19/2017 -"VR Technology Holdings Limited",DSJ,01/19/2017 -"Pimax Tech. CO., LTD",PVR,02/07/2017 -"Total Vision LTD",TVL,02/07/2017 -"Shanghai Lexiang Technology Limited",DPN,02/07/2017 -"Black Box Corporation",BBX,02/28/2017 -"TRAPEZE GROUP",TRP,02/28/2017 -"Pabian Embedded Systems",PMS,02/28/2017 -"Televic Conference",TCF,02/28/2017 -"Shanghai Chai Ming Huang Info&Tech Co, Ltd",HYL,02/28/2017 -"Techlogix Networx",TLN,02/28/2017 -"G2TOUCH KOREA",GGT,05/25/2017 -"MediCapture, Inc.",MVR,05/25/2017 -"HOYA Corporation PENTAX Lifecare Division",PNT,05/25/2017 -"christmann informationstechnik + medien GmbH & Co. KG",CHR,05/25/2017 -Tencent,TEN,06/20/2017 -"VRstudios, Inc.",VRS,06/22/2017 -"Extreme Engineering Solutions, Inc.",XES,06/22/2017 -NewTek,NTK,06/22/2017 -"BlueBox Video Limited",BBV,06/22/2017 -"Televés, S.A.",TEV,06/22/2017 -"Avatron Software Inc.",AVS,08/23/2017 -"Positivo Tecnologia S.A.",POS,09/01/2017 -"VRgineers, Inc.",VRG,09/07/2017 -"Noritake Itron Corporation",NRI,11/13/2017 -"Matrix Orbital Corporation",MOC,11/13/2017 -"Elegant Invention",EIN,03/29/2018 -"Immersive Audio Technologies France",IMF,03/29/2018 -"Lightspace Technologies",LSP,03/29/2018 -"PixelNext Inc",PXN,03/29/2018 -"VRSHOW Technology Limited",TSW,03/29/2018 -"SONOVE GmbH",SNV,03/29/2018 -"Silex Inside",SXI,03/29/2018 -"Huawei Technologies Co., Inc.",HWV,04/25/2018 -"Varjo Technologies",VRT,11/17/2017 -"Japan E.M.Solutions Co., Ltd.",JEM,05/24/2018 -"QD Laser, Inc.",QDL,05/31/2018 -"VADATECH INC",VAT,07/09/2018 -"Medicaroid Corporation",MCJ,08/20/2018 -"Razer Taiwan Co. Ltd.",RZR,08/20/2018 -"GIGA-BYTE TECHNOLOGY CO., LTD.",GBT,09/05/2018 -"Kontron GmbH",KOM,09/05/2018 -"Convergent Engineering, Inc.",CIE,09/05/2018 -"WyreStorm Technologies LLC",WYR,09/05/2018 -"Astro HQ LLC",AHQ,09/05/2018 -"QSC, LLC",QSC,01/18/2019 -"Dimension Engineering LLC",DMN,02/06/2019 -"Shenzhen Dlodlo Technologies Co., Ltd.",DLO,04/29/2019 -"LENOVO BEIJING CO. LTD.",VLM,05/21/2019 -"Cammegh Limited",CRW,06/18/2019 -"Beihai Century Joint Innovation Technology Co.,Ltd",LHC,09/10/2019 -"Findex, Inc.",FDX,10/22/2019 -"Express Luck, Inc.",ELD,10/22/2019 -"LLC SKTB “SKIT”",SKI,10/22/2019 -"WOLF Advanced Technology",WLF,10/22/2019 -"BILD INNOVATIVE TECHNOLOGY LLC",BLD,10/22/2019 -"MIMO Monitors",MMT,10/22/2019 -Icron,ICR,10/22/2019 -"TECNART CO.,LTD.",PIS,10/22/2019 -"Moxa Inc.",MHQ,10/22/2019 -"Disguise Technologies",DSG,10/22/2019 -"Comark LLC",CMK,07/15/2020 -"Megapixel Visual Realty",MPV,07/15/2020 -Skyworth,SKW,07/15/2020 -"Meta View, Inc.",CFR,07/15/2020 -MILCOTS,MLC,07/15/2020 -"NZXT (PNP same EDID)_",NXT,07/15/2020 -"Unicompute Technology Co., Ltd.",UTC,10/19/2020 -"TECHNOGYM S.p.A.",TGW,01/08/2021 -"Clover Electronics",CLR,02/02/2021 -"Kyokko Communication System Co., Ltd.",KTS,02/18/2021 -"Terumo Corporation",TMO,02/02/2021 -"Micro-Star Int'l Co., Ltd.",CND,02/17/2021 -"Newline Interactive Inc.",NWL,12/03/2020 -"CORSAIR MEMORY Inc.",CRM,02/05/2021 -aviica,VAV,06/01/2021 -Monoprice.Inc,DMG,06/04/2021 -"Shenzhen KTC Technology Group",SKG,06/01/2021 -"Truly Semiconductors Ltd.",TLY,06/01/2021 -"Hitevision Group",HHT,03/08/2021 -"DLOGIC Ltd.",DLM,06/10/2021 -"Fun Technology Innovation INC.",FUL,06/10/2021 -"Guangxi Century Innovation Display Electronics Co., Ltd",IOC,06/10/2021 -"ICC Intelligent Platforms GmbH",EMR,06/10/2021 -"New H3C Technology Co., Ltd.",NHC,06/10/2021 -"Seco S.p.A.",SCG,06/10/2021 -"Silent Power Electronics GmbH",LCP,06/10/2021 -"NAFASAE INDIA Pvt. Ltd",NAF,06/18/2021 -"Pico Technology Inc.",PIR,06/18/2021 -"Life is Style Inc.",LIS,06/18/2021 -"Hansung Co., Ltd",HSN,06/18/2021 -"Hubei Century Joint Innovation Technology Co.Ltd",TTR,06/18/2021 -"Zake IP Holdings LLC (3B tech)",VIO,06/18/2021 -"PreSonus Audio Electronics",PAE,06/24/2021 -"C&T Solution Inc.",MXM,07/21/2021 -VARCem,VCE,07/21/2021 -"Nextorage Corporation",NXR,09/21/2021 -"Netvio Ltd.",NVO,09/21/2021 -"Beijing Guochengwantong Information Technology Co., Ltd.",STV,09/21/2021 -"Kopin Corporation",KOP,10/01/2021 -"Anker Innovations Limited",AKR,12/10/2021 -"SAMPO CORPORATION",SPO,12/10/2021 -"Shiftall Inc.",SFL,12/31/2021 -AudioControl,AUD,12/31/2021 -"Schneider Consumer Group",SCA,02/08/2022 -"NOLO CO., LTD.",NVR,04/08/2022 -"Riedel Communications Canada Inc.",RDL,04/08/2022 -"arpara Technology Co., Ltd.",IMX,04/08/2022 -Nreal,MRG,04/08/2022 -"Venetex Corporation",VNX,04/08/2022 -G.VISION,GVS,06/17/2022 -"Galaxy Microsystems Ltd.",GXL,06/17/2022 -"OZO Co.Ltd",OZD,06/17/2022 -"GoUp Co.,Ltd",GUP,06/24/2022 -"Eizo Technologies GmbH",ETG,06/24/2022 -"Steelseries ApS",SSG,09/23/2022 -"Weidmuller Interface GmbH & Co. KG",WMI,09/23/2022 -"Bigscreen, Inc.",BIG,09/23/2022 -"Jiangxi Jinghao Optical Co., Ltd.",OFI,09/23/2022 -"EverPro Technologies Company Limited",EVP,09/30/2022 -"NewCoSemi (Beijing) Technology CO.,Ltd.",NCV,09/30/2022 -"LG Display",LGD,09/30/2022 -"Tianma Microelectronics Ltd.",TMA,10/20/2022 -"Printronix LLC",PTX,10/20/2022 -Colorlight,KLT,10/20/2022 -"Beck GmbH & Co. Elektronik Bauelemente KG",BCK,10/20/2022 -"Shenzhen Soogeen Electronics Co., LTD.",SGN,11/29/2022 -"Emotiva Audio Corp. ",EAC,02/02/2023 -Delem,DLD,04/05/2023 -"UNIONMAN TECHNOLOGY CO., LTD",JLK,04/19/2023 -"Samsung Display Corp.",SDC,04/25/2023 -"KOGAN AUSTRALIA PTY LTD",KGN,05/09/2023 -"Brainlab AG",BRL,05/16/2023 -"Lincoln Technology Solutions",LNC,05/24/2023 -"Norxe AS",NXE,06/07/2023 -"Avocor Technologies USA, Inc",ATU,06/07/2023 -"HONOR Device Co., Ltd.",HNM,06/07/2023 -VITEC,VVI,06/12/2023 -"Dixon Technologies (India) Limited",DXN,07/31/2023 -"China Star Optoelectronics Technology Co., Ltd",CSW,08/02/2023 -RODE,OMG,08/03/2023 -AVARRO,RRO,08/07/2023 -"InfoVision Optoelectronics (Kunshan) Co.,Ltd China",IVO,09/01/2023 -"Moore Threads Virtual Display",MTT,09/11/2023 -"Lumens Digital Optics Inc.",LMS,10/04/2023 -"LUMINO Licht Elektronik GmbH",LLT,11/07/2023 -"Reonel Oy",RNL,01/04/2024 -DemoPad Software Ltd,DEM,01/04/2024 -"TeamViewer Germany GmbH",TMV,01/04/2024 -"Pixio USA",PXO,02/14/2024 -"ELARABY COMPANY FOR ENGINEERING INDUSTRIES",EEI,02/14/2024 -"Maxnerva Technology Services Limited",MNS,02/14/2024 -"Somnium Space Ltd.",SMN,02/29/2024 -"Raspberry PI",RPL,05/07/2024 -"DEIF A/S",DEF,05/10/2024 -"Moka International limited",MOK,05/23/2024 -"Shure Inc.",SHU,06/13/2024 -"Indicates an identity defined by CTS/DID Standards other than EDID",CID,06/28/2024 -"Daten Tecnologia",DTM,06/15/2024 -"LABAU Technology Corp.",LBC,08/05/2024 -"Xiaomi Corporation",XMI,08/05/2024 -"Airdrop Gaming LLC",ADG,09/03/2024 -"Ugreen Group Ltd.",UGR,01/28/2025 -"Barnfind Technologies",BFT,01/28/2025 \ No newline at end of file +"21ST CENTURY ENTERTAINMENT",BUT,2002-04-25 +"2-Tel B.V",TTL,1999-03-20 +"3Com Corporation",TCM,1996-11-29 +"3D Perception",TDP,2002-05-16 +3M,VSD,1998-10-16 +"3NOD Digital Technology Co. Ltd.",NOD,2014-12-11 +"A D S Exports",NGS,1998-07-16 +"A Plus Info Corporation",API,1996-11-29 +"A&R Cambridge Ltd.",ACG,2007-06-13 +"A/Vaux Electronics",AVX,2012-08-29 +"A+V Link",APV,2010-01-27 +"Aashima Technology B.V.",TRU,1998-05-08 +"Aava Mobile Oy",AAM,2013-08-13 +"ABBAHOME INC.",ABA,1999-11-08 +"Abeam Tech Ltd.",MEG,1996-11-29 +"Ably-Tech Corporation",ATC,1996-11-29 +"AboCom System Inc.",ABC,1997-03-28 +"ACC Microelectronics",WTC,1996-11-29 +"Access Works Comm Inc",AWC,1996-11-29 +"Acco UK Ltd.",PKA,2003-05-12 +"Accton Technology Corporation",ACC,1996-11-29 +Acculogic,ACU,1996-11-29 +"AccuScene Corporation Ltd",ASL,2007-06-13 +"Ace CAD Enterprise Company Ltd",ANT,1996-11-29 +"Acer Inc",CHE,1996-11-29 +"Acer Labs",ALI,1996-11-29 +"Acer Netxus Inc",ANX,1996-11-29 +"Acer Technologies",ACR,1996-11-29 +Acksys,ACK,1996-11-29 +"Acnhor Datacomm",ADC,1996-11-29 +Acon,CAL,1996-11-29 +"Acrolink Inc",ALK,1997-03-12 +"Acroloop Motion Control Systems Inc",ACM,1998-03-26 +"ACT Labs Ltd",LAB,1997-09-02 +"Actek Engineering Pty Ltd",ACE,1996-11-29 +"Actiontec Electric Inc",AEI,1996-11-29 +"ActivCard S.A",ACV,1998-05-08 +"Aculab Ltd",ACB,1996-11-29 +"Acutec Ltd.",ALM,1999-11-08 +"AD electronics",GLE,2000-04-19 +"Ad Lib MultiMedia Inc",ADM,1998-04-23 +"Adaptec Inc",ADP,1996-11-29 +"Adax Inc",ADX,1996-11-29 +ADC-Centre,RSH,1999-11-08 +"Add Value Enterpises (Asia) Pte Ltd",AVE,1999-01-10 +"Addi-Data GmbH",ADA,1996-11-29 +"ADI Systems Inc",ADI,1996-11-29 +"ADPM Synthesis sas",DPM,2000-08-10 +"Adrienne Electronics Corporation",AXB,1997-10-07 +Adtek,ADT,1996-11-29 +"Adtek System Science Company Ltd",ADK,1996-11-29 +"ADTI Media, Inc",FLE,2009-09-15 +"Adtran Inc",AND,1996-11-29 +"Advan Int'l Corporation",AGM,1998-05-26 +"Advance Computer Corporation",AVN,2010-06-10 +"Advanced Digital Systems",MSM,1996-11-29 +"Advanced Electronic Designs, Inc.",AED,2004-07-12 +"Advanced Engineering",RJS,1998-06-25 +"Advanced Gravis",GRV,1996-11-29 +"Advanced Integ. Research Inc",AIR,1996-11-29 +"Advanced Logic",ALR,1996-11-29 +"Advanced Micro Devices Inc",ADV,1996-11-29 +"Advanced Micro Peripherals Ltd",EVE,2011-11-18 +"Advanced Optics Electronics, Inc.",AOE,2004-04-20 +"Advanced Peripheral Devices Inc",ADD,1996-11-29 +"Advanced Research Technology",ABV,1997-01-16 +"Advanced Signal Processing Technologies",PSA,1999-09-13 +"Advantech Co., Ltd.",AHC,2007-06-13 +"Aerodata Holdings Ltd",ADH,1997-11-11 +"Aetas Peripheral International",AEP,1999-11-08 +"Aethra Telecomunicazioni S.r.l.",AET,1996-12-13 +"Agentur Chairos",CHS,2001-03-15 +"Agilent Technologies",AGT,2001-10-08 +"Ahead Systems",ASI,1996-11-29 +"AIMS Lab Inc",AIM,1998-03-13 +"Airlib, Inc",AYR,2000-02-21 +"Aironet Wireless Communications, Inc",AWL,1998-08-11 +"Aiwa Company Ltd",AIW,1996-11-29 +"AJA Video Systems, Inc.",AJA,2007-10-11 +"AKAMI Electric Co.,Ltd",AKE,2010-09-03 +"Akebia Ltd",AKB,1996-11-29 +"AKIA Corporation",AKI,1998-12-23 +"AL Systems",ALH,1999-01-20 +"Alacron Inc",ALA,1996-11-29 +"Alana Technologies",ALN,2000-01-13 +Alcatel,AOT,2001-11-06 +"Alcatel Bell",ABE,1996-11-29 +Aldebbaron,ADB,2001-03-15 +"Alenco BV",ALE,2014-05-20 +"ALEXON Co.,Ltd.",ALX,1999-09-13 +"Alfa Inc",AFA,1996-11-29 +"Algolith Inc.",ALO,2005-05-02 +"AlgolTek, Inc.",AGO,2013-10-23 +"Alien Internet Services",AIS,2001-06-21 +"Allen Bradley Company",ABD,1996-11-29 +"Alliance Semiconductor Corporation",ALL,1996-11-29 +"Allied Telesis KK",ATI,1996-11-29 +"Allied Telesyn International (Asia) Pte Ltd",ATA,1997-11-10 +"Allied Telesyn Int'l",ATK,1996-11-29 +"Allion Computer Inc.",ACO,2000-10-23 +"Alpha Data",XAD,2009-10-08 +"Alpha Telecom Inc",ATD,1997-09-26 +"Alpha-Top Corporation",ATP,1996-12-04 +"AlphaView LCD",ALV,2008-11-01 +"ALPS ALPINE CO., LTD.",APE,2013-01-22 +"ALPS ALPINE CO., LTD.",ALP,1996-11-29 +"ALPS ALPINE CO., LTD.",AUI,1996-11-29 +"Alta Research Corporation",ARC,1996-11-29 +"Altec Corporation",ALC,1998-08-04 +"Altec Lansing",ALJ,2000-01-13 +"ALTINEX, INC.",AIX,2001-04-24 +"Altmann Industrieelektronik",AIE,1996-11-29 +"Altos Computer Systems",ACS,1996-11-29 +"Altos India Ltd",AIL,1996-11-29 +"Alvedon Computers Ltd",CNC,1998-11-06 +"Ambient Technologies, Inc.",AMB,1999-05-16 +Altra,ALT,1996-11-29 +"Amdek Corporation",AMD,1996-11-29 +"America OnLine",AOL,1996-11-29 +"American Biometric Company",YOW,1999-05-16 +"American Express",AXP,1999-07-16 +"American Magnetics",AXI,2001-03-15 +"American Megatrends Inc",AMI,1996-11-29 +"American Nuclear Systems Inc",MCA,1997-02-12 +"American Power Conversion",CNB,2001-03-15 +"American Power Conversion",APC,1996-11-29 +"Amimon LTD.",AMN,2007-06-13 +"Amino Technologies PLC and Amino Communications Limited",AMO,2011-12-09 +"AMiT Ltd",AKL,1997-12-02 +"AMP Inc",AMP,1996-11-29 +"Amptron International Inc.",AII,2000-05-24 +"AMT International Industry",AMT,1996-11-29 +"AmTRAN Technology Co., Ltd.",AMR,2013-06-10 +"AMX LLC",AMX,2008-07-06 +Anakron,ANA,1999-11-08 +"Analog & Digital Devices Tel. Inc",ADN,1997-03-14 +"Analog Devices Inc",ADS,1996-11-29 +"Analog Way SAS",ANW,2014-01-22 +"Analogix Semiconductor, Inc",ANL,2005-10-10 +"Anchor Bay Technologies, Inc.",ABT,2006-02-14 +"Anatek Electronics Inc.",AAE,2004-05-25 +"Ancor Communications Inc",ACI,1996-11-29 +Ancot,ANC,1996-11-29 +"Anderson Multimedia Communications (HK) Limited",AML,2003-01-03 +"Andrew Network Production",ANP,1996-11-29 +"Anigma Inc",ANI,1996-11-29 +"Anko Electronic Company Ltd",ANK,1998-03-24 +"Ann Arbor Technologies",AAT,2001-04-24 +"an-najah university",BBB,2001-03-15 +"Anorad Corporation",ANO,2000-01-13 +"ANR Ltd",ANR,1996-11-29 +"Ansel Communication Company",ANS,1996-11-29 +"Antex Electronics Corporation",AEC,1996-11-29 +"AOpen Inc.",AOA,2001-11-06 +"AP Designs Ltd",APX,1997-12-08 +"Apache Micro Peripherals Inc",DNG,1997-11-11 +"Aplicom Oy",APL,2005-05-02 +"Appian Tech Inc",APN,1996-11-29 +"Apple Computer Inc",APP,1996-11-29 +AppliAdata,APD,1996-11-29 +"Applied Creative Technology",ACT,1996-11-29 +"Applied Memory Tech",APM,1996-11-29 +"Apricot Computers",ACL,1996-11-29 +"Aprilia s.p.a.",APR,1999-02-22 +"ArchiTek Corporation",ATJ,2014-01-22 +"Archtek Telecom Corporation",ACH,1997-01-15 +"Arcus Technology Ltd",ATL,1996-11-29 +"AREC Inc.",ARD,2013-07-08 +"Arescom Inc",ARS,1996-11-29 +Argolis,AGL,2001-03-15 +"Argosy Research Inc",ARI,1997-02-24 +"Argus Electronics Co., LTD",ARG,2004-06-04 +"Ariel Corporation",ACA,1996-12-13 +Arima,ARM,2004-04-07 +"Arithmos, Inc.",ADE,1999-07-16 +"Ark Logic Inc",ARK,1996-11-29 +"Arlotto Comnet Inc",ARL,1997-04-29 +"ARMSTEL, Inc.",AMS ,2011-02-25 +"Arnos Insturments & Computer Systems",AIC,1996-11-29 +"ARRIS Group, Inc.",ARR,2015-01-27 +"ART s.r.l.",IMB,2012-01-27 +"Artish Graphics Inc",AGI,1996-11-29 +Arvanics,NPA,2015-03-05 +"Asahi Kasei Microsystems Company Ltd",AKM,1996-11-29 +"Asante Tech Inc",ASN,1996-11-29 +"Ascom Business Systems",HER,1999-01-20 +"Ascom Strategic Technology Unit",ASC,1996-11-29 +"ASEM S.p.A.",ASM,2001-03-15 +"ASEM S.p.A.",AEM,1996-11-29 +"AseV Display Labs",ASE,1998-10-16 +"Ashton Bentley Concepts",ASH,2013-09-20 +"Asia Microelectronic Development Inc",AMA,1997-09-24 +"Ask A/S",ASK,1996-11-29 +"Askey Computer Corporation",DYN,1997-07-22 +"ASP Microelectronics Ltd",ASP,1996-11-29 +"Askey Computer Corporation",AKY,1997-04-02 +"Aspen Tech Inc",ACP,1996-11-29 +"AST Research Inc",AST,1996-11-29 +"Astec Inc",JAC,1996-11-29 +"ASTRA Security Products Ltd",ADL,1997-07-30 +"ASTRO DESIGN, INC.",ATO,2003-06-06 +"Asuscom Network Inc",ASU,1996-11-29 +AT&T,ATT,1996-11-29 +"AT&T Global Info Solutions",GIS,1996-11-29 +"AT&T Microelectronics",HSM,1996-11-29 +"AT&T Microelectronics",TME,1996-11-29 +"AT&T Paradyne",PDN,1996-11-29 +"Atelier Vision Corporation",AVJ,2015-02-24 +"Athena Informatica S.R.L.",ATH,1997-01-29 +"Athena Smartcard Solutions Ltd.",ATN,1999-09-13 +"Athenix Corporation",ATX,1996-11-29 +"ATI Tech Inc",BUJ,1996-11-29 +Atlantis,CFG,1996-11-29 +"ATM Ltd",ATM,1996-11-29 +"Atom Komplex Prylad",AKP,2000-10-23 +"Attachmate Corporation",AMC,1996-11-29 +"Attero Tech, LLC",FWA,2010-04-20 +"Audio Processing Technology Ltd",APT,1997-03-18 +AudioScience,ASX,1996-11-29 +"August Home, Inc.",AUG,2014-06-11 +"Auravision Corporation",AVC,1996-11-29 +"Aureal Semiconductor",AUR,1996-11-29 +"Autologic Inc",APS,1996-11-29 +"automated computer control systems",CLT,1999-09-13 +"Autotime Corporation",AUT,2001-10-08 +"Auvidea GmbH",AUV,2014-04-21 +"Avalue Technology Inc.",AVL,2011-11-18 +"Avance Logic Inc",ALS,1996-11-29 +"Avaya Communication",AVA,2001-03-15 +Avencall,AEN,2012-01-27 +"AVer Information Inc.",AVR,2010-05-07 +"Avid Electronics Corporation",AVD,1996-11-29 +"AVM GmbH",AVM,1996-11-29 +"Avolites Ltd",AAA,2012-02-17 +"Avocent Corporation",AVO,2000-10-23 +"Avtek (Electronics) Pty Ltd",AVT,1996-11-29 +"AWETA BV",ACD,1998-01-20 +Axel,AXL,1996-11-29 +"AXIOMTEK CO., LTD.",AXC,2005-05-02 +"Axonic Labs LLC",AXO,2012-06-21 +"Axtend Technologies Inc",AXT,1997-12-01 +"Axxon Computer Corporation",AXX,1996-11-29 +"AXYZ Automation Services, Inc",AXY,1998-08-11 +"Aydin Displays",AYD,2007-06-13 +"AZ Middelheim - Radiotherapy",AZM,2003-11-14 +"Aztech Systems Ltd",AZT,1996-11-29 +B&Bh,BBH,2003-01-17 +"B.& V. s.r.l.",SMR,1997-03-21 +"B.F. Engineering Corporation",BFE,1996-11-29 +"B.U.G., Inc.",BUG,2011-08-30 +"Bang & Olufsen",BNO,2003-05-16 +"Banksia Tech Pty Ltd",BNK,1996-11-29 +Banyan,BAN,1996-11-29 +BARC,BRC,2000-08-10 +"Barco Display Systems",BDS,1999-09-13 +"Barco GmbH",BCD,2011-03-07 +"Barco Graphics N.V",BGB,1996-11-29 +"Barco, N.V.",BPS,2000-09-12 +"Barco, N.V.",DDS,2000-10-23 +"Baug & Olufsen",BEO,1996-11-29 +"Beaver Computer Corporaton",BCC,1996-11-29 +"Beckhoff Automation",BEC,2002-04-25 +"Beckworth Enterprises Inc",BEI,1997-07-16 +"Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd.",AGC,2001-06-21 +"Beijing AnHeng SecoTech Information Technology Co., Ltd.",AHS,2015-03-24 +"Beijing ANTVR Technology Co., Ltd.",ANV,2015-08-24 +"Beijing Northern Radiantelecom Co.",NRT,1999-03-20 +"Beko Elektronik A.S.",BEK,2005-06-15 +"Beltronic Industrieelektronik GmbH",BEL,2006-09-05 +"Benson Medical Instruments Company",BMI,1996-12-04 +"B&R Industrial Automation GmbH",BUR,1996-11-29 +"Best Buy",INZ,2004-06-04 +"Best Buy",VPR,2002-05-16 +"Best Power",BPU,1996-11-29 +"Biamp Systems Corporation",BIA,2015-05-14 +"BICC Data Networks Ltd",ICC,1996-11-29 +"Big Island Communications",BIC,1997-05-13 +"Billion Electric Company Ltd",BIL,1996-12-11 +"BioLink Technologies",BLN,2000-08-10 +"BioLink Technologies International, Inc.",BIO,2000-05-24 +"BIOMED Lab",BML,1997-05-22 +"Biomedical Systems Laboratory",BSL,1997-10-16 +BIOMEDISYS,BMS,2000-05-24 +"Biometric Access Corporation",BAC,1998-05-19 +"BioTao Ltd",BTO,2012-03-21 +"Bit 3 Computer",BIT,1996-11-29 +"Bit 3 Computer",BTC,1996-11-29 +"Bitfield Oy",BTF,1996-11-29 +"BitHeadz, Inc.",BHZ,2003-09-29 +"Bitworks Inc.",BWK,2003-07-10 +"Blackmagic Design",BMD,2012-09-13 +"Blonder Tongue Labs, Inc.",BDR,2008-09-16 +"Bloomberg L.P.",BLP,2008-09-16 +"Boca Research Inc",ZZZ,1997-02-13 +"Boca Research Inc",BRI,1996-11-29 +"BodySound Technologies, Inc.",BST,2008-03-12 +"Boeckeler Instruments Inc",BII,1996-10-17 +"Booria CAD/CAM systems",BCS,2005-05-11 +BOS,BOS,1997-07-03 +"Bose Corporation",BSE,2006-09-05 +"Boulder Nonlinear Systems",BNS,2008-03-12 +"Braemac Pty Ltd",BRA,2010-11-18 +"Braemar Inc",BRM,1997-10-07 +"Brahler ICS",BDO,1998-06-04 +"Brain Boxes Limited",BBL,2001-10-02 +"Bridge Information Co., Ltd",BRG,1998-08-11 +"BRIGHTSIGN, LLC",BSN,2012-02-28 +"Brilliant Technology",BTE,1996-11-29 +"Broadata Communications Inc.",BCI,2013-11-19 +Broadcom,BCM,2004-04-01 +"BROTHER INDUSTRIES,LTD.",BRO,2000-02-21 +"BTC Korea Co., Ltd",NFC,2002-02-25 +"Budzetron Inc",BGT,1996-11-29 +Bull,BUL,1998-02-03 +"Bull AB",BNE,1998-10-06 +Busicom,BLI,1998-08-11 +"BusTech Inc",BTI,1996-11-29 +BusTek,BUS,1996-11-29 +"Butterfly Communications",FLY,1997-05-05 +"Buxco Electronics",BXE,1996-11-29 +"byd:sign corporation",BYD,2008-04-10 +"C3PO S.L.",XMM,1998-03-03 +"CA & F Elettronica",CAC,1999-05-16 +"Cabletime Ltd",CBT,2010-05-04 +"Cabletron System Inc",CSI,1996-11-29 +Cache,CCI,1996-11-29 +CalComp,CAG,1996-11-29 +CalComp,CDP,1996-11-29 +"Calibre UK Ltd",CUK,2005-09-15 +"California Institute of Technology",CSO,1999-03-20 +"Cambridge Audio",CAM,2008-08-09 +"Cambridge Electronic Design Ltd",CED,1996-11-29 +"Cambridge Research Systems Ltd",CMR,2002-04-25 +"Canon Inc",CNN,1996-11-29 +"Canon Inc.",CAI,2001-11-06 +"Canonical Ltd.",UBU,2013-05-24 +"Canopus Company Ltd",CAN,1996-11-29 +"Capella Microsystems Inc.",CPM,2012-05-09 +"Capetronic USA Inc",CCP,1996-11-29 +"Capstone Visua lProduct Development",DJE,2008-10-09 +"Cardinal Company Ltd",CAR,1996-11-29 +"Cardinal Technical Inc",CRD,1996-11-29 +CardLogix,CLX,2001-03-15 +"Carina System Co., Ltd.",CKJ,2010-09-03 +"Carl Zeiss AG",CZE,2009-06-03 +"CASIO COMPUTER CO.,LTD",CAS,1998-10-06 +"Castles Automation Co., Ltd",CAA,2000-01-13 +"Cavium Networks, Inc",CAV,2011-02-02 +"C-C-C Group Plc",FVX,1998-05-04 +CCL/ITRI,CCL,1997-03-31 +"C-Cube Microsystems",CCC,1996-11-29 +C-DAC,CEP,1996-11-29 +"Cebra Tech A/S",CBR,1996-11-29 +"Cefar Digital Vision",CEF,1997-02-19 +"Centurion Technologies P/L",CEN,2000-10-23 +"Century Corporation",TCE,1996-11-29 +"Cerevo Inc.",CRV,2010-07-13 +Ceronix,CER,2008-09-02 +"Ceton Corporation",TOM,2014-05-08 +"CH Products",CHP,1997-04-24 +"ChangHong Electric Co.,Ltd",CHD,2001-11-30 +"Chase Research PLC",CHA,1996-11-29 +"Cherry GmbH",CHY,1999-05-16 +"Chi Mei Optoelectronics corp.",CMO,2001-03-15 +"CHIC TECHNOLOGY CORP.",CHM,1999-07-16 +"Chicony Electronics Company Ltd",CEC,1996-11-29 +"Chimei Innolux Corporation",CMN,2010-09-02 +"China Hualu Group Co., Ltd.",HLG,2013-05-13 +Chloride-R&D,CHL,1996-11-29 +"Christie Digital Systems Inc",CDG,2001-04-24 +"Chromatec Video Products Ltd",CVP,2013-08-09 +"Chrontel Inc",CHI,1996-11-29 +"Chunghwa Picture Tubes,LTD.",CHT,2001-03-15 +"Chunghwa Telecom Co., Ltd.",CTE,2002-05-16 +"Chunichi Denshi Co.,LTD.",KCD,2010-12-23 +"Chuomusen Co., Ltd.",QQQ,2002-08-07 +"Chyron Corp",CGS,2008-11-13 +Cine-tal,CNE,2007-06-13 +"Cipher Systems Inc",PTG,1996-11-29 +"Ciprico Inc",CIP,1996-11-29 +"Ciprico Inc",CPC,1996-11-29 +"Cirel Systemes",FPX,1996-11-29 +"Cirque Corporation",CRQ,1996-11-29 +"Cirrus Logic Inc",CIR,1996-11-29 +"Cirrus Logic Inc",CLI,1996-11-29 +"Cirtech (UK) Ltd",SNS,1997-08-20 +"CIS Technology Inc",WSC,1996-11-29 +"Cisco Systems Inc",CIS,1996-11-29 +"Citicom Infotech Private Limited",CIL,2000-08-10 +"Citifax Limited",CIT,1997-07-16 +"Citron GmbH",CIN,2005-07-28 +"Clarion Company Ltd",CLA,1996-11-29 +"Clarity Visual Systems",CVS,2000-01-13 +"Classe Audio",CLE,2006-02-16 +"Clevo Company",CLV,1998-01-30 +"Clinton Electronics Corp.",PPM,2003-10-01 +"Clone Computers",CLO,1996-11-29 +"Cloudium Systems Ltd.",CSL,2013-02-14 +"CMC Ltd",CMC,1996-11-29 +"C-Media Electronics",CMI,1996-11-29 +"CNet Technical Inc",JQE,1996-11-29 +"COBY Electronics Co., Ltd",COB,2007-06-13 +"CODAN Pty. Ltd.",COD,2000-10-23 +"Codec Inc.",COI,2001-11-30 +"Codenoll Technical Corporation",CDN,1996-11-29 +"COINT Multimedia Systems",CNT,1999-03-20 +Colin.de,CDE,2005-01-18 +"Colorado MicroDisplay, Inc.",CMD,1999-03-20 +"Colorado Video, Inc.",CVI,2012-08-15 +"COM 1",MVX,1996-11-29 +"Comex Electronics AB",CMX,2004-05-28 +"Comm. Intelligence Corporation",CIC,1996-11-29 +"COMMAT L.t.d.",CLD,2000-08-10 +"Communications Specialies, Inc.",SDH,2005-09-06 +"Communications Supply Corporation (A division of WESCO)",INX,2012-11-07 +"Compal Electronics Inc",CPL,1996-11-29 +"Compaq Computer Company",CPQ,1996-11-29 +"Compound Photonics",CPP,2013-10-01 +CompuAdd,CPD,1996-11-29 +"CompuMaster Srl",CMS,1999-02-22 +"Computer Diagnostic Systems",CDS,2001-03-15 +"Computer Peripherals Inc",CPI,1996-11-29 +"Computer Technology Corporation",CTP,1998-03-26 +"ComputerBoards Inc",CBI,1998-02-03 +"Computerm Corporation",CTM,1996-11-29 +"Computone Products",CTN,1996-11-29 +Comrex,COX,2011-10-18 +"Comtec Systems Co., Ltd.",CTS,2002-04-25 +"Comtime GmbH",CMM,2002-09-23 +"Comtrol Corporation",COM,1996-11-29 +"Concept Development Inc",CDI,1996-11-29 +"Concept Solutions & Engineering",CSE,1996-12-11 +"Concepts Inc",DCI,1996-11-29 +"Conexant Systems",CXT,1999-01-20 +"congatec AG",CGT,2011-06-16 +"Connect Int'l A/S",CNI,1996-11-29 +"Connectware Inc",CWR,1996-11-29 +"CONRAC GmbH",CRC,2004-04-20 +"Consultancy in Advanced Technology",CAT,1997-09-19 +"Consumer Electronics Association",CEA,2006-09-05 +"CONTEC CO.,LTD.",CCJ,2000-08-10 +"Contec Company Ltd",CON,1996-11-29 +"Contemporary Research Corp.",CRH,2015-02-24 +"Control4 Corporation",CTR,2014-05-28 +"Convergent Data Devices",CDD,2004-02-27 +"Convergent Design Inc.",CDV,2006-09-05 +"Core Dynamics Corporation",CDC,1996-11-29 +"Corion Industrial Corporation",ART,1996-11-29 +"Core Technology Inc",COT,2000-04-19 +CoreLogic,CLG,1998-11-27 +"Cornerstone Imaging",CRN,1996-11-29 +"Corollary Inc",COR,1996-12-13 +"Cosmic Engineering Inc.",CSM,2012-04-18 +"CoStar Corporation",COS,1996-11-29 +"CoSystems Inc",CTA,1998-10-24 +"Covia Inc.",CVA,2010-05-11 +cPATH,CPT,1998-03-09 +"CRALTECH ELECTRONICA, S.L.",CRA,2015-03-24 +"Cray Communications",CDK,1996-11-29 +"CRE Technology Corporation",IOA,1997-06-30 +"Creative Labs Inc",CRE,1996-11-29 +"Creative Logic  ",CRL,1997-10-16 +"Creative Technology Ltd",CTL,1996-11-29 +"Creatix Polymedia GmbH",CTX,1996-11-29 +"Crescendo Communication Inc",CRS,1996-11-29 +"Cresta Systems Inc",CSD,1997-08-01 +"Crestron Electronics, Inc.",CEI,2006-05-08 +"Crio Inc.",CRI,1999-09-13 +"Cromack Industries Inc",CII,1997-01-22 +"Crystal Computer",XTL,1996-11-29 +"Crystal Semiconductor",CSC,1996-11-29 +"CrystaLake Multimedia",CLM,1996-11-29 +"CSS Laboratories",CSS,1997-01-02 +"CSTI Inc",CST,1996-11-29 +"CTC Communication Development Company Ltd",CTC,1997-10-21 +"Cubix Corporation",CUB,1996-11-29 +"Curtiss-Wright Controls, Inc.",CWC,2013-04-05 +Cyberlabs,CYL,1998-04-14 +CyberVision,CYB,1997-05-13 +Cyberware,CYW,2000-02-21 +"Cybex Computer Products Corporation",CBX,1999-11-08 +"Cyclades Corporation",CYD,2001-05-07 +"Cylink Corporation",CYC,1996-11-29 +"Cyrix Corporation",CYX,1997-10-21 +"Cyrix Corporation",CRX,1997-03-21 +"Cytechinfo Inc",CYT,1998-03-13 +"Cyviz AS",CYV,2002-04-25 +"D&M Holdings Inc, Professional Business Company",DMP,2006-09-05 +"D.N.S. Corporation",OPI,1996-11-29 +"DA2 Technologies Corporation",DDA,2006-03-13 +"DA2 Technologies Inc",DAW,2005-09-06 +"Daewoo Electronics Company Ltd",DWE,1996-11-29 +"Dai Telecom S.p.A.",TLT,2003-06-04 +"Daintelecom Co., Ltd",DIN,1999-11-08 +"DAIS SET Ltd.",DAI,2000-02-21 +Daktronics,DAK,2004-06-23 +"Dale Computer Corporation",DCC,1996-11-29 +"Dancall Telecom A/S",DCT,1997-08-12 +"Danelec Marine A/S",DAN,2009-12-24 +"Danka Data Devices",DDD,1996-11-29 +"Daou Tech Inc",DAU,1996-11-29 +DAT,HCA,2001-03-15 +"Data Apex Ltd",DAX,1996-11-29 +"Data Display AG",DDI,2002-07-17 +"Data Expert Corporation",DXP,1996-11-29 +"Data Export Corporation",EXP,1996-11-29 +"Data Modul AG",DMO,2013-12-03 +"Data Price Informatica",EBH,2001-05-24 +"Data Race Inc",DRI,1997-07-30 +"Data Ray Corp.",DRC,2001-11-30 +"Data Translation",DTX,1996-11-29 +"Data Video",DVT,2007-02-13 +"Databook Inc",DBK,1996-11-29 +"Datacast LLC",DCD,1997-12-02 +"Datacommunicatie Tron B.V.",TRN,1996-11-29 +"Datacube Inc",DQB,1996-11-29 +"Datadesk Technologies Inc",DDT,1998-11-27 +"Datakey Inc",DKY,1998-04-06 +"Datalogic Corporation",LJX,1996-11-29 +"Datang Telephone Co",DTN,1998-09-23 +"Dataq Instruments Inc",DII,1996-11-29 +"Datasat Digital Entertainment",DDE,2011-11-18 +"Datatronics Technology Inc",DCV,1997-01-02 +"Datel Inc",DAT,1996-11-29 +"Datenerfassungs- und Informationssysteme",MSD,1998-03-16 +"Davicom Semiconductor Inc",DAV,1997-01-15 +"DAVIS AS",DAS,1998-02-03 +"DB Networks Inc",DBN,1997-12-01 +"DBA Hans Wedemeyer",HWC,1999-03-20 +"DCM Data Products",DCM,1996-11-29 +"Dearborn Group Technology",DGT,1997-11-11 +"DECIMATOR DESIGN PTY LTD",DXD,2012-03-06 +"Decros Ltd",DCR,1996-11-29 +"Deep Video Imaging Ltd",MLD,2003-08-14 +"DEI Holdings dba Definitive Technology",DFT,2011-12-09 +"Deico Electronics",DEI,1996-11-29 +"Dell Inc",DLL,2009-03-27 +"Dell Inc.",DEL,2009-12-09 +"Delphi Automotive LLP",DPH,2013-10-15 +"Delta Electronics Inc",DPC,1996-11-29 +"Delta Information Systems, Inc",DDV,2012-01-03 +DELTATEC,DTA,2009-03-13 +"Deltec Corporation",FPS,1996-11-29 +"DENON, Ltd.",DON,2004-04-01 +"Dension Audio Systems",DHD,2013-03-04 +"Densitron Computers Ltd",DEN,1999-09-13 +"Design & Test Technology, Inc.",DTT,2010-09-30 +"Design Technology",LPI,1996-11-29 +"Deterministic Networks Inc.",DNI,2000-04-19 +"Deutsche Telekom Berkom GmbH",BCQ,1997-08-12 +"Deutsche Thomson OHG",DTO,2007-06-14 +"Devolo AG",DVL,2002-05-30 +"Dextera Labs Inc",DXL,2009-12-09 +DFI,DFI,1996-11-29 +"DH Print",DHP,1996-11-29 +Diadem,DIA,1996-11-29 +"Diagsoft Inc",DGS,1996-11-29 +"Dialogue Technology Corporation",DCO,2004-06-16 +"Diamond Computer Systems Inc",DCS,1996-11-29 +"Diamond Lane Comm. Corporation",DLC,1996-11-29 +DiCon,DNV,2004-12-15 +"Dictaphone Corporation",DVD,1998-04-03 +"Diebold Inc.",DBD,2006-09-05 +"Digatron Industrie Elektronik GmbH",DAE,1997-02-24 +"DIGI International",DGI,1996-11-29 +"DigiBoard Inc",DBI,1996-11-29 +"Digicom S.p.A.",DIG,1996-11-29 +"Digicom Systems Inc",DMB,1998-03-13 +"Digicorp European sales S.A.",DGP,1997-05-22 +"Digiital Arts Inc",DGA,2007-06-14 +"Digipronix Control Systems",DXC,1999-07-16 +"Digital Acoustics Corporation",DAC,2000-05-24 +"Digital Audio Labs Inc",DAL,1996-11-29 +"Digital Communications Association",DCA,1996-11-29 +"Digital Discovery",SHR,1997-09-24 +"Schneider Electric Japan Holdings, Ltd.",PRF,2003-01-02 +"Digital Equipment Corporation",DEC,1996-11-29 +"Digital Processing Systems",DPS,1996-11-29 +"Digital Projection Limited",DPL,2002-07-09 +"DIGITAL REFLECTION INC.",DRD,2000-02-21 +"Digital Video System",DVS,1996-11-29 +"DigiTalk Pro AV",DPA,2000-10-23 +"Digital-Logic GmbH",DLG,2003-09-02 +"Digitan Systems Inc",DSI,1996-11-29 +"Digitelec Informatique Park Cadera",DLT,1996-11-29 +"Dimension Technologies, Inc.",DTE,2010-05-03 +"Dimond Multimedia Systems Inc",DMM,1996-11-29 +"Diseda S.A.",DIS,1996-11-29 +"Distributed Management Task Force, Inc. (DMTF)",DMT,2009-03-31 +"Diversified Technology, Inc.",DTI,1996-11-29 +"D-Link Systems Inc",ABO,1996-11-29 +"D-Link Systems Inc",DLK,1996-11-29 +"DNA Enterprises, Inc.",DNA,1998-09-01 +"DO NOT USE - AUO",AUO,2008-09-16 +"DO NOT USE - LPL",LPL,2008-09-16 +"DO NOT USE - PHI",PHI,1996-11-29 +"DO NOT USE - PTW",PTW,2009-09-09 +"DO NOT USE - PVC",PVC,2009-09-09 +"DO NOT USE - RTK",RTK,2009-09-09 +"DO NOT USE - SEG",SEG,2009-09-09 +"DO NOT USE - TNJ",TNJ,2009-09-09 +"DO NOT USE - UND",UND,1996-11-29 +"DO NOT USE - UNE",UNE,1996-11-29 +"DO NOT USE - UNF",UNF,1996-11-29 +"DO NOT USE - WAN",WAN,2009-09-09 +"DO NOT USE - XER",XER,2009-09-09 +"DO NOT USE - XOC",XOC,2009-09-09 +"Doble Engineering Company",DBL,1996-11-29 +DocuPoint,DPI,1996-11-29 +"Dolby Laboratories Inc.",DLB,2010-01-27 +"Dolman Technologies Group Inc",DOL,1997-11-11 +"Domain Technology Inc",DSP,1996-11-29 +"DOME imaging systems",DMS,2000-10-23 +"Dome Imaging Systems",DOM,1996-11-29 +"Dongguan Alllike Electronics Co., Ltd.",AIK,2015-04-11 +"Dosch & Amand GmbH & Company KG",DUA,1997-12-02 +"Dotronic Mikroelektronik GmbH",DOT,2002-06-28 +"dPict Imaging, Inc.",DIM,2008-02-12 +"DpiX, Inc.",DPX,1998-09-23 +DPT,DPT,1996-11-29 +"Dr. Bott KG",DRB,2002-04-25 +"Dr. Neuhous Telekommunikation GmbH",DNT,1996-11-29 +"Dragon Information Technology",DIT,1996-11-29 +"DRS Defense Solutions, LLC",DRS,2011-10-18 +"DS Multimedia Pte Ltd",DSD,2006-02-14 +"DSM Digital Services GmbH",DSM,1996-11-29 +"dSPACE GmbH",DCE,1996-12-16 +"DTC Tech Corporation",DTC,1996-11-29 +"DugoTech Co., LTD",DGK,2007-06-14 +"Dune Microsystems Corporation",DMC,1996-11-29 +"Dycam Inc",DYC,1998-01-08 +"Dymo-CoStar Corporation",DYM,1998-12-28 +"Dynamic Controls Ltd",DCL,2000-05-24 +"Dynax Electronics (HK) Ltd",DTK,1996-11-29 +"Dynax Electronics (HK) Ltd",DYX,1996-11-29 +"e.Digital Corporation",EDC,2000-10-23 +"E.E.P.D. GmbH",EEP,2007-06-14 +"Eagle Technology",EGL,1996-11-29 +"Eastman Kodak Company",KOD,2000-05-24 +"Eastman Kodak Company",EKC,1996-11-29 +"Easytel oy",TWI,1999-07-16 +"EBS Euchner Büro- und Schulsysteme GmbH",EBS,2013-02-05 +"Echo Speech Corporation",ECO,1996-11-29 +"Eclipse Tech Inc",ETI,1996-11-29 +"E-Cmos Tech Corporation",ECM,1996-11-29 +"Eden Sistemas de Computacao S/A",ESC,1996-11-29 +"Edimax Tech. Company Ltd",EDI,1996-11-29 +EDMI,EDM,1998-07-16 +"Edsun Laboratories",ELI,1996-11-29 +"EE Solutions, Inc.",EES,2003-04-16 +"EEH Datalink GmbH",EEH,1997-07-03 +"Efficient Networks",ENI,1996-11-29 +"Egenera, Inc.",EGN,2002-10-08 +"Eicon Technology Corporation",EIC,1996-11-29 +"EIZO GmbH Display Technologies",EGD,2009-02-13 +"Eizo Nanao Corporation",ENC,1998-12-28 +"EKSEN YAZILIM",EKS,2002-04-25 +"ELAD srl",ELA,2002-04-25 +"ELAN MICROELECTRONICS CORPORATION",ETD,2009-11-03 +"ELAN MICROELECTRONICS CORPORATION",TSH,2014-11-14 +"Elbit Systems of America",ESA,2009-06-15 +"ELCON Systemtechnik GmbH",ESG,1999-07-16 +"ELEA CardWare",LXS,1998-06-25 +"Elecom Company Ltd",ECP,1996-11-29 +"Elecom Company Ltd",ELE,1996-11-29 +"Electro Cam Corp.",ECA,2000-08-10 +"Electro Scientific Ind",ELC,1996-11-29 +"Electronic Measurements",MMM,1996-11-29 +"Electronic Trade Solutions Ltd",ETS,2002-08-20 +"Electronic-Design GmbH",EDG,1997-08-12 +"Electrosonic Ltd",ELL,1999-09-13 +"Element Labs, Inc.",ELT,2007-10-11 +"Elgato Systems LLC",EGA,2011-02-08 +"Elitegroup Computer Systems Company Ltd",ECS,1996-11-29 +"Elitegroup Computer Systems Company Ltd",UEG,1996-11-29 +"Elmeg GmbH Kommunikationstechnik",ELG,1996-11-29 +"Elmic Systems Inc",ELM,1996-11-29 +"ELMO COMPANY, LIMITED",EMO,2012-06-26 +"Elo TouchSystems Inc",ELO,1996-11-29 +"Elonex PLC",ELX,1996-11-29 +"El-PUSK Co., Ltd.",LPE,2001-08-14 +"ELSA GmbH",ELS,1996-11-29 +"ELTEC Elektronik AG",EAG,2014-11-25 +"Embedded computing inc ltd",EMB,2002-02-25 +"Embedded Solution Technology",EST,2000-05-24 +"Embrionix Design Inc.",EMD,2013-07-24 +"Emcore Corporation",EMK,2012-05-31 +"Emerging Display Technologies Corp",EDT,2009-08-18 +"EMG Consultants Inc",EMG,1996-11-29 +"EMiNE TECHNOLOGY COMPANY, LTD.",EME,2005-06-16 +Empac,EPC,1996-12-04 +"Emulex Corporation",EMU,1996-11-29 +"Enciris Technologies",ECI,2008-11-01 +"Enciris Technologies",ECT,2008-11-01 +"ENE Technology Inc.",ENE,2001-03-15 +"e-Net Inc",DTL,1997-10-16 +Enhansoft,EHN,2010-11-16 +"ENIDAN Technologies Ltd",END,2000-04-19 +"Ensemble Designs, Inc",ESD,2009-12-09 +"Ensoniq Corporation",ENS,1996-11-29 +"Enterprise Comm. & Computing Inc",ENT,1996-11-29 +"Envision Peripherals, Inc",EPI,1999-02-22 +"Eon Instrumentation, Inc.",EON,2015-01-15 +"EPiCON Inc.",EPN,1998-09-23 +"Epiphan Systems Inc. ",EPH ,2011-03-14 +"Epson Research",EHJ,1996-11-29 +"Equinox Systems Inc",EQX,1996-11-29 +"Equipe Electronics Ltd.",EQP,2005-07-14 +"Ergo Electronics",EGO,1996-11-29 +"Ergo System",ERG,1996-11-29 +"Ericsson Mobile Communications AB",ERI,1997-10-22 +"Ericsson Mobile Networks B.V.",EUT,1998-04-14 +"Ericsson, Inc.",ERN,1998-09-23 +ES&S,ESK,1999-11-08 +eSATURNUS,ESN,2012-02-21 +"Escort Insturments Corporation",ERT,1997-05-02 +"ESS Technology Inc",ESS,1996-11-29 +"ESSential Comm. Corporation",ECC,1996-11-29 +"Esterline Technologies",ESL,2012-01-06 +ScioTeq,ESB,2015-01-15 +"E-Systems Inc",ESY,1996-11-29 +"ET&T Technology Company Ltd",EEE,1998-05-04 +"E-Tech Inc",ETT,1996-11-29 +"eTEK Labs Inc.",ETK,1998-07-16 +"Etherboot Project",ETH,2010-07-09 +"Eugene Chukhlomin Sole Proprietorship, d.b.a.",ECK,2008-05-03 +"Euraplan GmbH",ERP,1996-11-29 +"Evans and Sutherland Computer",EAS,2003-01-28 +Everex,EVX,1996-11-29 +"Everton Technology Company Ltd",ETC,1997-04-10 +"Evertz Microsystems Ltd.",ETL,2007-06-14 +"eviateg GmbH",EVI,2000-02-21 +"Ex Machina Inc",EMI,1996-11-29 +"Exacom SA",YHW,1996-11-29 +"Exatech Computadores & Servicos Ltda",EXT,1998-09-23 +"Excel Company Ltd",ECL,1997-05-27 +"Excession Audio",EXC,1998-11-06 +"EXFO Electro Optical Engineering",XFO,1998-04-29 +"Exide Electronics",EXI,1996-11-29 +"Extended Systems, Inc.",ESI,1999-07-16 +"Exterity Ltd",EXY,2009-02-12 +"Extraordinary Technologies PTY Limited",CRO,2005-04-11 +"Exxact GmbH",EXX,1996-11-29 +"eyefactive Gmbh",EYF,2015-07-07 +"eyevis GmbH",EYE,2011-11-18 +"EzE Technologies",EZE,2005-02-21 +"F.J. Tieman BV",FJT,1998-06-25 +"Fairfield Industries",FFI,1996-11-29 +"Fantalooks Co., Ltd.",FAN,2014-03-12 +"Fanuc LTD",FNC,1997-01-29 +"Farallon Computing",FAR,1996-11-29 +"FARO Technologies",FRO,2012-09-21 +"Faroudja Laboratories",FLI,2004-06-02 +"Fast Multimedia AG",FMA,1996-11-29 +"FastPoint Technologies, Inc.",FTI,2001-06-21 +"Feature Integration Technology Inc.",FIT,2009-08-11 +"Fellowes & Questec",FEL,1996-11-29 +"Fellowes, Inc.",FMI,2001-07-05 +"Fen Systems Ltd.",FEN,2010-05-04 +"Ferranti Int'L",FER,1996-11-29 +"Ferrari Electronic GmbH",TLA,1996-12-04 +FHLP,FHL,1996-11-29 +"Fibernet Research Inc",FRI,1996-11-29 +"Finecom Co., Ltd.",FIN,1998-11-27 +"Fingerprint Cards AB",FPC,2013-06-14 +"First Industrial Computer Inc",PCG,1996-11-29 +"First International Computer Inc",LEO,1997-09-19 +"First International Computer Ltd",FCG,1997-04-10 +"First Virtual Corporation",FVC,1996-11-29 +"Flat Connections Inc",FWR,1996-11-29 +"FlightSafety International",SSD,2000-08-10 +"FLY-IT Simulators",FIS,1997-09-08 +"FocalTech Systems Co., Ltd.",FTS,2013-07-23 +"Focus Enhancements, Inc.",FCS,2002-12-12 +"Fokus Technologies GmbH",FOK,2013-10-22 +"FOR-A Company Limited",FOA,2008-12-06 +"Force Computers",FRC,1996-11-29 +"Ford Microelectronics Inc",FMC,1997-03-11 +"Fore Systems Inc",FSI,1996-11-29 +"Forefront Int'l Ltd",FIL,1996-11-29 +"Formosa Industrial Computing Inc",FIC,1996-11-29 +Formoza-Altair,FMZ,2003-04-25 +"Forth Dimension Displays Ltd",FDD,2015-07-07 +"Forvus Research Inc",FRE,1997-04-24 +"Foss Tecator",FOS,1997-10-22 +"Founder Group Shenzhen Co.",FZC,1999-11-08 +"Fountain Technologies Inc",FTN,1996-11-29 +"Fraunhofer Heinrich-Hertz-Institute",HHI,2012-07-27 +"Freedom Scientific BLV",FRD,2007-06-15 +"FREEMARS Heavy Industries",TCX,2001-03-15 +"Frontline Test Equipment Inc.",FTE,1999-01-20 +"FTG Data Systems",FTG,1996-11-29 +"Fuji Xerox",FXX,1996-11-29 +"FUJIFILM Corporation",FFC,2011-08-22 +"Fujitsu Display Technologies Corp.",FDT,2002-10-23 +"Fujitsu General Limited.",FGL,2000-02-21 +"Fujitsu Ltd",FUJ,1996-11-29 +"Fujitsu Microelect Ltd",FML,1996-11-29 +"Fujitsu Peripherals Ltd",FPE,1997-08-19 +"Fujitsu Siemens Computers GmbH",FUS,2000-01-13 +"Fujitsu Spain",FJS,1996-11-29 +"FCL COMPONENTS LIMITED",FJC,1999-05-16 +"FUJITSU TEN LIMITED",FTL,2011-12-20 +"Funai Electric Co., Ltd.",FNI,2005-01-18 +"Furukawa Electric Company Ltd",FCB,1996-11-29 +"FURUNO ELECTRIC CO., LTD.",FEC,1996-11-29 +"Future Designs, Inc.",FDI,2014-09-29 +"Future Domain",FDC,1996-11-29 +"Future Systems Consulting KK",FSC,1996-11-29 +"Futuretouch Corporation",FTC,1996-11-29 +"FZI Forschungszentrum Informatik",FZI,1997-08-12 +"G&W Instruments GmbH",SPH,2002-02-25 +"G. Diehl ISDN GmbH",GDI,1996-11-29 +"Gadget Labs LLC",GLS,1996-11-29 +"Gage Applied Sciences Inc",GAG,1996-11-29 +"GAI-Tronics, A Hubbell Company",HUB,2009-03-26 +"Galil Motion Control",GAL,1996-11-29 +"Garmin International",GRM,2011-12-09 +"Garnet System Company Ltd",GTM,1996-11-29 +"Gateway 2000",GWY,1996-11-29 +"Gateway Comm. Inc",GCI,1996-11-29 +"Gateworks Corporation",GWK,2013-07-31 +"Gaudi Co., Ltd.",GAU,2003-03-31 +"GCC Technologies Inc",GCC,1997-06-05 +GDS,GDS,2004-06-23 +"GE Fanuc Embedded Systems",GEF,2007-06-14 +"Abaco Systems, Inc.",GEH,2010-09-03 +"Gefen Inc.",GFN,2007-10-11 +"Gem Plus",GEM,1998-02-27 +"GEMINI 2000 Ltd",GMN,2000-10-23 +"General Datacom",GDC,1996-11-29 +"General Dynamics C4 Systems",GED,2013-01-09 +"General Information Systems",GML,2000-01-13 +"General Inst. Corporation",GIC,1996-11-29 +"General Standards Corporation",GSC,1998-07-16 +"General Touch Technology Co., Ltd.",GTT,2002-11-21 +"Genesys ATE Inc",GEN,1996-11-29 +"Genesys Logic",GLM,1999-11-08 +"Gennum Corporation",GND,2006-09-05 +"GEO Sense",GEO,1996-11-29 +"Geotest Marvin Test Systems Inc",GTS,1998-02-24 +"GERMANEERS GmbH",GER,2011-12-20 +"GES Singapore Pte Ltd",GES,2001-03-15 +"Getac Technology Corporation",GET,2010-05-11 +"GFMesstechnik GmbH",GFM,2001-03-15 +"GI Provision Ltd",GIP,2012-02-08 +"Global Data SA",PST,1996-11-29 +"Global Village Communication",GVL,1996-11-29 +"GMK Electronic Design GmbH",GMK,2008-01-18 +"GMM Research Inc",GMM,1996-11-29 +"GMX Inc",GMX,1996-11-29 +"GN Nettest Inc",GNN,1997-07-30 +"GOEPEL electronic GmbH",GOE,2013-06-24 +"Goldmund - Digital Audio SA",GLD,2012-02-06 +"GOLD RAIN ENTERPRISES CORP.",GRE,2003-06-04 +"Goldstar Company Ltd",GSM,1996-11-29 +Goldtouch,GTI,1997-08-06 +"Google Inc.",GGL,2010-05-26 +"GoPro, Inc.",GPR,2015-01-15 +"Granch Ltd",GRH,2002-09-23 +"Grand Junction Networks",GJN,1996-11-29 +"Grandstream Networks, Inc.",GSN,2014-03-03 +"Graphic SystemTechnology",GST,1996-11-29 +"Graphica Computer",GRA,1996-11-29 +"Graphtec Corporation",GTC,1996-11-29 +"Grass Valley Germany GmbH",TGV,2007-06-14 +"Grey Cell Systems Ltd",GCS,1997-04-29 +"Grossenbacher Systeme AG",GSY,2000-04-19 +"G-Tech Corporation",GTK,1996-11-29 +"Guillemont International",GIM,1997-10-29 +"GUNZE Limited",GZE,2005-05-02 +"Gunze Ltd",GNZ,1996-11-29 +"Guntermann & Drunck GmbH",GUD,2003-03-10 +"Guzik Technical Enterprises",GUZ,1996-11-29 +"GVC Corporation",GVC,1996-11-29 +"H.P.R. Electronics GmbH",HPR,2007-08-29 +"Hagiwara Sys-Com Company Ltd",HSC,1996-11-29 +"GW Instruments",GWI,1996-11-29 +"Haider electronics",HAE,2001-07-05 +"Haivision Systems Inc.",HAI,2007-11-15 +Halberthal,HAL,1998-02-10 +"Hall Research",HRI,2012-05-10 +"HAMAMATSU PHOTONICS K.K.",HPK,2006-12-20 +"Hampshire Company, Inc.",HTI,1999-01-20 +"Hanchang System Corporation",HAN,2003-06-21 +"HannStar Display Corp",HSD,2009-08-11 +"HannStar Display Corp",HSP,2009-08-11 +"HardCom Elektronik & Datateknik",HDC,1998-04-14 +"Harman International Industries, Inc",HII,2015-01-09 +"Harris & Jeffries Inc",HJI,1996-11-29 +"Harris Canada Inc",HWA,1998-03-13 +"Harris Corporation",HAR,2011-12-20 +"Harris Semiconductor",HRS,1997-01-02 +"Hauppauge Computer Works Inc",HCW,1996-11-29 +"Hayes Microcomputer Products Inc",HAY,1996-11-29 +"HCL America Inc",HCL,1996-11-29 +"HCL Peripherals",HCM,2001-10-02 +"HD-INFO d.o.o.",HDI,2001-10-08 +"Headplay, Inc.",HPI,2007-04-30 +"Heng Yu Technology (HK) Limited",HYT,2000-10-23 +Hercules,HRC,2001-03-15 +HERCULES,HRT,2001-03-15 +"HETEC Datensysteme GmbH",HET,2004-02-03 +"Hewlett Packard",HWP,2001-03-15 +"Hewlett Packard",HPD,1997-05-02 +"Hewlett-Packard Co.",HPC,2000-08-10 +"Hewlett-Packard Co.",HPQ,2004-07-12 +"Hexium Ltd.",HXM,2008-04-15 +"Hibino Corporation",HIB,2003-07-09 +"Highwater Designs Ltd",HWD,1996-11-29 +"Hikom Co., Ltd.",HIK,2003-10-13 +"Hilevel Technology",HIL,1996-11-29 +"HIRAKAWA HEWTECH CORP.",HHC,2008-05-20 +"Hitachi America Ltd",HIT,1996-11-29 +"Hitachi Consumer Electronics Co., Ltd",HCE,2009-05-15 +"Hitachi Information Technology Co., Ltd.",HIC,2000-04-19 +"Hitachi Ltd",HTC,1996-11-29 +"Hitachi Maxell, Ltd.",MXL,2000-01-13 +"Hitachi Micro Systems Europe Ltd",HEL,1997-07-09 +"Hitex Systementwicklung GmbH",HTX,1998-01-30 +"hmk Daten-System-Technik BmbH",HMK,1997-09-30 +"HOB Electronic GmbH",HOB,1996-11-29 +"Holoeye Photonics AG",HOL,2005-02-02 +"Holografika kft.",HDV,2005-03-31 +"Holtek Microelectronics Inc",HTK,1996-11-29 +"Home Row Inc",INC,1996-11-29 +"HON HAI PRECISON IND.CO.,LTD.",FOX,2010-08-02 +"HONKO MFG. CO., LTD.",HKA,2004-12-01 +"Hope Industrial Systems, Inc.",HIS,2014-01-13 +"Horner Electric Inc",APG,1996-11-29 +"Horsent Technology Co., Ltd.",HST,2015-04-11 +"Hosiden Corporation",HOE,1997-08-05 +"HTBLuVA Mödling",HTL,2014-02-17 +"Hualon Microelectric Corporation",HMC,1996-11-29 +"HUALONG TECHNOLOGY CO., LTD",EBT,2007-06-15 +"Hughes Network Systems",HNS,1996-11-29 +"HUMAX Co., Ltd.",HMX,2006-02-14 +"HYC CO., LTD.",HYO,2006-04-12 +"Hydis Technologies.Co.,LTD",HYD,2010-11-22 +"Hynix Semiconductor",HYV,2008-11-29 +"Hypercope Gmbh Aachen",HYC,1997-12-01 +"Hypertec Pty Ltd",HYR,1996-11-29 +"Hyphen Ltd",HYP,1996-11-29 +"I&T Telecom.",ITT,1999-11-08 +"I/OTech Inc",IOT,1996-11-29 +"IAT Germany GmbH",IAT,1996-11-29 +"IBM Brasil",IBM,1996-11-29 +"IBM Corporation",CDT,1996-11-29 +"IBP Instruments GmbH",IBP,1998-09-23 +"IBR GmbH",IBR,1998-01-16 +"IC Ensemble",ICE,1997-09-19 +"ICA Inc",ICA,2002-05-20 +"ICCC A/S",ICX,1996-11-29 +"ICD Inc",ICD,1997-06-09 +"ICET S.p.A.",ARE,1999-05-16 +"ICP Electronics, Inc./iEi Technology Corp.",ICP,2012-09-07 +ICSL,IUC,1997-08-14 +"Icuiti Corporation",XTD,2007-06-14 +"Icuiti Corporation",IWR,2007-03-06 +"Id3 Semiconductors",ISC,2001-03-15 +"IDE Associates",IDE,1996-11-29 +"IDEO Product Development",IDO,1997-09-30 +"idex displays",DEX,2002-04-25 +"IDEXX Labs",IDX,1996-11-29 +"IDK Corporation",IDK,2003-04-16 +"Idneo Technologies",IDN,2012-07-05 +IDTECH,ITS,2002-06-17 +IEE,IEE,2001-06-21 +"IGM Communi",IGM,1996-11-29 +"IINFRA Co., Ltd",IIN,2003-05-09 +"Iiyama North America",IVM,1996-11-29 +"Ikegami Tsushinki Co. Ltd.",IKE,2014-11-14 +"Ikos Systems Inc",IKS,1996-11-29 +ILC,IND,2004-06-16 +"Image Logic Corporation",ILC,1996-11-29 +"Image Stream Medical",ISM,2010-05-27 +"IMAGENICS Co., Ltd.",IMG,2006-09-05 +"IMAGEQUEST Co., Ltd",IQT,2002-10-08 +Imagraph,IME,1996-12-04 +Imagraph,IMA,1996-11-29 +"ImasDe Canarias S.A.",IMD,1997-07-03 +"IMC Networks",IMC,1996-11-29 +"Immersion Corporation",IMM,1997-07-16 +"IMP Electronics Ltd.",HUM,2004-06-16 +Impinj,IMP,2012-08-14 +"Impossible Production",IMN,2000-08-10 +"In Focus Systems Inc",IFS,1996-11-29 +"In4S Inc",ALD,1997-12-05 +INBINE.CO.LTD,IBI,2001-11-06 +"Indtek Co., Ltd.",INK,2007-03-26 +"IneoQuest Technologies, Inc",IQI,2011-02-18 +"Industrial Products Design, Inc.",IPD,1999-07-16 +"Ines GmbH",INS,1996-11-29 +"Infineon Technologies AG",IFX,2000-04-19 +"Infinite Z",IFZ,2012-01-04 +"Informatik Information Technologies",IIT,2013-08-14 +Informtech,IFT,1996-11-29 +"Infotek Communication Inc",ICI,1996-11-29 +"Infotronic America, Inc.",ITR,2001-06-21 +"Inframetrics Inc",INF,1996-11-29 +"Ingram Macrotron",VSN,2000-08-10 +"Ingram Macrotron Germany",VID,2000-05-24 +"InHand Electronics",IHE,2010-04-20 +"Initio Corporation",INI,1996-11-29 +"Inmax Technology Corporation",IMT,2003-02-12 +"Innolab Pte Ltd",INO,1999-01-20 +"InnoLux Display Corporation",INL,2004-12-15 +"InnoMedia Inc",INM,1996-11-29 +"Innotech Corporation",ILS,2000-10-23 +"Innovate Ltd",ATE,1996-11-29 +"Innovent Systems, Inc.",INN,2000-04-19 +"Innoware Inc",WII,1998-01-30 +"Inovatec S.p.A.",inu,2001-03-15 +"Inside Contactless",ICV,2010-11-04 +"Inside Out Networks",ION,1998-12-28 +"Insignia Solutions Inc",ISG,1996-11-29 +"INSIS Co., LTD.",ISR,2003-02-12 +"Institut f r angewandte Funksystemtechnik GmbH",IAF,1999-03-20 +"Integraph Corporation",ING,1996-11-29 +"Integrated Business Systems",IBC,1996-11-29 +"Integrated Device Technology, Inc.",IDP,2010-01-27 +"Integrated Tech Express Inc",ITE,1996-11-29 +"Integrated Tech Express Inc",SRC,1996-11-29 +"integrated Technology Express Inc",ITX,1997-06-25 +"Integration Associates, Inc.",IAI,2004-03-17 +"Intel Corp",ICO,2000-08-10 +"Intelligent Instrumentation",III,1996-11-29 +"Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell)",IPI,2000-05-24 +"Intelliworxx, Inc.",IWX,1999-05-16 +"Intellix Corp.",SVC,2008-01-18 +"Interaction Systems, Inc",TCH,1999-03-20 +"Interactive Computer Products Inc",PEN,1997-01-15 +"Intercom Inc",ITC,1996-11-29 +"Interdigital Sistemas de Informacao",IDS,1997-10-28 +"Interface Corporation",FBI,1996-11-29 +"Interface Solutions",ISI,1996-11-29 +"Intergate Pty Ltd",IGC,1996-11-29 +"Interlace Engineering Corporation",IEC,1996-11-29 +"Interlink Electronics",IEI,1998-10-16 +"International Datacasting Corporation",IDC,1997-02-25 +"International Display Technology",IDT,2002-05-16 +"International Integrated Systems,Inc.(IISI)",ISY,2000-08-10 +"International Microsystems Inc",IMI,1996-11-29 +"International Power Technologies",IPT,1997-04-11 +"Internet Technology Corporation",ITD,1997-12-05 +"Interphase Corporation",INP,1996-11-29 +"Interphase Corporation",INT,1996-11-29 +"Intersil Corporation",LSD,2012-03-14 +"Intersolve Technologies",IST,1999-03-20 +Inter-Tel,ITL,1997-03-21 +"Intertex Data AB",IXD,1996-11-29 +"Intervoice Inc",IVI,1996-11-29 +"Intevac Photonics Inc.",IVS,2011-02-16 +"Intracom SA",ICM,1998-08-03 +"Intrada-SDD Ltd",SDD,2007-11-21 +"IntreSource Systems Pte Ltd",ISP,1997-08-27 +"Intuitive Surgical, Inc.",SRG,2006-02-16 +"Inventec Corporation",INA,2013-09-13 +"Inventec Electronics (M) Sdn. Bhd.",INE,1998-07-21 +"Inviso, Inc.",INV,2000-10-23 +"I-O Data Device Inc",IOD,1996-11-29 +"i-O Display System",IOS,2001-03-15 +Iomega,IOM,1996-11-29 +"IP Power Technologies GmbH",IPP,2010-12-06 +"IP3 Technology Ltd.",IPQ,2013-11-11 +"IPC Corporation",IPC,1996-11-29 +"IPM Industria Politecnica Meridionale SpA",IPM,1998-09-23 +"IPS, Inc. (Intellectual Property Solutions, Inc.)",IPS,2001-09-05 +"IPWireless, Inc",IPW,2001-03-15 +"ISIC Innoscan Industrial Computers A/S",IIC,2003-07-23 +"Isolation Systems",ISL,1996-11-29 +"ISS Inc",ISS,1996-11-29 +"Itausa Export North America",ITA,1996-11-29 +"Ithaca Peripherals",IPR,1997-07-01 +"ITK Telekommunikation AG",ITK,1996-11-29 +"ITM inc.",ITM,2001-04-24 +"IT-PRO Consulting und Systemhaus GmbH",ITP,2000-10-23 +"Jace Tech Inc",JCE,1996-11-29 +"Jaeik Information & Communication Co., Ltd.",JIC,2000-10-23 +"Jan Strapko - FOTO",XFG,2001-05-07 +"Janich & Klass Computertechnik GmbH",JUK,2002-10-08 +"Janz Automationssysteme AG",JAS,2009-11-03 +"Japan Aviation Electronics Industry, Limited",JAE,2001-03-15 +"Japan Digital Laboratory Co.,Ltd.",JDL,2000-04-19 +"Japan Display Inc.",JDI,2013-04-18 +"Jaton Corporation",JAT,1997-09-24 +"JET POWER TECHNOLOGY CO., LTD.",JET,2001-03-15 +"Jetway Information Co., Ltd",JWY,2003-09-22 +"jetway security micro,inc",JTY,2009-11-11 +"Jiangsu Shinco Electronic Group Co., Ltd",SHI,2004-08-10 +"Jones Futurex Inc",JFX,1996-11-29 +"Jongshine Tech Inc",LTI,1996-11-29 +"Josef Heim KG",HKG,1996-11-29 +"JPC Technology Limited",JPC,2000-10-23 +"JS DigiTech, Inc",JSD,2000-10-23 +"JS Motorsports",JTS,1997-12-05 +Junnila,TPJ,2001-03-15 +"Jupiter Systems",JUP,2006-09-05 +"Jupiter Systems, Inc.",JSI,2007-06-14 +JVC,JVC,2000-10-23 +"JVC KENWOOD Corporation",JKC,2012-03-08 +"JWSpencer & Co.",JWS,1999-07-16 +"Kansai Electric Company Ltd",SGE,1996-12-04 +"Kaohsiung Opto Electronics Americas, Inc.",HIQ,2012-03-14 +"Karn Solutions Ltd.",KSL,2006-05-08 +Karna,KAR,2000-02-21 +"Katron Tech Inc",KTN,1996-11-29 +"Kayser-Threde GmbH",KTG,1996-11-29 +"KDDI Technology Corporation",KDT,2012-05-22 +KDE,KDE,2001-08-14 +"KDS USA",KDS,1996-11-29 +"KEISOKU GIKEN Co.,Ltd.",KGL,2012-04-17 +"Kensington Microware Ltd",KML,1996-11-29 +"Kenwood Corporation",KWD,2008-02-22 +KEPS,EPS,1996-11-29 +"Kesa Corporation",KES,1996-11-29 +"Key Tech Inc",KEY,1996-11-29 +"Key Tronic Corporation",KTK,1996-11-29 +"Keycorp Ltd",KCL,1997-05-20 +KeyView,KVX,2012-08-13 +"Kidboard Inc",KBI,1997-04-24 +"KIMIN Electronics Co., Ltd.",KME,2011-02-15 +"Kinetic Systems Corporation",KSC,1996-11-29 +"King Phoenix Company",KPC,1996-11-29 +"King Tester Corporation",KSX,1998-07-16 +"Kingston Tech Corporation",KTC,1996-11-29 +"Kionix, Inc.",KIO,2013-12-23 +"KiSS Technology A/S",KIS,2005-06-16 +"Klos Technologies, Inc.",PVP,2000-08-10 +"Kobil Systems GmbH",KBL,2001-03-15 +"Kobil Systems GmbH",KOB,2001-03-15 +"Kodiak Tech",KDK,1996-11-29 +"Kofax Image Products",KFX,1996-11-29 +"Kollmorgen Motion Technologies Group",KOL,1996-11-29 +"KOLTER ELECTRONIC",KOE,2001-03-15 +"Komatsu Forest",KFE,2010-04-20 +"Konica corporation",KNC,1997-08-05 +"Konica Technical Inc",KTI,1996-11-29 +"Kontron Electronik",TWE,1996-11-29 +"Kontron Embedded Modules GmbH",KEM,2007-08-29 +"Kontron Europe GmbH",KEU,2014-02-20 +"Korea Data Systems Co., Ltd.",KDM,2003-12-18 +"KOUZIRO Co.,Ltd.",KOU,2012-07-27 +"KOWA Company,LTD.",KOW,2008-03-12 +"Kramer Electronics Ltd. International",KMR,2013-07-10 +"Krell Industries Inc.",KRL,2004-08-03 +"Kroma Telecom",KRM,2010-05-05 +"Kroy LLC",KRY,1998-07-16 +K-Tech,KTE,2003-03-31 +"KUPA China Shenzhen Micro Technology Co., Ltd. Gold Institute",KSG,2014-04-22 +"Kurta Corporation",KUR,1996-11-29 +"Kvaser AB",KVA,1997-01-24 +"KYE Syst Corporation",KYE,1996-11-29 +"Kyocera Corporation",KYC,1996-11-29 +"Kyushu Electronics Systems Inc",KEC,1998-01-12 +"K-Zone International",KZN,2001-06-21 +"K-Zone International co. Ltd.",KZI,2000-08-10 +"L-3 Communications",LLL,2010-05-11 +"La Commande Electronique",LCE,1996-11-29 +"Labcal Technologies",LCT,1999-11-08 +"Labtec Inc",LTC,1997-12-08 +"Labway Corporation",LWC,1996-12-04 +LaCie,LAC,1998-12-28 +"Laguna Systems",LAG,1996-11-29 +"Land Computer Company Ltd",LND,1996-11-29 +"LANETCO International",LNT,2003-05-02 +"Lanier Worldwide",LWW,1996-11-29 +"Lars Haagh ApS",LHA,1997-01-09 +"LASAT Comm. A/S",LAS,1996-11-29 +"Laser Master",LMT,1996-11-29 +"Laserdyne Technologies",LDN,2013-10-16 +"Lasergraphics, Inc.",LGX,2000-02-21 +"Latitude Comm.",LCM,1996-11-29 +"Lava Computer MFG Inc",LAV,1997-04-14 +LCI,LCC,2000-08-10 +"Lectron Company Ltd",LEC,1997-03-27 +"Leda Media Products",LMP,1998-05-11 +"Legerity, Inc",LEG,2005-01-18 +"Leitch Technology International Inc.",LTV,2003-12-09 +Lenovo,LNV,2005-07-14 +"Lenovo Beijing Co. Ltd.",LIN,2012-05-22 +"Lenovo Group Limited",LEN,2005-06-03 +"Lexical Ltd",LEX,1996-11-29 +LEXICON,LCN,2005-03-01 +"Leutron Vision",PRS,1996-11-29 +"Lexmark Int'l Inc",LMI,1996-11-29 +"LG Semicom Company Ltd",LGS,1996-11-29 +LGIC,MAN,2000-02-21 +"LifeSize Communications",LSC,2006-02-14 +"Lighthouse Technologies Limited",LHT,2010-05-04 +"Lightware Visual Engineering",LWR,2009-02-04 +"Lightware, Inc",LTW,1998-10-16 +"Lightwell Company Ltd",LZX,1997-12-02 +"Likom Technology Sdn. Bhd.",LKM,1998-04-23 +"Linear Systems Ltd.",LNR,2007-10-11 +"Link Tech Inc",LNK,1996-11-29 +"Linked IP GmbH",LIP,2010-07-19 +"Lisa Draexlmaier GmbH",FGD,1999-02-22 +"Litelogic Operations Ltd",LOL,2011-12-09 +"Lite-On Communication Inc",LCI,1996-11-29 +"Lithics Silicon Technology",LIT,2001-03-15 +"Litronic Inc",LTN,1998-02-03 +"Locamation B.V.",LOC,2004-01-09 +"Loewe Opta GmbH",LOE,2005-05-02 +"Logic Ltd",LGC,1994-04-02 +"Logical Solutions",LSL,1996-11-29 +"Logicode Technology Inc",LOG,1996-11-29 +"Logitech Inc",LGI,1996-11-29 +"LogiDataTech Electronic GmbH",LDT,2001-03-15 +"Logos Design A/S",SGO,2001-04-24 +"Long Engineering Design Inc",LED,1996-11-29 +"Longshine Electronics Company",LCS,1996-11-29 +"Loughborough Sound Images",LSI,1996-11-29 +"LSI Japan Company Ltd",LSJ,1996-11-29 +"LSI Systems Inc",LSY,1996-11-29 +"LTS Scale LLC",LTS,2007-11-15 +Lubosoft,LBO,2001-04-24 +"Lucent Technologies",LUC,2000-04-19 +"Lucent Technologies",LMG,1997-01-13 +"Lucidity Technology Company Ltd",LTK,1998-05-18 +"Lumagen, Inc.",LUM,2004-08-12 +"Lung Hwa Electronics Company Ltd",LHE,1998-06-12 +Luxeon,LXN,2001-03-15 +"Luxxell Research Inc",LUX,1997-06-09 +"LVI Low Vision International AB",LVI,2011-01-21 +"LXCO Technologies AG",LXC,2012-01-11 +"MAC System Company Ltd",MAC,1997-09-26 +"Mac-Eight Co., LTD.",MEJ,2011-01-19 +"Macraigor Systems Inc",OCD,1998-03-23 +"Macrocad Development Inc.",VHI,2000-04-19 +"Macronix Inc",MXI,1996-11-29 +"Madge Networks",MDG,1996-11-29 +"Maestro Pty Ltd",MAE,1996-12-04 +"MAG InnoVision",MAG,1996-11-29 +"Magic Leap",MLP,2014-11-14 +"Magni Systems Inc",MCP,1996-11-29 +"MagTek Inc.",EKA,2006-02-14 +"Magus Data Tech",MDT,1996-11-29 +"Mainpine Limited",MPN,2007-06-30 +"Mainpine Limited",MUK,1999-09-13 +"Many CNC System Co., Ltd.",PAK,2004-03-12 +"Maple Research Inst. Company Ltd",MPL,1996-11-29 +"MARANTZ JAPAN, INC.",MJI,2000-10-23 +"Marconi Instruments Ltd",MIL,1996-11-29 +"Marconi Simulation & Ty-Coch Way Training",MRC,1996-11-29 +"Marina Communicaitons",MCR,1996-11-29 +"Mark Levinson",MLN,2005-02-28 +"Mark of the Unicorn Inc",MTU,1997-03-21 +"Marseille, Inc.",MNI,2013-02-27 +"Marshall Electronics",MBM,2006-03-13 +"Mars-Tech Corporation",MTC,1996-11-29 +"Maruko & Company Ltd",MRK,1996-11-29 +"MASPRO DENKOH Corp.",MSR,2012-10-25 +"Mass Inc.",MAS,2002-02-25 +"Matelect Ltd.",MEQ,2002-05-30 +Matrox,MTX,1996-11-29 +"Mat's Computers",MCQ,2004-07-22 +"Matsushita Communication Industrial Co., Ltd.",WPA,2001-03-15 +"Panasonic Connect Co.,Ltd.",MAT,2022-04-01 +"MaxCom Technical Inc",MTI,1996-11-29 +"MaxData Computer AG",VOB,2000-02-21 +"MaxData Computer GmbH & Co.KG",MXD,2000-04-19 +"Maxpeed Corporation",MXP,1997-02-19 +"Maxtech Corporation",MXT,1996-11-29 +"MaxVision Corporation",MXV,1999-07-16 +"Maygay Machines, Ltd",DJP,2000-08-10 +"Maynard Electronics",MAY,1996-11-29 +"MAZeT GmbH",MAZ,1998-08-11 +MBC,MBC,1996-11-29 +"McDATA Corporation",MCD,1996-11-29 +"McIntosh Laboratory Inc.",MLI,2008-01-18 +"MCM Industrial Technology GmbH",MIT,2004-10-29 +"MEC Electronics GmbH",CEM,2000-04-19 +"Medar Inc",MDR,1996-12-11 +"Media Technologies Ltd.",MTB,2009-01-05 +"Media Tek Inc.",MKC,2007-06-14 +"Media Vision Inc",MVI,1996-11-29 +"Media4 Inc",MDA,1997-03-20 +"Mediacom Technologies Pte Ltd",OWL,1996-11-29 +"Mediaedge Corporation",MEK,2013-11-19 +"MediaFire Corp.",MFR,1998-12-28 +Mediasonic,FTR,1996-11-29 +"MediaTec GmbH",MTE,1996-12-13 +"Mediatek Corporation",MDK,1997-03-13 +"Mediatrix Peripherals Inc",MPI,1997-04-24 +"Medikro Oy",MRO,1997-09-19 +"Mega System Technologies Inc",MEC,1997-12-29 +"Mega System Technologies, Inc.",MGA,1998-12-28 +"Megasoft Inc",MSK,1996-11-29 +"Megatech R & D Company",MGT,1996-11-29 +"Meld Technology",MEP,2012-08-16 +"MEN Mikroelectronik Nueruberg GmbH",MEN,1997-05-23 +"Mentor Graphics Corporation",MGC,2009-07-30 +MEPCO,RLD,2001-03-15 +MEPhI,PPD,1998-11-27 +"Merging Technologies",MRT,1996-11-29 +"Meridian Audio Ltd",MAL,2009-02-04 +"Messeltronik Dresden GmbH",MED,1996-11-29 +"MET Development Inc",MDV,1996-11-29 +"Meta Watch Ltd",MTA,2013-08-29 +"Metheus Corporation",MET,1996-11-29 +"Metricom Inc",MCM,1996-11-29 +"Metronics Inc",QCH,1996-11-29 +"Mettler Toledo",NET,1996-11-29 +"Metz-Werke GmbH & Co KG",MCE,2005-06-30 +"M-G Technology Ltd",MGL,1997-10-29 +"Micom Communications Inc",MIC,1997-05-05 +"Micomsoft Co., Ltd.",MSX,2008-04-10 +"Micro Computer Systems",MCS,1996-11-29 +"Micro Design Inc",MDI,1998-01-20 +"Micro Display Systems Inc",MDS,1996-11-29 +"Micro Firmware",MFI,1997-12-30 +"Micro Industries",MCC,2003-04-21 +"Micro Solutions, Inc.",BPD,2000-04-19 +"Micro Systemation AB",MSA,1999-11-08 +"Micro Technical Company Ltd",JMT,1996-11-29 +"Microbus PLC",MBD,2002-08-13 +Microcom,MNP,1996-11-29 +"MicroDatec GmbH",MDX,1999-09-13 +"MicroDisplay Corporation",MRD,2007-06-14 +"Microdyne Inc",MDY,1996-12-18 +"MicroField Graphics Inc",MFG,1996-11-29 +Microlab,MPJ,1997-05-23 +Microline,LAF,1999-09-13 +"Micrologica AG",MLG,1998-10-06 +"Micromed Biotecnologia Ltd",MMD,1996-12-11 +"Micromedia AG",MMA,1997-04-24 +"Micron Electronics Inc",MCN,1997-02-20 +"Micronics Computers",MCI,1996-11-29 +micronpc.com,MIP,2000-08-10 +"Micronyx Inc",MYX,1996-11-29 +"Micropix Technologies, Ltd.",MPX,2001-10-08 +"MicroSlate Inc.",MSL,1999-05-16 +Microsoft,PNP,2004-03-05 +Microsoft,MSH,1996-11-29 +Microsoft,PNG,1996-11-29 +MicroSoftWare,WBN,1998-01-14 +Microstep,MSI,1996-11-29 +Microtec,MCT,1996-11-29 +"Micro-Tech Hearing Instruments",MTH,1997-12-15 +"MICROTEK Inc.",MKT,2005-07-14 +"Microtek International Inc.",MTK,2002-02-25 +"MicroTouch Systems Inc",MSY,2000-08-10 +Microvision,MVS,2009-02-13 +"Microvitec PLC",MVD,1996-11-29 +"Microway Inc",MWY,1996-11-29 +"Midori Electronics",MDC,1996-11-29 +"Mikroforum Ring 3",SFT,2004-11-02 +"Milestone EPE",MLS,1998-08-11 +"Millennium Engineering Inc",MLM,1996-11-29 +"Millogic Ltd.",MLL,2014-01-09 +"Millson Custom Solutions Inc.",MCX,2013-10-17 +"Miltope Corporation",VTM,2009-09-23 +"Mimio – A Newell Rubbermaid Company",MIM,2012-07-31 +"MindTech Display Co. Ltd",MTD,2007-06-14 +"MindTribe Product Engineering, Inc.",FTW,2011-02-14 +"Mini Micro Methods Ltd",MNC,1996-11-29 +"Minicom Digital Signage",MIN,2010-08-13 +"MiniMan Inc",MMN,1996-11-29 +"Minnesota Mining and Manufacturing",MMF,2001-03-15 +"Miranda Technologies Inc",MRA,1996-11-29 +Miratel,MRL,1998-10-16 +"Miro Computer Prod.",MIR,1996-11-29 +"miro Displays",MID,1999-03-20 +"Mistral Solutions [P] Ltd.",MSP,1998-09-23 +"Mitec Inc",MII,1996-11-29 +"Mitel Corporation",MTL,1997-08-01 +"Mitron computer Inc",MTR,1996-11-29 +"Mitsubishi Electric Corporation",MEL,1996-11-29 +"Mitsubishi Electric Engineering Co., Ltd.",MEE,2005-10-03 +"Mitsumi Company Ltd",KMC,1996-11-29 +"MJS Designs",MJS,1996-11-29 +"MK Seiko Co., Ltd.",MKS,2013-06-18 +"M-Labs Limited",OHW,2013-11-27 +"MMS Electronics",MMS,1998-02-24 +"Modesto PC Inc",FST,1997-02-27 +MODIS,MDD,1999-11-08 +"Modular Industrial Solutions Inc",MIS,1996-11-29 +"Modular Technology",MOD,1997-06-09 +"Momentum Data Systems",MOM,2008-01-18 +"Monorail Inc",MNL,1997-02-18 +Monydata,MYA,1996-11-29 +"Moreton Bay",MBV,2000-01-13 +"Moses Corporation",MOS,1996-11-29 +"Mosgi Corporation",MSV,1996-11-29 +"Motion Computing Inc.",MCO,2002-05-30 +Motium,MTM,2012-06-19 +motorola,MSU,2001-03-15 +"Motorola Communications Israel",MCL,2002-07-02 +"Motorola Computer Group",MCG,1997-08-14 +"Motorola UDS",MOT,1996-11-29 +"Mouse Systems Corporation",MSC,1996-11-29 +"M-Pact Inc",MPC,1996-11-29 +"mps Software GmbH",MPS,1996-11-29 +"MS Telematica",MST,1997-04-28 +"MSC Vertriebs GmbH",MEX,2012-06-04 +"MSI GmbH",MSG,1999-09-13 +"M-Systems Flash Disk Pioneers",MSF,1997-12-17 +"Mtron Storage Technology Co., Ltd.",MTN,2008-06-17 +"Multi-Dimension Institute",MUD,2000-10-23 +Multimax,MMI,1996-11-29 +"Multi-Tech Systems",MTS,1996-11-29 +"Multiwave Innovation Pte Ltd",MWI,1996-11-29 +"Mutoh America Inc",MAI,1999-09-13 +mware,MWR,2001-04-24 +"Mylex Corporation",MLX,1996-11-29 +"Myriad Solutions Ltd",MYR,1996-11-29 +"Myse Technology",WYS,1996-11-29 +"N*Able Technologies Inc",NBL,1998-04-28 +"NAD Electronics",NAD,2007-06-14 +"Naitoh Densei CO., LTD.",NDK,2006-04-12 +"Najing CEC Panda FPD Technology CO. ltd",NCP,2015-02-24 +"Nakano Engineering Co.,Ltd.",NAK,2009-07-22 +"Nakayo Relecommunications, Inc.",NYC,2000-08-10 +"Nanomach Anstalt",SCS,1996-11-29 +"Nasa Ames Research Center",ADR,1996-11-29 +"National DataComm Corporaiton",NDC,1996-11-29 +"National Display Systems",NDI,2003-08-08 +"National Instruments Corporation",NIC,1996-11-29 +"National Key Lab. on ISN",NBS,1998-07-16 +"National Semiconductor Corporation",NSC,1996-11-29 +"National Semiconductor Japan Ltd",TTB,1997-04-14 +"National Transcomm. Ltd",NTL,1996-11-29 +"Nationz Technologies Inc.",ZIC,2009-03-12 +"Natural Micro System",NMS,1996-11-29 +"NaturalPoint Inc.",NAT,2010-09-03 +"Navatek Engineering Corporation",NVT,1998-03-02 +"Navico, Inc.",NME,2012-11-28 +"Navigation Corporation",NAV,1999-02-22 +"Naxos Tecnologia",NAX,1997-12-12 +"NCR Corporation",DUN,2002-04-25 +"NCR Corporation",NCC,1996-11-29 +"NCR Electronics",NCR,1996-11-29 +"NDF Special Light Products B.V.",NDF,2014-09-18 +"NDS Ltd",DMV,1997-06-25 +"NEC Corporation",NEC,2000-05-24 +"NEC CustomTechnica, Ltd.",NCT,2002-10-23 +"NEC-Mitsubishi Electric Visual Systems Corporation",NMV,2002-02-25 +"NEO TELECOM CO.,LTD.",NEO,1999-11-08 +Neomagic,NMX,1996-11-29 +"NeoTech S.R.L",NTC,1997-11-11 +"Netaccess Inc",NTX,1997-02-07 +"NetComm Ltd",NCL,1996-11-29 +"NetVision Corporation",NVC,1996-11-29 +"Network Alchemy",NAL,1997-09-30 +"Network Designers",NDL,1996-11-29 +"Network General",NGC,1997-08-26 +"Network Info Technology",NIT,1996-11-29 +"Network Peripherals Inc",NPI,1996-11-29 +"Network Security Technology Co",NST,1999-02-22 +"Networth Inc",NTW,1996-11-29 +"NeuroSky, Inc.",NSA,2013-08-28 +"NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA",NEU,2001-03-15 +"New Tech Int'l Company",NTI,1996-11-29 +"NewCom Inc",NCI,1997-01-09 +"Newisys, Inc.",NWS,2002-10-08 +"Newport Systems Solutions",NSS,1996-11-29 +Nexgen,NXG,1996-11-29 +"Nexgen Mediatech Inc.,",NEX,2003-11-11 +"Nexiq Technologies, Inc.",NXQ,2001-10-08 +"Next Level Communications",NLC,1996-11-29 +"NextCom K.K.",NXC,1996-11-29 +"NingBo Bestwinning Technology CO., Ltd",NBT,2006-09-05 +"NINGBO BOIGLE DIGITAL TECHNOLOGY CO.,LTD",BOI,2009-11-25 +"Nippon Avionics Co.,Ltd",AVI,2000-10-23 +"NIPPONDENCHI CO,.LTD",GSB,2000-05-24 +"NISSEI ELECTRIC CO.,LTD",NSI,2000-01-13 +"Nissei Electric Company",NIS,1996-11-29 +"Nits Technology Inc.",NTS,2006-12-19 +"Nixdorf Company",NCA,1996-11-29 +NNC,NNC,1996-11-29 +"Nokia Data",NDS,1996-11-29 +"Nokia Display Products",NOK,1996-11-29 +"Nokia Mobile Phones",NMP,1996-11-29 +"Norand Corporation",NOR,1997-03-19 +"Norcent Technology, Inc.",NCE,2007-06-20 +"NordicEye AB",NOE,2009-09-23 +"North Invent A/S",NOI,2010-05-04 +"Northgate Computer Systems",NCS,1996-11-29 +"Not Limited Inc",NOT,1998-01-30 +"NovaWeb Technologies Inc",NWP,1998-06-12 +"Novell Inc",NVL,1996-11-29 +"Nspire System Inc.",NSP,2007-02-13 +"N-trig Innovative Technologies, Inc.",NTR,2005-10-03 +"NTT Advanced Technology Corporation",NTT,2004-08-19 +"NU Inc.",NUI,2007-08-29 +"NU Technology, Inc.",NUG,2004-04-16 +"Number Five Software",NFS,1999-02-22 +"Nutech Marketing PTL",KNX,1996-11-29 +"NuVision US, Inc.",NVI,2006-09-05 +"Nuvoton Technology Corporation",NTN,2008-10-09 +Nvidia,NVD,1996-11-29 +N-Vision,JEN,2000-10-23 +"NXP Semiconductors bv.",NXP,2007-06-14 +"NW Computer Engineering",NWC,1997-02-03 +"Oak Tech Inc",OAK,1996-11-29 +"Oasys Technology Company",OAS,1996-11-29 +"OBJIX Multimedia Corporation",OMC,1996-11-29 +"OCTAL S.A.",PCB,1998-02-24 +"Oculus VR, Inc.",OVR,2012-10-19 +Odrac,ODR,2001-06-21 +"Office Depot, Inc.",ATV,2007-06-13 +"OKI Electric Industrial Company Ltd",OKI,1996-11-29 +"Oksori Company Ltd",OQI,1996-11-29 +"Oksori Company Ltd",OSR,1996-11-29 +Olfan,OCN,1996-11-29 +"Olicom A/S",OLC,1996-11-29 +"Olidata S.p.A.",OLD,2006-03-13 +"Olitec S.A.",OLT,1996-11-29 +"Olitec S.A.",OLV,1996-11-29 +Olivetti,OLI,1996-11-29 +"OLYMPUS CORPORATION",OLY,2005-05-02 +OmniTek,OTK,2013-09-19 +Omnitel,OMN,1998-04-28 +"Omron Corporation",OMR,1996-11-29 +"On Systems Inc",ONS,1996-11-29 +"Oneac Corporation",ONE,1998-04-14 +"ONKYO Corporation",ONK,2005-06-16 +"OnLive, Inc",ONL,2010-09-03 +"OOO Technoinvest",TIV,1997-08-05 +"Opcode Inc",OPC,1996-11-29 +"Open Connect Solutions",OCS,1999-09-13 +"OPEN Networks Ltd",ONW,2003-04-25 +"Open Stack, Inc.",OSI,2013-07-22 +"OPPO Digital, Inc.",OPP,2012-06-19 +"OPTi Inc",OPT,1996-11-29 +"Optibase Technologies",OBS,2010-11-01 +"Optical Systems Design Pty Ltd",OSD,2013-06-03 +"Option Industrial Computers",OIC,2001-05-07 +"Option International",OIN,2000-10-23 +"Option International",OIM,1997-01-30 +"OPTI-UPS Corporation",OSP,1997-07-01 +"Optivision Inc",OPV,1996-11-29 +"OPTO22, Inc.",OTT,1998-10-06 +"Optoma Corporation          ",OTM,2010-04-20 +"Optum Engineering Inc.",OEI,2010-08-02 +"Orchid Technology",OTI,1996-11-29 +"ORGA Kartensysteme GmbH",ORG,1998-10-24 +"Orion Communications Co., Ltd.",TOP,2007-04-30 +"ORION ELECTRIC CO., LTD.",ORN,2005-01-19 +"ORION ELECTRIC CO.,LTD",OEC,2000-01-13 +"OSAKA Micro Computer, Inc.",OSA,2003-09-05 +"OSR Open Systems Resources, Inc.",ORI,1999-01-20 +OSRAM,OOS,2002-04-25 +"OUK Company Ltd",OUK,1996-11-29 +outsidetheboxstuff.com,OTB,2010-09-03 +"Oxus Research S.A.",OXU,1996-11-29 +"OZ Corporation",OZC,2012-08-07 +"Pacific Avionics Corporation",PAC,1996-11-29 +"Pacific CommWare Inc",PCW,1996-11-29 +"Pacific Image Electronics Company Ltd",PIE,1997-10-21 +"Packard Bell Electronics",PBL,1996-11-29 +"Packard Bell NEC",PBN,1996-11-29 +"PACSGEAR, Inc.",PGI,2012-08-13 +"Padix Co., Inc.",QFF,1999-09-13 +"Pan Jit International Inc.",PJT,2004-08-03 +Panasonic,MDO,1996-11-29 +"Panasonic Avionics Corporation",PLF,2010-08-13 +"Panasonic Industry Company",MEI,1996-11-29 +"Panelview, Inc.",PNL,2003-08-04 +"Pantel Inc",PTL,1996-11-29 +"PAR Tech Inc.",PTA,2011-01-26 +"Parade Technologies, Ltd.",PRT,2012-04-06 +"Paradigm Advanced Research Centre",PGM,2005-06-16 +"Parallan Comp Inc",PAR,1996-11-29 +"Parallax Graphics",PLX,1996-11-29 +"Parc d'Activite des Bellevues",RCE,1996-11-29 +Parrot,POT,2014-11-25 +"Pathlight Technology Inc",PTH,1996-11-29 +"PC Xperten",PCX,1998-02-24 +PCBANK21,PCK,2007-02-13 +"PCM Systems Corporation",PCM,1997-03-25 +"PC-Tel Inc",PCT,1997-05-02 +"PD Systems International Ltd",PDS,1999-03-20 +"PDTS - Prozessdatentechnik und Systeme",PDT,1998-02-10 +"Pegatron Corporation",PEG,2013-08-27 +"PEI Electronics Inc",PEI,1998-04-06 +"Penta Studiotechnik GmbH",PVM,2010-05-05 +"pentel.co.,ltd",PCL,2002-02-25 +"Peppercon AG",PEP,2006-04-12 +"Perceptive Pixel Inc.",PPX,2010-05-04 +"Perceptive Signal Technologies",PER,1997-05-13 +PerComm,PRC,2001-04-24 +"Performance Concepts Inc.,",PCO,2002-09-24 +"Performance Technologies",IPN,2004-02-24 +"Perle Systems Limited",PSL,1999-02-22 +"Perpetual Technologies, LLC",PON,2000-01-13 +"Peter Antesberger Messtechnik",PAM,1998-04-28 +"Peus-Systems GmbH",PSD,1996-11-29 +"Philips BU Add On Card",PCA,1996-11-29 +"Philips Communication Systems",PHS,1996-11-29 +"Philips Consumer Electronics Company",PHL,1996-11-29 +"Philips Medical Systems Boeblingen GmbH",PHE,2010-04-20 +"Philips Semiconductors",PSC,1996-11-29 +"Phoenix Contact",PXC,2008-02-27 +"Phoenix Technologies, Ltd.",PNX,1999-11-08 +"Phoenixtec Power Company Ltd",PPC,1999-05-16 +"Photonics Systems Inc.",PHO,2002-06-03 +PhotoTelesis,RSC,1998-03-16 +"Phylon Communications",PHY,1996-11-29 +PicPro,PPR,2004-10-18 +"Pijnenburg Beheer N.V.",PHC,2001-04-24 +"Pioneer Computer Inc",PCI,1996-11-29 +"Pioneer Electronic Corporation",PIO,1997-07-16 +"Pitney Bowes",PBV,1999-09-13 +"Pitney Bowes",PBI,1996-11-29 +"Pixel Qi",PQI,2009-06-24 +"Pixel Vision",PVN,1996-11-29 +"PIXELA CORPORATION",PXE,2007-11-21 +"Pixie Tech Inc",PIX,1996-11-29 +"Plain Tree Systems Inc",PTS,1996-11-29 +"Planar Systems, Inc.",PNR,2003-08-11 +"PLUS Vision Corp.",PLV,2001-07-05 +"PMC Consumer Electronics Ltd",PMC,1996-12-11 +"pmns GmbH",SPR,2002-10-08 +"Point Multimedia System",PMM,1997-06-09 +"Polycom Inc.",PLY,2002-06-19 +"PolyComp (PTY) Ltd.",POL,2006-02-14 +"Polycow Productions",COW,2001-03-15 +"Portalis LC",POR,2008-11-01 +"Poso International B.V.",ARO,1997-08-01 +"POTRANS Electrical Corp.",PEC,1999-07-16 +"PowerCom Technology Company Ltd",PCC,1997-09-02 +"Powermatic Data Systems",CPX,1996-11-29 +"Practical Electronic Tools",PET,1999-02-22 +"Practical Peripherals",PPI,1996-11-29 +"Practical Solutions Pte., Ltd.",PSE,1998-10-06 +"Praim S.R.L.",PRD,1996-11-29 +"Primax Electric Ltd",PEL,1996-11-29 +"Prime Systems, Inc.",SYX,2003-10-21 +"Prime view international Co., Ltd",PVI,2009-07-06 +"Princeton Graphic Systems",PGS,1996-11-29 +"Prism, LLC",PIM,2007-07-24 +"Priva Hortimation BV",PRI,1997-10-22 +PRO/AUTOMATION,PRA,1999-07-16 +"Procomp USA Inc",PCP,1996-11-29 +"Prodea Systems Inc.",PSY,2013-02-04 +"Prodrive B.V.",PDV,2005-01-18 +Projecta,PJA,1997-01-29 +"Projectavision Inc",DHT,1998-01-14 +"Projectiondesign AS",PJD,2002-09-23 +"PROLINK Microsystems Corp.",PLM,2002-02-25 +"Pro-Log Corporation",PLC,1996-11-29 +"Promate Electronic Co., Ltd.",PMT,2003-01-13 +Prometheus,PRM,1996-11-29 +"Promise Technology Inc",PTI,1997-01-02 +"Promotion and Display Technology Ltd.",PAD,2001-04-24 +"Promotion and Display Technology Ltd.",TEL,2001-04-24 +"propagamma kommunikation",PGP,2000-04-19 +Prosum,PSM,1996-11-29 +Proteon,PRO,1996-11-29 +"Proview Global Co., Ltd",PVG,2002-10-08 +"Proxim Inc",PXM,1997-09-19 +"Proxima Corporation",PRX,1996-11-29 +"PS Technology Corporation",PTC,1997-01-29 +"Psion Dacom Plc.",PDM,1999-11-08 +"PSI-Perceptive Solutions Inc",PSI,1996-11-29 +"PT Hartono Istana Teknologi",PLT,2010-05-05 +"Pulse-Eight Ltd",PUL,2012-09-12 +"Pure Data Inc",PDR,1996-11-29 +"Purup Prepress AS",PPP,1996-11-29 +"Qingdao Haier Electronics Co., Ltd.",HRE,2006-04-12 +Q-Logic,QLC,1996-11-29 +"Qtronix Corporation",QTR,1996-11-29 +Quadram,DHQ,1996-11-29 +Quadram,QDM,1996-11-29 +"Quadrant Components Inc",QCL,1997-04-03 +"QuakeCom Company Ltd",QCC,1998-03-23 +"Qualcomm Inc",QCP,1999-05-16 +"Quanta Computer Inc",QCI,1996-11-29 +"Quanta Display Inc.",QDS,2002-04-25 +Quantum,QTM,1996-11-29 +"Quantum 3D Inc",QTD,1997-05-23 +"Quantum Data Incorporated",QDI,2001-03-15 +Quartics,QVU,2010-11-04 +"Quatographic AG",QUA,2000-01-13 +"Questech Ltd",QTH,2000-01-13 +"Questra Consulting",QUE,1998-01-30 +"Quick Corporation",QCK,1996-11-29 +"Quickflex, Inc",QFI,1998-08-04 +"Quicknet Technologies Inc",QTI,1996-11-29 +"R Squared",RSQ,1999-11-08 +R.P.T.Intergroups,RPT,1996-11-29 +"Racal Interlan Inc",RII,1996-11-29 +"Racal-Airtech Software Forge Ltd",TSF,1996-11-29 +"Racore Computer Products Inc",RAC,1996-11-29 +"Radicom Research Inc",RRI,1997-12-02 +"Radio Consult SRL",RCN,2002-09-24 +"RADIODATA GmbH",RDN,2012-07-25 +"RadioLAN Inc",RLN,1996-11-29 +"Radiospire Networks, Inc.",RSN,2007-06-14 +"Radisys Corporation",RAD,1996-11-29 +"Radius Inc",RDS,1997-03-07 +"RAFI GmbH & Co. KG",RFI,2015-08-24 +"Rainbow Displays, Inc.",RDI,1998-09-23 +"Rainbow Technologies",RNB,1996-11-29 +"Raintree Systems",RTS,2001-10-02 +"Rainy Orchard",BOB,2000-02-21 +"Rampage Systems Inc",RSI,1996-11-29 +"Rancho Tech Inc",RAN,1996-11-29 +"Rancho Tech Inc",RTI,1996-11-29 +"Rapid Tech Corporation",RSX,1996-11-29 +"Raritan Computer, Inc",RMC,1998-11-27 +"Raritan, Inc.",RAR,2007-06-14 +"RAScom Inc",RAS,1996-11-29 +"RATOC Systems, Inc.",REX,2012-01-06 +"Raylar Design, Inc.",RAY,2000-01-13 +"RC International",RCI,1996-11-29 +"Reach Technology Inc",RCH,1998-02-09 +"Reakin Technolohy Corporation",RKC,2001-03-15 +"Real D",REA,2007-11-15 +"Realtek Semiconductor Company Ltd",RTL,1996-11-29 +"Realtek Semiconductor Corp.",ALG,2002-10-25 +"Realvision Inc",RVI,1996-11-29 +ReCom,REC,1999-05-16 +"Red Wing Corporation",RWC,1998-01-08 +"Redfox Technologies Inc.",RFX,2014-01-14 +"Reflectivity, Inc.",REF,2000-04-19 +"Rehan Electronics Ltd.",REH,2012-02-15 +"Relia Technologies",RTC,1996-11-29 +"Reliance Electric Ind Corporation",REL,1996-11-29 +"Renesas Technology Corp.",REN,2007-06-14 +Rent-A-Tech,RAT,1999-02-22 +"Research Electronics Development Inc",RED,1997-12-02 +"Research Machines",RMP,1996-11-29 +"ResMed Pty Ltd",RES,2000-02-21 +"Resonance Technology, Inc.",RET,2011-02-09 +"Restek Electric Company Ltd",WTS,1996-11-29 +"Reveal Computer Prod",RVL,1996-11-29 +"Revolution Display, Inc.",REV,2014-03-19 +"RGB Spectrum",RGB,2012-11-14 +"RGB Systems, Inc. dba Extron Electronics",EXN,2008-07-06 +"RICOH COMPANY, LTD.",RIC,2010-05-13 +"RightHand Technologies",RHD,2012-05-01 +"Rios Systems Company Ltd",RIO,1996-11-29 +"Ritech Inc",RIT,1998-04-14 +"Rivulet Communications",RIV,2007-07-19 +"Robert Bosch GmbH",BSG,2014-05-15 +"Robert Gray Company",GRY,1998-03-31 +"Robertson Geologging Ltd",RGL,2000-08-10 +"Robust Electronics GmbH",ROB,2008-01-18 +"Rockwell Automation/Intecolor",RAI,1998-03-13 +"Rockwell Collins",RCO,2010-09-10 +"Rockwell Collins / Airshow Systems",ASY,2004-12-02 +"Rockwell Collins, Inc.",COL,2007-06-14 +"Rockwell International",ROK,1996-11-29 +"Rockwell Semiconductor Systems",RSS,1996-11-29 +"Rogen Tech Distribution Inc",MAX,1996-11-29 +"Rohde & Schwarz",ROS,2012-01-20 +"Rohm Co., Ltd.",ROH,2004-06-16 +"Rohm Company Ltd",RHM,1997-05-13 +"Roland Corporation",RJA,1996-11-29 +"RoomPro Technologies",RPI,2010-07-09 +"Roper International Ltd",ROP,1999-05-16 +"Roper Mobile",RMT,2010-07-02 +"Ross Video Ltd",RSV,2012-06-11 +"Royal Information",TRL,1996-11-29 +"Rozsnyó, s.r.o.",RZS,2014-03-24 +"RSI Systems Inc",RVC,1998-04-28 +"RUNCO International",RUN,2004-04-01 +"S&K Electronics",SNK,2000-02-21 +"S3 Inc",TLV,1997-01-07 +"S3 Inc",SIM,1996-11-29 +"S3 Inc",SSS,1996-11-29 +"Saab Aerotech",SAE,2007-06-14 +"Sage Inc",SAI,1997-07-16 +SAGEM,SGM,2003-09-05 +SAIT-Devlonics,SDK,1996-11-29 +"Saitek Ltd",SAK,1999-05-16 +"Salt Internatioinal Corp.",SLT,2006-09-05 +"Samsung Electric Company",SAM,1996-11-29 +"Samsung Electro-Mechanics Company Ltd",SKT,1996-11-29 +"Samsung Electronics America",STN,2000-08-10 +"Samsung Electronics America Inc",KYK,1998-02-24 +"Samsung Electronic Co.",SSE,2000-08-10 +"Samsung Electronics Company Ltd",SEM,1996-11-29 +"Samtron Displays Inc",SDI,1996-11-29 +"SANKEN ELECTRIC CO., LTD",JSK,1999-09-13 +"Sankyo Seiki Mfg.co., Ltd",SSJ,2003-01-28 +"Sanritz Automation Co.,Ltd.",SAA,2002-02-25 +"SANTAK CORP.",STK,1998-11-27 +"Santec Corporation",SOC,2015-01-12 +"Sanyo Electric Co.,Ltd.",SAN,1999-11-08 +"Sanyo Electric Company Ltd",SCD,1996-11-29 +"Sanyo Electric Company Ltd",SIB,1996-11-29 +"Sanyo Electric Company Ltd",TSC,1996-11-29 +"Sanyo Icon",ICN,1996-11-29 +"Sapience Corporation",SPN,1996-11-29 +"SAT (Societe Anonyme)",SDA,1996-11-29 +"SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.)",AVV,2002-12-17 +"SBS-or Industrial Computers GmbH",SBS,1998-12-28 +"Scan Group Ltd",SGI,1996-11-29 +"Scanport, Inc.",SCN,2002-08-05 +"SCD Tech",KFC,2002-10-23 +"Sceptre Tech Inc",SPT,1996-11-29 +Schlumberger,SMB,1999-07-16 +"Schlumberger Cards",SCH,1998-04-28 +"Schlumberger Technology Corporate",SLR,2000-08-10 +"Schneider & Koch",SKD,1996-11-29 +"Schneider Electric S.A.",MGE,1996-11-29 +"Schnick-Schnack-Systems GmbH",SLS,2009-05-06 +"SCI Systems Inc.",REM,2000-08-10 +"SCM Microsystems Inc",SCM,1996-11-29 +"Scriptel Corporation",SCP,2007-06-14 +"SDR Systems",SDR,2001-03-15 +"SDS Technologies",STY,1999-11-08 +"SDX Business Systems Ltd",SDX,1996-11-29 +"Seanix Technology Inc",NIX,2007-04-09 +"Seanix Technology Inc.",SEA,1998-02-24 +Sedlbauer,SAG,1996-11-29 +"SeeColor Corporation",SEE,1996-11-29 +"SeeCubic B.V.",SCB,2012-11-02 +"SeeReal Technologies GmbH",SRT,2005-06-27 +"Seiko Epson Corporation",SEC,1996-11-29 +"Seiko Instruments Information Devices Inc",SID,1996-12-16 +"Seiko Instruments USA Inc",SIU,1996-11-29 +"Seitz & Associates Inc",SEI,1998-01-30 +"Sejin Electron Inc",SJE,1997-08-20 +"SELEX GALILEO",SXG,2012-10-01 +"Semtech Corporation",STH,2001-11-30 +"SendTek Corporation",SET,1999-11-08 +"Senseboard Technologies AB",SBT,2002-09-03 +Sencore,SEN,1997-05-23 +"Sentelic Corporation",STU,2012-07-27 +"SEOS Ltd",SEO,2003-02-20 +"Sentronic International Corp.",SNC,2000-10-23 +"SEP Eletronica Ltda.",SEP,2001-05-07 +"Sequent Computer Systems Inc",SQT,1996-11-29 +"Session Control LLC",SES,2010-09-03 +Setred,SRD,2006-09-05 +"SEVIT Co., Ltd.",SVT,2002-06-25 +SGEG,SVA,2000-02-21 +"Seyeon Tech Company Ltd",SYT,1997-12-02 +"SGS Thomson Microelectronics",STM,1997-11-11 +"Shadow Systems",OYO,1996-11-29 +"Shanghai Bell Telephone Equip Mfg Co",SBC,1998-04-30 +"Shanghai Guowei Science and Technology Co., Ltd.",SGW,2011-01-28 +"SHANGHAI SVA-DAV ELECTRONICS CO., LTD",XQU,2003-07-24 +"Sharedware Ltd",SWL,1998-08-11 +"Shark Multimedia Inc",SMM,1996-11-29 +"SharkTec A/S",DFK,2006-02-14 +"Sharp Corporation",SHP,1996-11-29 +"SHARP TAKAYA ELECTRONIC INDUSTRY CO.,LTD.",SXT,2010-06-24 +"Shenzhen ChuangZhiCheng Technology Co., Ltd.",CZC,2013-10-23 +"Shenzhen Inet Mobile Internet Technology Co., LTD",IXN,2014-11-04 +"Shenzhen MTC Co., Ltd",SZM,2013-08-09 +"Shenzhen Ramos Digital Technology Co., Ltd",RMS,2014-10-29 +"Shenzhen South-Top Computer Co., Ltd.",SSL,2013-12-06 +"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",AZH,2013-09-17 +"Shenzhen Zhuona Technology Co., Ltd.",XYE,2013-10-01 +"Shenzhen ZhuoYi HengTong Computer Technology Limited",HTR,2013-12-13 +"Shenzhen Zowee Technology Co., LTD",ZWE,2015-05-26 +"Sherwood Digital Electronics Corporation",SDE,1996-11-29 +"ShibaSoku Co., Ltd.",SHC,2005-05-26 +"Shin Ho Tech",SHT,1996-11-29 +"Shlumberger Ltd",SLB,1996-11-29 +"Shuttle Tech",SAT,1996-11-29 +"Sichuan Changhong Electric CO, LTD.",CHG,2003-02-26 +"Sichuang Changhong Corporation",CHO,2001-11-30 +Siemens,SIE,1996-11-29 +"Siemens AG",SDT,2006-02-14 +"SIEMENS AG",SIA,2001-03-15 +"Siemens Microdesign GmbH",SNI,1996-11-29 +"Siemens Nixdorf Info Systems",SNP,1996-11-29 +"Sierra Semiconductor Inc",SSC,1996-11-29 +"Sierra Wireless Inc.",SWI,2003-07-10 +"Sigma Designs Inc",SIG,1996-11-29 +"Sigma Designs, Inc.",SGD,2006-02-14 +"Sigmacom Co., Ltd.",SCL,2002-04-25 +"SigmaTel Inc",STL,1997-03-03 +Signet,DXS,2000-10-23 +"SII Ido-Tsushin Inc",STE,1997-04-03 +"Silcom Manufacturing Tech Inc",SMT,1996-11-29 +"Silex technology, Inc.",SXD,2009-03-12 +"Silicom Multimedia Systems Inc",SMS,1996-12-04 +"Silicon Graphics Inc",SGX,1996-11-29 +"Silicon Image, Inc.",SII,2000-01-13 +"Silicon Integrated Systems Corporation",SIS,1996-11-29 +"Silicon Laboratories, Inc",SIL,1998-07-16 +"Silicon Library Inc.",SLH,2008-11-01 +"Silicon Optix Corporation",SOI,2005-07-28 +"Silitek Corporation",SLK,1997-07-16 +"SIM2 Multimedia S.P.A.",SPU,2002-09-05 +"Simple Computing",SMP,1996-11-29 +"Simplex Time Recorder Co.",SPX,2001-03-15 +"Singular Technology Co., Ltd.",SIN,1999-11-08 +"SINOSUN TECHNOLOGY CO., LTD",SNO,2005-06-27 +"Sirius Technologies Pty Ltd",SIR,1998-03-13 +"sisel muhendislik",FUN,2002-04-25 +"SITECSYSTEM CO., LTD.",STS,2005-03-16 +Sitintel,SIT,1996-11-29 +"SKYDATA S.P.A.",SKY,1997-09-19 +"Smart Card Technology",SCT,2000-08-10 +"SMART Modular Technologies",SMA,1997-04-04 +"Smart Silicon Systems Pty Ltd",SPL,2000-08-10 +"Smart Tech Inc",STI,1996-11-29 +"SMART Technologies Inc.",SBI,2007-06-14 +"SMK CORPORATION",SMK,2000-02-21 +"Snell & Wilcox",SNW,2002-04-25 +"SOBO VISION",MVM,2007-06-14 +"Socionext Inc.",SCX,2015-05-14 +"Sodeman Lancom Inc",LAN,1996-11-29 +"SODIFF E&T CO., Ltd.",SDF,2007-06-01 +"Soft & Hardware development Goldammer GmbH",SHG,1996-11-29 +"Softbed - Consulting & Development Ltd",SBD,1997-12-23 +"Software Café",SWC,1996-11-29 +"Software Technologies Group,Inc.",SWT,2008-11-29 +"Solitron Technologies Inc",SOL,1996-11-29 +"Solomon Technology Corporation",SLM,1998-01-16 +SolutionInside,SXL,2001-05-08 +"SOMELEC Z.I. Du Vert Galanta",ONX,1996-11-29 +Sonitronix,HON,2011-02-03 +"Sonix Comm. Ltd",SNX,1996-11-29 +Sony,SNY,1996-11-29 +Sony,SON,1996-11-29 +"Sony Ericsson Mobile Communications Inc.",SER,2004-04-16 +"SORCUS Computer GmbH",SCO,2000-01-13 +"Sorcus Computer GmbH",SOR,1996-11-29 +"SORD Computer Corporation",SCC,1996-11-29 +"Sotec Company Ltd",SOT,1997-05-21 +"South Mountain Technologies, LTD",FRS,2006-02-14 +"SOYO Group, Inc",SOY,2006-12-18 +"SPACE-I Co., Ltd.",SPI,2005-05-11 +"SpaceLabs Medical Inc",SMI,1996-11-29 +"SPEA Software AG",SPE,1996-11-29 +SpeakerCraft,SPK,2010-04-20 +Specialix,SLX,1996-11-29 +"Spectragraphics Corporation",SGC,1996-11-29 +"Spectrum Signal Proecessing Inc",SSP,1996-11-29 +"SR-Systems e.K.",SRS,2012-11-19 +"S-S Technology Inc",SSI,1996-11-29 +"ST Electronics Systems Assembly Pte Ltd",STA,1998-12-28 +"STAC Electronics",STC,1996-11-29 +"Standard Microsystems Corporation",SMC,1996-11-29 +"Star Paging Telecom Tech (Shenzhen) Co. Ltd.",STT,1998-09-23 +"Starflight Electronics",STF,1997-05-23 +"Stargate Technology",SGT,1996-11-29 +StarLeaf,SLF,2010-11-01 +"Starlight Networks Inc",STR,1996-11-29 +"Starwin Inc.",STW,2001-04-24 +Static,SWS,1999-05-16 +"STB Systems Inc",STB,1996-11-29 +"STD Computer Inc",STD,1996-11-29 +"StereoGraphics Corp.",STG,2001-10-02 +ST-Ericsson,STX,2011-12-09 +STMicroelectronics,SMO,2007-06-14 +"Stollmann E+V GmbH",STO,1997-03-27 +"Stores Automated Systems Inc",SAS,1997-03-19 +"Storm Technology",EZP,1996-10-17 +"StreamPlay Ltd",STP,2009-02-04 +"Stryker Communications",SYK,2005-10-10 +"Subspace Comm. Inc",SUB,1996-11-29 +"Sumitomo Metal Industries, Ltd.",SML,1999-09-13 +"Summagraphics Corporation",SUM,1996-11-29 +"Sun Corporation",SCE,1996-11-29 +"Sun Electronics Corporation",SUN,1996-11-29 +"Sun Microsystems",SVI,2003-01-13 +"SUNNY ELEKTRONIK",SNN,2014-11-14 +"SunRiver Data System",SDS,1996-11-29 +"Super Gate Technology Company Ltd",SGL,1997-12-30 +"SuperNet Inc",SNT,1998-04-23 +"Supra Corporation",SUP,1996-11-29 +"Surenam Computer Corporation",SUR,1996-11-29 +"Surf Communication Solutions Ltd",SRF,1998-03-23 +"SVD Computer",SVD,1998-04-14 +SVSI,SVS,2008-08-09 +"SY Electronics Ltd",SYE,2010-09-20 +"Sylvania Computer Products",SYL,1998-06-12 +"Symbios Logic Inc",SLI,1996-11-29 +"Symbol Technologies",ISA,1997-06-02 +"Symicron Computer Communications Ltd.",SYM,1996-11-29 +"Synaptics Inc",SYN,1996-11-29 +"Synopsys Inc",SPS,1996-11-29 +Syntax-Brillian,SXB,2006-05-08 +"SYPRO Co Ltd",SYP,1998-11-27 +"Sysgration Ltd",SYS,1997-04-28 +"Syslogic Datentechnik AG",SLC,1999-01-20 +"Sysmate Company",SME,1997-09-02 +"Sysmate Corporation",SIC,1997-05-05 +Sysmic,SYC,1996-11-29 +"Systec Computer GmbH",SGZ,1997-10-02 +"System Craft",SCI,1996-11-29 +"system elektronik GmbH",SEB,2000-04-19 +"Systeme Lauer GmbH&Co KG",SLA,1999-03-20 +"Systems Enhancement",UPS,1996-11-29 +"SystemSoft Corporation",SST,1996-11-29 +"Systran Corporation",SCR,1996-11-29 +"SYVAX Inc",SYV,1996-11-29 +"T+A elektroakustik GmbH",TUA,2011-01-05 +"Taicom Data Systems Co., Ltd.",TCD,2001-10-08 +"Taicom International Inc",TMR,1996-11-29 +"Taiko Electric Works.LTD",TKC,2001-03-15 +"Taiwan Video & Monitor Corporation",TVM,1996-11-29 +"Takahata Electronics Co.,Ltd.",KTD,2009-07-22 +"Tamura Seisakusyo Ltd",TAM,1997-07-17 +Tandberg,TAA,2003-10-21 +"Tandberg Data Display AS",TDD,1996-11-29 +"Tandem Computer Europe Inc",TDM,1996-11-29 +"Tandon Corporation",TCC,1996-11-29 +"Tandy Electronics",TDY,1996-11-29 +"Taskit Rechnertechnik GmbH",TAS,1997-12-15 +"Tatung Company of America Inc",TCS,1996-11-29 +"Tatung UK Ltd",VIB,1999-07-16 +"Taugagreining hf",NRV,1996-11-29 +"Taxan (Europe) Ltd",TAX,1997-03-13 +"TDK USA Corporation",PMD,1996-11-29 +TDT,TDT,1996-11-29 +"TDVision Systems, Inc.",TDV,2008-01-18 +"TEAC System Corporation",TEA,1996-11-29 +"TEC CORPORATION",CET,1998-07-16 +"TEAC America Inc",TCJ,1996-11-29 +"Tech Source Inc.",TEZ,2013-08-14 +"Techmedia Computer Systems Corporation",TMC,1998-02-10 +"Technical Concepts Ltd",TCL,1996-11-29 +"Technical Illusions Inc.",TIL,2014-02-14 +"TechniSat Digital GmbH",TSD,2005-07-14 +"Technology Nexus Secure Open Systems AB",NXS,1998-05-08 +"Technology Power Enterprises Inc",TPE,1996-11-29 +"TechnoTrend Systemtechnik GmbH",TTS,1996-11-29 +"Tecmar Inc",TEC,1996-11-29 +"Tecnetics (PTY) Ltd",TCN,1996-11-29 +"TECNIMAGEN SA",TNM,2005-05-02 +Tecnovision,TVD,2006-03-13 +"Tectona SoftSolutions (P) Ltd.,",RXT,2004-06-02 +"Teknor Microsystem Inc",TKN,1996-11-29 +"Tekram Technology Company Ltd",TRM,1996-11-29 +"Tektronix Inc",TEK,1999-05-16 +"TEKWorx Limited",TWX,2009-12-24 +"Telecom Technology Centre Co. Ltd.",TCT,1999-07-16 +"Telecommunications Techniques Corporation",TTC,1996-11-29 +"Teleforce.,co,ltd",TLF,2012-11-19 +"Teleliaison Inc",TAT,1997-04-29 +"Telelink AG",TLK,1998-09-01 +"Teleprocessing Systeme GmbH",TPS,1997-01-24 +"Teles AG",TAG,1996-11-29 +"Teleste Educational OY",TLS,1996-11-29 +"TeleVideo Systems",TSI,1996-11-29 +"Telia ProSoft AB",PFT,1999-09-13 +Telindus,TLD,1996-11-29 +"Telxon Corporation",TLX,1996-11-29 +"Tennyson Tech Pty Ltd",TNY,1996-11-29 +Teradici,TDC,2007-10-11 +"TerraTec Electronic GmbH",TER,1997-03-21 +"Texas Insturments",TXN,1996-11-29 +"Texas Microsystem",TMI,1996-11-29 +"Textron Defense System",TXT,1996-11-29 +"The Concept Keyboard Company Ltd",CKC,1997-06-02 +"The Linux Foundation",LNX,2014-04-04 +"The Moving Pixel Company",PXL,2003-11-24 +"The NTI Group",ITN,1996-11-29 +"The OPEN Group",TOG,1999-09-13 +"The Panda Project",PAN,1996-11-29 +"The Phoenix Research Group Inc",PRG,1997-09-19 +"The Software Group Ltd",TSG,1996-11-29 +"Thermotrex Corporation",TMX,1996-11-29 +Thinklogical,TLL,2015-06-01 +"Thomas-Conrad Corporation",TCO,1996-11-29 +"Thomson Consumer Electronics",TCR,1998-08-20 +"Thruput Ltd",TPT,2010-06-16 +"Thundercom Holdings Sdn. Bhd.",THN,1997-03-21 +"Tidewater Association",TWA,1996-11-29 +"Time Management, Inc.",TMM,1999-03-20 +"TimeKeeping Systems, Inc.",TKS,1998-08-31 +"Times (Shanghai) Computer Co., Ltd.",TPD,2013-12-12 +"TIPTEL AG",TIP,1998-02-24 +"Tixi.Com GmbH",TIX,1998-10-16 +"T-Metrics Inc.",TMT,2000-02-21 +"TNC Industrial Company Ltd",TNC,1998-02-27 +"Todos Data System AB",TAB,1997-08-20 +"TOEI Electronics Co., Ltd.",TOE,2001-10-02 +TONNA,TON,2012-03-14 +"Top Victory Electronics ( Fujian ) Company Ltd",TPV,1999-05-16 +"TOPRE CORPORATION",TPK,2009-02-13 +"Topro Technology Inc",TPR,1998-05-08 +"Topson Technology Co., Ltd.",TTA,1998-09-23 +"TORNADO Company",SFM,1997-04-15 +"Torus Systems Ltd",TGS,1996-11-29 +"Torus Systems Ltd",TRS,1996-11-29 +"Toshiba America Info Systems Inc",TAI,1996-11-29 +"Toshiba America Info Systems Inc",TSB,1996-11-29 +"Dynabook Inc.",TOS,1996-11-29 +"Toshiba Corporation",TTP,2015-07-07 +"Toshiba Global Commerce Solutions, Inc.",TGC,2012-06-26 +"Toshiba Matsushita Display Technology Co., Ltd",LCD,2000-05-24 +"TOSHIBA PERSONAL COMPUTER SYSTEM CORPRATION",PCS,2010-06-22 +"TOSHIBA TELI CORPORATION",TLI,2008-01-18 +"Totoku Electric Company Ltd",TTK,1996-11-29 +"Tottori Sanyo Electric",TSE,1996-11-29 +"Tottori SANYO Electric Co., Ltd.",TSL,2001-11-06 +"Touch Panel Systems Corporation",TPC,1997-09-02 +"TouchKo, Inc.",TKO,2006-01-12 +"Touchstone Technology",TOU,2001-05-07 +TouchSystems,TSY,2008-01-18 +"TOWITOKO electronics GmbH",TWK,1998-04-14 +"Transtex SA",CSB,2001-03-15 +"Transtream Inc",TST,1997-04-29 +TRANSVIDEO,TSV,2010-05-04 +Tremetrics,TRE,1997-04-24 +"Tremon Enterprises Company Ltd",RDM,1996-11-29 +"Trenton Terminals Inc",TTI,1996-11-29 +"Trex Enterprises",TRX,2000-02-21 +"Tribe Computer Works Inc",OZO,1996-11-29 +"Tricord Systems",TRI,1996-11-29 +"Tri-Data Systems Inc",TDS,1996-11-29 +"TRIDELITY Display Solutions GmbH",TTY,2010-07-19 +"Trident Microsystem Inc",TRD,1996-11-29 +"Trident Microsystems Ltd",TMS,2002-07-15 +"TriGem Computer Inc",TGI,1996-11-29 +"TriGem Computer,Inc.",TGM,2001-07-05 +"Trigem KinfoComm",TIC,2003-02-26 +"Trioc AB",TRC,2000-01-13 +"Triple S Engineering Inc",TBB,1997-09-26 +"Tritec Electronic AG",TRT,2012-01-11 +"TriTech Microelectronics International",TRA,1997-01-24 +"Triumph Board a.s.",TRB,2013-09-27 +"Trivisio Prototyping GmbH",TRV,2011-11-18 +"Trixel Ltd",TXL,2000-08-10 +"Trtheim Technology",MKV,1997-03-17 +Truevision,TVI,1996-11-29 +"TTE, Inc.",TTE,2005-01-18 +"Tulip Computers Int'l B.V.",TCI,1996-11-29 +"Turbo Communication, Inc",TBC,1998-09-01 +"Turtle Beach System",TBS,1996-11-29 +"Tut Systems",TUT,1997-08-19 +"TV Interactive Corporation",TVR,1996-11-29 +"TV One Ltd",TVO,2008-09-02 +"TV1 GmbH",TVV,2012-02-06 +"TVS Electronics Limited",TVS,2008-05-20 +"Twinhead International Corporation",TWH,1996-11-29 +"Tyan Computer Corporation",TYN,1996-11-29 +"U. S. Electronics Inc.",USE,2013-10-28 +"U.S. Naval Research Lab",NRL,1996-11-29 +"U.S. Navy",TSP,2002-10-17 +"U.S. Digital Corporation",USD,1996-11-29 +"U.S. Robotics Inc",USR,1996-11-29 +"Ubinetics Ltd.",UBL,2002-05-23 +"Ueda Japan Radio Co., Ltd.",UJR,2003-07-09 +"UFO Systems Inc",UFO,1996-11-29 +"Ultima Associates Pte Ltd",UAS,1997-01-02 +"Ultima Electronics Corporation",UEC,1998-09-01 +"Ultra Network Tech",ULT,1996-11-29 +"Umezawa Giken Co.,Ltd",UMG,2008-04-10 +"Ungermann-Bass Inc",UBI,1996-11-29 +Unicate,UNY,1998-07-21 +"Uniden Corporation",UDN,2004-10-18 +"Uniform Industrial Corporation",UIC,1996-11-29 +"Uniform Industry Corp.",UNI,2001-11-06 +UNIGRAF-USA,UFG,2008-10-09 +"Unisys Corporation",UNB,1996-11-29 +"Unisys Corporation",UNC,1996-11-29 +"Unisys Corporation",UNM,1996-11-29 +"Unisys Corporation",UNO,1996-11-29 +"Unisys Corporation",UNS,1996-11-29 +"Unisys Corporation",UNT,1996-11-29 +"Unisys DSD",UNA,1996-11-29 +"Uni-Take Int'l Inc.",WKH,2002-06-17 +"United Microelectr Corporation",UMC,1996-11-29 +Unitop,UNP,2001-11-06 +"Universal Electronics Inc",UEI,1997-08-20 +"Universal Empowering Technologies",UET,1997-09-26 +"Universal Multimedia",UMM,2001-10-08 +"Universal Scientific Industrial Co., Ltd.",USI,2003-11-04 +"University College",JGD,1996-11-29 +"Uniwill Computer Corp.",UWC,2004-04-16 +"Up to Date Tech",UTD,1996-11-29 +UPPI,UPP,1998-04-14 +"Ups Manufactoring s.r.l.",RUP,2001-03-15 +"USC Information Sciences Institute",ASD,1997-04-08 +"Utimaco Safeware AG",USA,1998-05-04 +"Vaddio, LLC",VAD,2012-11-30 +Vadem,VDM,1996-11-29 +"VAIO Corporation",VAI,2014-04-18 +"Valence Computing Corporation",VAL,1996-11-29 +"Valley Board Ltda",VBT,2001-03-15 +"ValleyBoard Ltda.",VLB,1998-04-05 +"Valve Corporation",VLV,2013-03-06 +"VanErum Group",ITI,2013-10-01 +"Varian Australia Pty Ltd",VAR,2000-04-19 +"VATIV Technologies",VTV,2006-04-12 +"VBrick Systems Inc.",VBR,2009-08-19 +VCONEX,VCX,2005-06-15 +"VDC Display Systems",VDC,2009-04-29 +"Vector Informatik GmbH",VEC,1997-09-10 +"Vector Magnetics, LLC",VCM,2006-04-12 +Vektrex,VEK,1996-12-13 +"VeriFone Inc",VFI,1998-05-29 +"Vermont Microsystems",VMI,1996-11-29 +"Vestax Corporation",VTX,2012-02-14 +"Vestel Elektronik Sanayi ve Ticaret A. S.",VES,1997-09-19 +"Via Mons Ltd.",VIM,2012-08-29 +"VIA Tech Inc",VIA,1996-11-29 +"Victor Company of Japan, Limited",VCJ,2009-02-06 +"Victor Data Systems",VDA,2000-05-24 +"Victron B.V.",VIC,1996-11-29 +"Video & Display Oriented Corporation",VDO,1996-11-29 +"Video Computer S.p.A.",URD,1998-02-24 +"Video International Inc.",JWD,2000-02-21 +"Video Products Inc",VPI,2010-05-04 +"VideoLan Technologies",VLT,1997-10-17 +VideoServer,VSI,1997-06-25 +"Videotechnik Breithaupt",VTB,2013-07-23 +"VIDEOTRON CORP.",VTN,2010-05-04 +"Vidisys GmbH & Company",VDS,1996-11-29 +"Viditec, Inc.",VDT,1999-11-08 +"ViewSonic Corporation",VSC,1996-11-29 +"Viewteck Co., Ltd.",VTK,2001-10-08 +"Viking Connectors",VIK,1996-11-29 +"Vinca Corporation",VNC,1996-11-29 +"Vinci Labs",NHT,2006-03-03 +"Vine Micros Limited",VML,2004-06-16 +"Vine Micros Ltd",VIN,2000-04-19 +"Virtual Computer Corporation",VCC,1996-11-29 +"Virtual Resources Corporation",VRC,1996-11-29 +"Vision Quest",VQ@,2009-10-26 +"Vision Systems GmbH",VSP,1996-11-29 +Visioneer,VIS,1996-11-29 +"Visitech AS",VIT,2006-09-05 +"Vislink International Ltd",VLK,2012-08-27 +"VistaCom Inc",VCI,1996-11-29 +"Visual Interface, Inc",VIR,1998-11-27 +"Vivid Technology Pte Ltd",VTL,1996-11-29 +"VIZIO, Inc",VIZ,2012-06-06 +"VLSI Tech Inc",VTI,1996-11-29 +"VMware Inc.,",VMW,2011-10-18 +"Voice Technologies Group Inc",VTG,1997-04-24 +"Vortex Computersysteme GmbH",GDT,1996-11-29 +"VPixx Technologies Inc.",VPX,2013-12-05 +"VRmagic Holding AG",VRM,2013-04-12 +"V-Star Electronics Inc.",VSR,2000-02-21 +"VTech Computers Ltd",VTS,1996-11-29 +"VTel Corporation",VTC,1996-11-29 +"Vutrix (UK) Ltd",VUT,2003-07-22 +"Vweb Corp.",VWB,2004-03-12 +"Wacom Tech",WAC,1996-11-29 +"Wallis Hamilton Industries",JPW,1999-07-16 +"Wanlida Group Co., Ltd.",MLT,2014-04-24 +"Wave Access",WAL,1996-12-13 +"Wave Systems",AWS,1996-11-29 +"Wave Systems Corporation",WVM,1997-12-05 +Wavephore,WAV,1996-11-29 +"Way2Call Communications",SEL,1997-03-20 +"WB Systemtechnik GmbH",WBS,1997-09-08 +W-DEV,WEL ,2010-11-01 +"Wearnes Peripherals International (Pte) Ltd",WPI,1998-03-31 +"Wearnes Thakral Pte",WTK,1996-11-29 +"WebGear Inc",WEB,1998-01-30 +"Westermo Teleindustri AB",WMO,2000-01-13 +"Western Digital",WDC,1996-11-29 +"Westinghouse Digital Electronics",WDE,2003-05-23 +"WEY Design AG",WEY,2004-10-18 +"Whistle Communications",WHI,1998-10-24 +"Wildfire Communications Inc",WLD,1997-02-13 +"WillNet Inc.",WNI,2000-04-19 +"Winbond Electronics Corporation",WEC,1996-11-29 +"Diebold Nixdorf Systems GmbH",WNX,2004-09-20 +"Winmate Communication Inc",WMT,2001-03-15 +"Winnov L.P.",WNV,1997-03-07 +"WiNRADiO Communications",WRC,1997-09-11 +"Wintop Technology Inc",WIN,1996-12-29 +"Wipotec Wiege- und Positioniersysteme GmbH",WWP,2014-04-08 +"WIPRO Information Technology Ltd",WIL,1996-11-29 +"Wipro Infotech",WIP,2004-01-09 +"Wireless And Smart Products Inc.",WSP,1999-03-20 +"Wisecom Inc",WCI,1996-11-29 +"Wistron Corporation",WST,2010-09-03 +"Wolfson Microelectronics Ltd",WML,1997-07-30 +"WolfVision GmbH",WVV,2012-09-18 +"Woodwind Communications Systems Inc",WCS,1996-11-29 +"Wooyoung Image & Information Co.,Ltd.",WYT,2008-01-18 +"WorkStation Tech",WTI,1996-11-29 +"World Wide Video, Inc.",WWV,1998-10-24 +"Woxter Technology Co. Ltd",WXT,2010-09-03 +"X-10 (USA) Inc",XTN,1997-02-24 +"X2E GmbH",XTE,2009-09-23 +"XAC Automation Corp",XAC,1999-02-22 +"XDM Ltd.",XDM,2010-11-22 +"Xedia Corporation",MAD,1996-11-29 +"Xilinx, Inc.",XLX,2007-08-01 +"Xinex Networks Inc",XIN,1997-02-07 +"Xiotech Corporation",XIO,1998-05-29 +"Xircom Inc",XRC,1996-11-29 +"Xitel Pty ltd",XIT,1996-11-29 +"Xirocm Inc",XIR,1996-11-29 +"XN Technologies, Inc.",XNT,2003-07-14 +XOCECO,UHB,1998-11-27 +"XORO ELECTRONICS (CHENGDU) LIMITED",XRO,2005-05-23 +"XS Technologies Inc",XST,1998-01-20 +"Xscreen AS",XSN,2006-02-14 +XSYS,XSY,1998-04-23 +"Yamaha Corporation",YMH,1996-11-29 +"Xycotec Computer GmbH",XYC,2002-09-03 +"Yasuhiko Shirai Melco Inc",BUF,1996-11-29 +"Y-E Data Inc",YED,1996-11-29 +"Yokogawa Electric Corporation",YHQ,1996-11-29 +"Ypoaz Systems Inc",TPZ,1996-11-29 +"Z Microsystems",ZMZ,2005-08-10 +"Z3 Technology",ZTT,2010-12-14 +"Zalman Tech Co., Ltd.",ZMT,2007-05-07 +"Zandar Technologies plc",ZAN,2003-12-03 +"ZeeVee, Inc.",ZAZ,2008-01-18 +"Zebra Technologies International, LLC",ZBR,2003-09-15 +"Zefiro Acoustics",ZAX,1996-11-29 +"ZeitControl cardsystems GmbH",ZCT,1999-01-20 +"ZENIC Inc.",ZEN,2015-04-17 +"Zenith Data Systems",ZDS,1996-11-29 +"Zenith Data Systems",ZGT,1996-11-29 +"Zenith Data Systems",ZSE,1996-11-29 +"Zetinet Inc",ZNI,1996-11-29 +"Zhejiang Tianle Digital Electric Co., Ltd.",TLE,2014-01-17 +"Zhong Shan City Richsound Electronic Industrial Ltd.",RSR,2015-01-27 +"Znyx Adv. Systems",ZNX,1996-11-29 +"Zoom Telephonics Inc",ZTI,1996-11-29 +"Zoran Corporation",ZRN,2005-03-31 +"Zowie Intertainment, Inc",ZOW,1999-02-22 +"ZT Group Int'l Inc.",ZTM,2007-06-14 +"ZTE Corporation",ZTE,2010-09-03 +"Zuniq Data Corporation",SIX,1996-11-29 +"Zydacron Inc",ZYD,1997-04-10 +"ZyDAS Technology Corporation",ZTC,2000-05-24 +"Zypcom Inc",ZYP,1997-03-19 +"Zytex Computers",ZYT,1996-11-29 +"Zytor Communications",HPA,2010-07-02 +"Alpha Electronics Company",AEJ,1996-11-29 +BOE,BOE,2004-12-02 +"Chaplet Systems Inc",FIR,1996-11-29 +"Chenming Mold Ind. Corp.",CMG,2003-11-14 +"coolux GmbH",COO,2010-09-30 +"Data General Corporation",DGC,1996-11-29 +Exabyte,EXA,1996-11-29 +"Herolab GmbH",HRL,1998-03-17 +"Hitachi Computer Products Inc",HCP,1996-11-29 +"Integrated Circuit Systems",ICS,1996-11-29 +Irdata,IRD,2001-04-24 +"Jewell Instruments, LLC",JWL,2001-06-21 +"MultiQ Products AB",MQP,1999-03-20 +"Ncast Corporation",NAC,2006-02-14 +"ODME Inc.",ODM,1998-09-23 +Photomatrix,PMX,1996-11-29 +"Quantum Solutions, Inc.",QSI,2000-01-13 +"Red Hat, Inc.",RHT,2011-02-17 +Zyxel,ZYX,1996-11-29 +"Carrera Computer Inc",JAZ,1994-01-01 +"Chunghwa Picture Tubes, LTD",CGA,1994-01-01 +"eMicro Corporation",EMC,1994-01-01 +"Hisense Electric Co., Ltd.",HEC,1994-01-01 +PanaScope,PNS,1994-01-01 +"SpinCore Technologies, Inc",SPC,1994-01-01 +"Sensics, Inc.",SVR,2015-08-27 +"IAdea Corporation",IAD,2015-09-10 +"Express Industrial, Ltd.",ELU,2015-09-10 +"Hewlett Packard Enterprise",HPE,2015-09-22 +"Klipsch Group, Inc",KGI,2015-09-22 +"Tek Gear",TKG,2015-10-16 +"HangZhou ZMCHIVIN",ZMC,2015-10-16 +"HTC Corportation",HVR,2015-10-16 +"Zebax Technologies",ZBX,2015-10-16 +"Guangzhou Shirui Electronics Co., Ltd.",SWO,2015-10-16 +"Picturall Ltd.",PIC,2015-11-13 +"Guangzhou Teclast Information Technology Limited",SKM,2015-11-18 +"GreenArrays, Inc.",GAC,2015-11-18 +"Thales Avionics",TAV,2015-11-18 +"Explorer Inc.",EXR,2015-11-18 +"Avegant Corporation",AVG,2015-12-02 +"MicroImage Video Systems",MIV,2015-12-08 +"ASUSTek COMPUTER INC",AUS,2015-12-21 +"Synthetel Corporation",STQ,2015-12-21 +"HP Inc.",HPN,2015-12-21 +"MicroTechnica Co.,Ltd.",MTJ,2016-01-04 +"Gechic Corporation",GEC,2016-01-04 +"MPL AG, Elektronik-Unternehmen",MEU,2016-01-15 +"Display Solution AG",DSA,2016-02-03 +"UEFI Forum",PRP,2016-02-03 +"Taitex Corporation",TTX,2016-02-03 +"EchoStar Corporation",ECH,2016-02-26 +"TCL Corporation",TOL,2016-03-30 +"ADDER TECHNOLOGY LTD",ADZ,2016-03-30 +"HKC OVERSEAS LIMITED",HKC,2016-03-30 +"KEYENCE CORPORATION",KYN,2016-03-30 +"TETRADYNE CO., LTD.",TET,2016-04-27 +"Abaco Systems, Inc.",ABS,2016-04-27 +"Meta Company",MVN,2016-05-25 +"Eizo Rugged Solutions",ERS,2016-05-25 +"VersaLogic Corporation",VLC,2016-05-25 +"CYPRESS SEMICONDUCTOR CORPORATION",CYP,2016-05-25 +"MILDEF AB",MDF,2016-06-23 +"FOVE INC",FOV,2016-07-01 +INNES,NES,2016-07-01 +"Hoffmann + Krippner GmbH",HUK,2016-07-01 +"Axell Corporation",AXE,2016-08-03 +UltiMachine,UMT,2016-08-11 +"TPK Holding Co., Ltd",KPT,2016-08-16 +"AAEON Technology Inc.",AAN,2016-09-01 +"Six15 Technologies",TDG,2016-09-14 +"Inlife-Handnet Co., Ltd.",IVR,2017-01-19 +"VR Technology Holdings Limited",DSJ,2017-01-19 +"Pimax Tech. CO., LTD",PVR,2017-02-07 +"Total Vision LTD",TVL,2017-02-07 +"Shanghai Lexiang Technology Limited",DPN,2017-02-07 +"Black Box Corporation",BBX,2017-02-28 +"TRAPEZE GROUP",TRP,2017-02-28 +"Pabian Embedded Systems",PMS,2017-02-28 +"Televic Conference",TCF,2017-02-28 +"Shanghai Chai Ming Huang Info&Tech Co, Ltd",HYL,2017-02-28 +"Techlogix Networx",TLN,2017-02-28 +"G2TOUCH KOREA",GGT,2017-05-25 +"MediCapture, Inc.",MVR,2017-05-25 +"HOYA Corporation PENTAX Lifecare Division",PNT,2017-05-25 +"christmann informationstechnik + medien GmbH & Co. KG",CHR,2017-05-25 +Tencent,TEN,2017-06-20 +"VRstudios, Inc.",VRS,2017-06-22 +"Extreme Engineering Solutions, Inc.",XES,2017-06-22 +NewTek,NTK,2017-06-22 +"BlueBox Video Limited",BBV,2017-06-22 +"Televés, S.A.",TEV,2017-06-22 +"Avatron Software Inc.",AVS,2017-08-23 +"Positivo Tecnologia S.A.",POS,2017-09-01 +"VRgineers, Inc.",VRG,2017-09-07 +"Noritake Itron Corporation",NRI,2017-11-13 +"Matrix Orbital Corporation",MOC,2017-11-13 +"Elegant Invention",EIN,2018-03-29 +"Immersive Audio Technologies France",IMF,2018-03-29 +"Lightspace Technologies",LSP,2018-03-29 +"PixelNext Inc",PXN,2018-03-29 +"VRSHOW Technology Limited",TSW,2018-03-29 +"SONOVE GmbH",SNV,2018-03-29 +"Silex Inside",SXI,2018-03-29 +"Huawei Technologies Co., Inc.",HWV,2018-04-25 +"Varjo Technologies",VRT,2017-11-17 +"Japan E.M.Solutions Co., Ltd.",JEM,2018-05-24 +"QD Laser, Inc.",QDL,2018-05-31 +"VADATECH INC",VAT,2018-07-09 +"Medicaroid Corporation",MCJ,2018-08-20 +"Razer Taiwan Co. Ltd.",RZR,2018-08-20 +"GIGA-BYTE TECHNOLOGY CO., LTD.",GBT,2018-09-05 +"Kontron GmbH",KOM,2018-09-05 +"Convergent Engineering, Inc.",CIE,2018-09-05 +"WyreStorm Technologies LLC",WYR,2018-09-05 +"Astro HQ LLC",AHQ,2018-09-05 +"QSC, LLC",QSC,2019-01-18 +"Dimension Engineering LLC",DMN,2019-02-06 +"Shenzhen Dlodlo Technologies Co., Ltd.",DLO,2019-04-29 +"LENOVO BEIJING CO. LTD.",VLM,2019-05-21 +"Cammegh Limited",CRW,2019-06-18 +"Beihai Century Joint Innovation Technology Co.,Ltd",LHC,2019-09-10 +"Findex, Inc.",FDX,2019-10-22 +"Express Luck, Inc.",ELD,2019-10-22 +"LLC SKTB “SKIT”",SKI,2019-10-22 +"WOLF Advanced Technology",WLF,2019-10-22 +"BILD INNOVATIVE TECHNOLOGY LLC",BLD,2019-10-22 +"MIMO Monitors",MMT,2019-10-22 +Icron,ICR,2019-10-22 +"TECNART CO.,LTD.",PIS,2019-10-22 +"Moxa Inc.",MHQ,2019-10-22 +"Disguise Technologies",DSG,2019-10-22 +"Comark LLC",CMK,2020-07-15 +"Megapixel Visual Realty",MPV,2020-07-15 +Skyworth,SKW,2020-07-15 +"Meta View, Inc.",CFR,2020-07-15 +MILCOTS,MLC,2020-07-15 +"NZXT (PNP same EDID)_",NXT,2020-07-15 +"Unicompute Technology Co., Ltd.",UTC,2020-10-19 +"TECHNOGYM S.p.A.",TGW,2021-01-08 +"Clover Electronics",CLR,2021-02-02 +"Kyokko Communication System Co., Ltd.",KTS,2021-02-18 +"Terumo Corporation",TMO,2021-02-02 +"Micro-Star Int'l Co., Ltd.",CND,2021-02-17 +"Newline Interactive Inc.",NWL,2020-12-03 +"CORSAIR MEMORY Inc.",CRM,2021-02-05 +aviica,VAV,2021-06-01 +Monoprice.Inc,DMG,2021-06-04 +"Shenzhen KTC Technology Group",SKG,2021-06-01 +"Truly Semiconductors Ltd.",TLY,2021-06-01 +"Hitevision Group",HHT,2021-03-08 +"DLOGIC Ltd.",DLM,2021-06-10 +"Fun Technology Innovation INC.",FUL,2021-06-10 +"Guangxi Century Innovation Display Electronics Co., Ltd",IOC,2021-06-10 +"ICC Intelligent Platforms GmbH",EMR,2021-06-10 +"New H3C Technology Co., Ltd.",NHC,2021-06-10 +"Seco S.p.A.",SCG,2021-06-10 +"Silent Power Electronics GmbH",LCP,2021-06-10 +"NAFASAE INDIA Pvt. Ltd",NAF,2021-06-18 +"Pico Technology Inc.",PIR,2021-06-18 +"Life is Style Inc.",LIS,2021-06-18 +"Hansung Co., Ltd",HSN,2021-06-18 +"Hubei Century Joint Innovation Technology Co.Ltd",TTR,2021-06-18 +"Zake IP Holdings LLC (3B tech)",VIO,2021-06-18 +"PreSonus Audio Electronics",PAE,2021-06-24 +"C&T Solution Inc.",MXM,2021-07-21 +VARCem,VCE,2021-07-21 +"Nextorage Corporation",NXR,2021-09-21 +"Netvio Ltd.",NVO,2021-09-21 +"Beijing Guochengwantong Information Technology Co., Ltd.",STV,2021-09-21 +"Kopin Corporation",KOP,2021-10-01 +"Anker Innovations Limited",AKR,2021-12-10 +"SAMPO CORPORATION",SPO,2021-12-10 +"Shiftall Inc.",SFL,2021-12-31 +AudioControl,AUD,2021-12-31 +"Schneider Consumer Group",SCA,2022-02-08 +"NOLO CO., LTD.",NVR,2022-04-08 +"Riedel Communications Canada Inc.",RDL,2022-04-08 +"arpara Technology Co., Ltd.",IMX,2022-04-08 +Nreal,MRG,2022-04-08 +"Venetex Corporation",VNX,2022-04-08 +G.VISION,GVS,2022-06-17 +"Galaxy Microsystems Ltd.",GXL,2022-06-17 +"OZO Co.Ltd",OZD,2022-06-17 +"GoUp Co.,Ltd",GUP,2022-06-24 +"Eizo Technologies GmbH",ETG,2022-06-24 +"Steelseries ApS",SSG,2022-09-23 +"Weidmuller Interface GmbH & Co. KG",WMI,2022-09-23 +"Bigscreen, Inc.",BIG,2022-09-23 +"Jiangxi Jinghao Optical Co., Ltd.",OFI,2022-09-23 +"EverPro Technologies Company Limited",EVP,2022-09-30 +"NewCoSemi (Beijing) Technology CO.,Ltd.",NCV,2022-09-30 +"LG Display",LGD,2022-09-30 +"Tianma Microelectronics Ltd.",TMA,2022-10-20 +"Printronix LLC",PTX,2022-10-20 +Colorlight,KLT,2022-10-20 +"Beck GmbH & Co. Elektronik Bauelemente KG",BCK,2022-10-20 +"Shenzhen Soogeen Electronics Co., LTD.",SGN,2022-11-29 +"Emotiva Audio Corp. ",EAC,2023-02-02 +Delem,DLD,2023-04-05 +"UNIONMAN TECHNOLOGY CO., LTD",JLK,2023-04-19 +"Samsung Display Corp.",SDC,2023-04-25 +"KOGAN AUSTRALIA PTY LTD",KGN,2023-05-09 +"Brainlab AG",BRL,2023-05-16 +"Lincoln Technology Solutions",LNC,2023-05-24 +"Norxe AS",NXE,2023-06-07 +"Avocor Technologies USA, Inc",ATU,2023-06-07 +"HONOR Device Co., Ltd.",HNM,2023-06-07 +VITEC,VVI,2023-06-12 +"Dixon Technologies (India) Limited",DXN,2023-07-31 +"China Star Optoelectronics Technology Co., Ltd",CSW,2023-08-02 +RODE,OMG,2023-08-03 +AVARRO,RRO,2023-08-07 +"InfoVision Optoelectronics (Kunshan) Co.,Ltd China",IVO,2023-09-01 +"Moore Threads Virtual Display",MTT,2023-09-11 +"Lumens Digital Optics Inc.",LMS,2023-10-04 +"LUMINO Licht Elektronik GmbH",LLT,2023-11-07 +"Reonel Oy",RNL,2024-01-04 +DemoPad Software Ltd,DEM,2024-01-04 +"TeamViewer Germany GmbH",TMV,2024-01-04 +"Pixio USA",PXO,2024-02-14 +"ELARABY COMPANY FOR ENGINEERING INDUSTRIES",EEI,2024-02-14 +"Maxnerva Technology Services Limited",MNS,2024-02-14 +"Somnium Space Ltd.",SMN,2024-02-29 +"Raspberry PI",RPL,2024-05-07 +"DEIF A/S",DEF,2024-05-10 +"Moka International limited",MOK,2024-05-23 +"Shure Inc.",SHU,2024-06-13 +"Indicates an identity defined by CTS/DID Standards other than EDID",CID,2024-06-28 +"Daten Tecnologia",DTM,2024-06-15 +"LABAU Technology Corp.",LBC,2024-08-05 +"Xiaomi Corporation",XMI,2024-08-05 +"Airdrop Gaming LLC",ADG,2024-09-03 +"Ugreen Group Ltd.",UGR,2025-01-28 +"Barnfind Technologies",BFT,2025-01-28 \ No newline at end of file From 3b3bec1abe98ab3a5dbc3a5f1489e4e154e0f879 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:17:23 +0100 Subject: [PATCH 1932/2155] Update hwdb autosuspend rules ninja -C build update-hwdb-autosuspend --- hwdb.d/60-autosuspend-fingerprint-reader.hwdb | 4 ++++ tools/chromiumos/gen_autosuspend_rules.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/hwdb.d/60-autosuspend-fingerprint-reader.hwdb b/hwdb.d/60-autosuspend-fingerprint-reader.hwdb index 85083b0f3d5fb..32bfa5ad7bdb6 100644 --- a/hwdb.d/60-autosuspend-fingerprint-reader.hwdb +++ b/hwdb.d/60-autosuspend-fingerprint-reader.hwdb @@ -172,6 +172,7 @@ usb:v04F3p0CA3* usb:v04F3p0CA7* usb:v04F3p0CA8* usb:v04F3p0CB0* +usb:v04F3p0CB2* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -204,12 +205,14 @@ usb:v10A5pD205* usb:v10A5p9524* usb:v10A5p9544* usb:v10A5pC844* +usb:v10A5p9B24* ID_AUTOSUSPEND=1 ID_PERSIST=0 # Supported by libfprint driver goodixmoc usb:v27C6p5840* usb:v27C6p6014* +usb:v27C6p6090* usb:v27C6p6092* usb:v27C6p6094* usb:v27C6p609A* @@ -239,6 +242,7 @@ usb:v27C6p659A* usb:v27C6p659C* usb:v27C6p6A94* usb:v27C6p6512* +usb:v27C6p6890* usb:v27C6p689A* usb:v27C6p66A9* ID_AUTOSUSPEND=1 diff --git a/tools/chromiumos/gen_autosuspend_rules.py b/tools/chromiumos/gen_autosuspend_rules.py index ee1682f6f3b0f..14c0010ba6b55 100755 --- a/tools/chromiumos/gen_autosuspend_rules.py +++ b/tools/chromiumos/gen_autosuspend_rules.py @@ -145,6 +145,10 @@ "33f8:01a2", # Rolling Wireless (RW135) "33f8:0115", + # Rolling Wireless (RW135R) + "33f8:1003", + # Rolling Wireless (RW350R USB) + "33f8:0802", # NetPrisma (LCUK54) "3731:0100", ] From 20a79f1222973735bf67f7359f914e16babd7f8a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:18:04 +0100 Subject: [PATCH 1933/2155] Update syscall numbers ninja -C build update-syscall-tables update-syscall-header --- src/include/override/sys/syscall-list.txt | 1 + src/include/override/sys/syscalls-alpha.txt | 1 + src/include/override/sys/syscalls-arc.txt | 1 + src/include/override/sys/syscalls-arm.txt | 1 + src/include/override/sys/syscalls-arm64.txt | 1 + src/include/override/sys/syscalls-i386.txt | 1 + src/include/override/sys/syscalls-loongarch64.txt | 3 ++- src/include/override/sys/syscalls-m68k.txt | 1 + src/include/override/sys/syscalls-mips64.txt | 1 + src/include/override/sys/syscalls-mips64n32.txt | 1 + src/include/override/sys/syscalls-mipso32.txt | 1 + src/include/override/sys/syscalls-parisc.txt | 1 + src/include/override/sys/syscalls-powerpc.txt | 1 + src/include/override/sys/syscalls-powerpc64.txt | 1 + src/include/override/sys/syscalls-riscv32.txt | 1 + src/include/override/sys/syscalls-riscv64.txt | 1 + src/include/override/sys/syscalls-s390x.txt | 1 + src/include/override/sys/syscalls-sh.txt | 1 + src/include/override/sys/syscalls-sparc.txt | 3 ++- src/include/override/sys/syscalls-x86_64.txt | 1 + 20 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/include/override/sys/syscall-list.txt b/src/include/override/sys/syscall-list.txt index 4e8260eca058e..baab9835443c1 100644 --- a/src/include/override/sys/syscall-list.txt +++ b/src/include/override/sys/syscall-list.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq +rseq_slice_yield rt_sigaction rt_sigpending rt_sigprocmask diff --git a/src/include/override/sys/syscalls-alpha.txt b/src/include/override/sys/syscalls-alpha.txt index 5bd33d275c729..e6de840d4eaa9 100644 --- a/src/include/override/sys/syscalls-alpha.txt +++ b/src/include/override/sys/syscalls-alpha.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 137 rseq 527 +rseq_slice_yield 581 rt_sigaction 352 rt_sigpending 354 rt_sigprocmask 353 diff --git a/src/include/override/sys/syscalls-arc.txt b/src/include/override/sys/syscalls-arc.txt index f42160becb8f9..76834685fbe72 100644 --- a/src/include/override/sys/syscalls-arc.txt +++ b/src/include/override/sys/syscalls-arc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-arm.txt b/src/include/override/sys/syscalls-arm.txt index 02776873f6e2c..a1998492872ef 100644 --- a/src/include/override/sys/syscalls-arm.txt +++ b/src/include/override/sys/syscalls-arm.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 398 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-arm64.txt b/src/include/override/sys/syscalls-arm64.txt index a29c0745baf50..725c51f181d87 100644 --- a/src/include/override/sys/syscalls-arm64.txt +++ b/src/include/override/sys/syscalls-arm64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-i386.txt b/src/include/override/sys/syscalls-i386.txt index ec79c704b135e..0b5ead1519eeb 100644 --- a/src/include/override/sys/syscalls-i386.txt +++ b/src/include/override/sys/syscalls-i386.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 386 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-loongarch64.txt b/src/include/override/sys/syscalls-loongarch64.txt index 78b4eb8dcc896..f694728f6d234 100644 --- a/src/include/override/sys/syscalls-loongarch64.txt +++ b/src/include/override/sys/syscalls-loongarch64.txt @@ -213,7 +213,7 @@ map_shadow_stack 453 mbind 235 membarrier 283 memfd_create 279 -memfd_secret +memfd_secret 447 memory_ordering migrate_pages 238 mincore 232 @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-m68k.txt b/src/include/override/sys/syscalls-m68k.txt index db78f19ae852c..d577d8fe5ea2c 100644 --- a/src/include/override/sys/syscalls-m68k.txt +++ b/src/include/override/sys/syscalls-m68k.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 384 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-mips64.txt b/src/include/override/sys/syscalls-mips64.txt index d85885c96197c..ab9aecbc0e5ae 100644 --- a/src/include/override/sys/syscalls-mips64.txt +++ b/src/include/override/sys/syscalls-mips64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 5082 rseq 5327 +rseq_slice_yield 5471 rt_sigaction 5013 rt_sigpending 5125 rt_sigprocmask 5014 diff --git a/src/include/override/sys/syscalls-mips64n32.txt b/src/include/override/sys/syscalls-mips64n32.txt index 1dbbf702dd437..08a983c24deb6 100644 --- a/src/include/override/sys/syscalls-mips64n32.txt +++ b/src/include/override/sys/syscalls-mips64n32.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 6082 rseq 6331 +rseq_slice_yield 6471 rt_sigaction 6013 rt_sigpending 6125 rt_sigprocmask 6014 diff --git a/src/include/override/sys/syscalls-mipso32.txt b/src/include/override/sys/syscalls-mipso32.txt index 5d27caabe2918..587183e352638 100644 --- a/src/include/override/sys/syscalls-mipso32.txt +++ b/src/include/override/sys/syscalls-mipso32.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 4040 rseq 4367 +rseq_slice_yield 4471 rt_sigaction 4194 rt_sigpending 4196 rt_sigprocmask 4195 diff --git a/src/include/override/sys/syscalls-parisc.txt b/src/include/override/sys/syscalls-parisc.txt index cf45fc19763b5..793f65070d35c 100644 --- a/src/include/override/sys/syscalls-parisc.txt +++ b/src/include/override/sys/syscalls-parisc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 354 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-powerpc.txt b/src/include/override/sys/syscalls-powerpc.txt index 533003ac062d5..02f9869efbdcf 100644 --- a/src/include/override/sys/syscalls-powerpc.txt +++ b/src/include/override/sys/syscalls-powerpc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 173 rt_sigpending 175 rt_sigprocmask 174 diff --git a/src/include/override/sys/syscalls-powerpc64.txt b/src/include/override/sys/syscalls-powerpc64.txt index 4f164e93df600..0da8e5c954775 100644 --- a/src/include/override/sys/syscalls-powerpc64.txt +++ b/src/include/override/sys/syscalls-powerpc64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 173 rt_sigpending 175 rt_sigprocmask 174 diff --git a/src/include/override/sys/syscalls-riscv32.txt b/src/include/override/sys/syscalls-riscv32.txt index 4f28fdd757ab4..b88d32c3618f9 100644 --- a/src/include/override/sys/syscalls-riscv32.txt +++ b/src/include/override/sys/syscalls-riscv32.txt @@ -359,6 +359,7 @@ riscv_flush_icache 259 riscv_hwprobe 258 rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-riscv64.txt b/src/include/override/sys/syscalls-riscv64.txt index cf82f4ff95e02..ff03aa304e551 100644 --- a/src/include/override/sys/syscalls-riscv64.txt +++ b/src/include/override/sys/syscalls-riscv64.txt @@ -359,6 +359,7 @@ riscv_flush_icache 259 riscv_hwprobe 258 rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-s390x.txt b/src/include/override/sys/syscalls-s390x.txt index ea838b1093748..017d7863b2370 100644 --- a/src/include/override/sys/syscalls-s390x.txt +++ b/src/include/override/sys/syscalls-s390x.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 383 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-sh.txt b/src/include/override/sys/syscalls-sh.txt index 1da85abd4d7ea..a8473a39221d6 100644 --- a/src/include/override/sys/syscalls-sh.txt +++ b/src/include/override/sys/syscalls-sh.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-sparc.txt b/src/include/override/sys/syscalls-sparc.txt index 23e1eb5d6186c..be5d76b2e30a7 100644 --- a/src/include/override/sys/syscalls-sparc.txt +++ b/src/include/override/sys/syscalls-sparc.txt @@ -39,7 +39,7 @@ clock_nanosleep_time64 407 clock_settime 256 clock_settime64 404 clone 217 -clone3 +clone3 435 close 6 close_range 436 connect 98 @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 137 rseq 365 +rseq_slice_yield 471 rt_sigaction 102 rt_sigpending 104 rt_sigprocmask 103 diff --git a/src/include/override/sys/syscalls-x86_64.txt b/src/include/override/sys/syscalls-x86_64.txt index f961ab78dd124..4ae31535c96cf 100644 --- a/src/include/override/sys/syscalls-x86_64.txt +++ b/src/include/override/sys/syscalls-x86_64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 84 rseq 334 +rseq_slice_yield 471 rt_sigaction 13 rt_sigpending 127 rt_sigprocmask 14 From 150d3398fc6103f618a05b786e94967168995f50 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:18:45 +0100 Subject: [PATCH 1934/2155] Update pot file ninja -C build systemd-pot --- po/systemd.pot | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/po/systemd.pot b/po/systemd.pot index 93bb42609816f..62768c05d7004 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-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -789,33 +789,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" From 6f32c24a81837e0798e3a0c029b5a6cfd5c91763 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:24:56 +0100 Subject: [PATCH 1935/2155] Update po files ninja -C build systemd-update-po --- po/ar.po | 31 ++++- po/be.po | 32 +++-- po/be@latin.po | 31 ++++- po/bg.po | 32 +++-- po/bo.po | 43 +++++-- po/ca.po | 31 ++++- po/cs.po | 31 ++++- po/da.po | 32 +++-- po/de.po | 31 ++++- po/el.po | 31 ++++- po/es.po | 31 ++++- po/et.po | 32 +++-- po/eu.po | 34 +++-- po/fi.po | 31 ++++- po/fr.po | 31 ++++- po/gl.po | 32 +++-- po/he.po | 31 ++++- po/hi.po | 32 +++-- po/hr.po | 32 +++-- po/hu.po | 31 ++++- po/ia.po | 31 ++++- po/id.po | 31 ++++- po/it.po | 31 ++++- po/ja.po | 2 +- po/ka.po | 31 ++++- po/kab.po | 31 ++++- po/kk.po | 31 ++++- po/km.po | 32 +++-- po/kn.po | 31 ++++- po/ko.po | 31 ++++- po/kw.po | 31 ++++- po/lo.po | 141 +++++++++------------ po/lt.po | 31 ++++- po/nl.po | 32 +++-- po/pa.po | 40 ++++-- po/pl.po | 35 ++++-- po/pt.po | 31 ++++- po/pt_BR.po | 31 ++++- po/ro.po | 32 +++-- po/ru.po | 31 ++++- po/si.po | 31 ++++- po/sk.po | 31 ++++- po/sl.po | 31 ++++- po/sr.po | 236 +++++++++++++++++++++++++++++++++-- po/sr@latin.po | 300 ++++++++++++++++++++++++++++++++++++++------ po/sv.po | 31 ++++- po/tr.po | 31 ++++- po/ug.po | 331 ++++++++++++++++++++++++++++++++++++------------- po/uk.po | 31 ++++- po/zh_CN.po | 31 ++++- po/zh_TW.po | 32 +++-- 51 files changed, 1961 insertions(+), 514 deletions(-) diff --git a/po/ar.po b/po/ar.po index 1c9882eaa322d..6ac6707498c87 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-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: 2026-04-11 19:58+0000\n" "Last-Translator: joo es \n" "Language-Team: Arabic \n" "Language-Team: Belarusian \n" "Language-Team: \n" @@ -921,11 +921,20 @@ msgstr "" "kantejnierami." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Kiravać lakaĺnymi virtuaĺnymi mašynami abo kantejnierami" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -933,12 +942,12 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia lakaĺnymi virtuaĺnymi mašynami i " "kantejnierami." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Kiravać lakaĺnymi virtuaĺnymi mašynami abo kantejnierami" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -946,11 +955,11 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia lakaĺnymi virtuaĺnymi mašynami i " "kantejnierami." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Kiravać vobrazami lakaĺnych virtuaĺnych mašyn i kantejnieraŭ" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -958,6 +967,16 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia vobrazami lakaĺnych virtuaĺnych " "mašyn i kantejnieraŭ." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" diff --git a/po/bg.po b/po/bg.po index fb84c6c9d7cd3..212d3855f6a0f 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-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: 2025-02-11 01:17+0000\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language-Team: Tibetan\n" @@ -147,7 +147,9 @@ msgstr "ཁྱིམ་དཀར་ཆག་མིང་རྟགས་ལྡེ msgid "" "Home of user %s is currently absent, please plug in the necessary storage " "device or backing file system." -msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་མེད། དགོས་ངེས་ཀྱི་གསོག་ཉར་སྒྲིག་ཆས་སམ་རྒྱབ་རྟེན་ཡིག་ཚགས་མ་ལག་སྦྲེལ་རོགས།" +msgstr "" +"སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་མེད། དགོས་ངེས་ཀྱི་གསོག་ཉར་སྒྲིག་ཆས་སམ་རྒྱབ་རྟེན་ཡིག་ཚགས་མ་ལག་སྦྲེལ་" +"རོགས།" #: src/home/pam_systemd_home.c:335 #, c-format @@ -221,7 +223,9 @@ msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་ར msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" -msgstr "བདེ་འཇགས་རྟགས PIN སྒོ་ལྕགས་བཀག་ཟིན། སྔོན་ལ་སྒྲོལ་རོགས། (ཁ་སྣོན: ཕྱིར་བཏོན་ནས་ཡང་བསྐྱར་བཙུགས་ན་འགྲིག་སྲིད།)" +msgstr "" +"བདེ་འཇགས་རྟགས PIN སྒོ་ལྕགས་བཀག་ཟིན། སྔོན་ལ་སྒྲོལ་རོགས། (ཁ་སྣོན: ཕྱིར་བཏོན་ནས་ཡང་བསྐྱར་བཙུགས་ན་" +"འགྲིག་སྲིད།)" #: src/home/pam_systemd_home.c:474 #, c-format @@ -714,7 +718,9 @@ msgstr "boot loader ལ་དམིགས་བསལ་གྱི་འཇུག msgid "" "Authentication is required to indicate to the boot loader to boot into a " "specific boot loader entry." -msgstr "boot loader ལ་དམིགས་བསལ་གྱི boot loader འཇུག་ཚན་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" +msgstr "" +"boot loader ལ་དམིགས་བསལ་གྱི boot loader འཇུག་ཚན་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་" +"དགོས།" #: src/login/org.freedesktop.login1.policy:396 msgid "Set a wall message" @@ -791,33 +797,52 @@ msgid "" msgstr "ས་གནས virtual machine དང container དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP ཞབས་ཞུ་ཆས་སྒྲིག" diff --git a/po/ca.po b/po/ca.po index 01e66821c3f78..3685e010fb0eb 100644 --- a/po/ca.po +++ b/po/ca.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: 2026-03-10 15:58+0000\n" "Last-Translator: naly zzwd \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" @@ -845,31 +845,40 @@ msgstr "" "konteinereid." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Loo kohalik virtuaalmasin või konteiner" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Autentimine on vajalik, et luua kohalikku virtuaalmasinat või konteinerit." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registreeri kohalik virtuaalmasin või konteiner" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Autentimine on vajalik, et registreerida kohalikku virtuaalmasinat või " "konteinerit." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Halda kohalikke virtuaalmasinaid ja konteinerpilte" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -877,6 +886,16 @@ msgstr "" "Autentimine on vajalik, et hallata kohalikke virtuaalmasinaid ja " "konteinerpilte." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Säti NTP servereid" @@ -988,7 +1007,6 @@ msgstr "DHCP server saadab sunduuendamise sõnumi" #: 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." diff --git a/po/eu.po b/po/eu.po index d292240069525..40e264e139034 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-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\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" "Language-Team: Kabyle \n" "Language-Team: Kazakh \n" "Language-Team: Khmer (Central) \n" "Language-Team: Korean \n" "Language-Team: Lao \n" "Language-Team: Lithuanian \n" "Language-Team: Dutch \n" "Language-Team: Punjabi \n" @@ -908,32 +908,41 @@ msgstr "" "wirtualnymi i kontenerami." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Utworzenie lokalnej maszyny wirtualnej lub kontenera" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Wymagane jest uwierzytelnienie, aby utworzyć lokalną maszynę wirtualną lub " "kontener." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Rejestracja lokalnej maszyny wirtualnej lub kontenera" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Wymagane jest uwierzytelnienie, aby zarejestrować lokalną maszynę wirtualną " "lub kontener." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Zarządzanie lokalnymi obrazami maszyn wirtualnych i kontenerów" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -941,6 +950,16 @@ msgstr "" "Wymagane jest uwierzytelnienie, aby zarządzać lokalnymi obrazami maszyn " "wirtualnych i kontenerów." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Ustawienie serwerów NTP" @@ -1058,8 +1077,8 @@ msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" -"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia z" -" serwera DHCP." +"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia " +"z serwera DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/pt.po b/po/pt.po index 8d6e42865e353..74f95a518906e 100644 --- a/po/pt.po +++ b/po/pt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: 2026-03-05 22:10+0000\n" "Last-Translator: Américo Monteiro \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: Serbian \n" "Language-Team: Swedish \n" "Language-Team: Turkish \n" "Language-Team: Uyghur\n" @@ -23,7 +23,9 @@ msgstr "مەخپىي ئىبارىنى سىستېمىغا قايتا يوللا" #: src/core/org.freedesktop.systemd1.policy.in:23 msgid "" "Authentication is required to send the entered passphrase back to the system." -msgstr "كىرگۈزۈلگەن مەخپىي ئىبارىنى سىستېمىغا قايتا يوللاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"كىرگۈزۈلگەن مەخپىي ئىبارىنى سىستېمىغا قايتا يوللاش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/core/org.freedesktop.systemd1.policy.in:33 msgid "Manage system services or other units" @@ -31,7 +33,9 @@ msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىر #: src/core/org.freedesktop.systemd1.policy.in:34 msgid "Authentication is required to manage system services or other units." -msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/core/org.freedesktop.systemd1.policy.in:43 msgid "Manage system service or unit files" @@ -39,17 +43,23 @@ msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىر #: src/core/org.freedesktop.systemd1.policy.in:44 msgid "Authentication is required to manage system service or unit files." -msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/core/org.freedesktop.systemd1.policy.in:54 msgid "Set or unset system and service manager environment variables" -msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلە ياكى بىكار قىل" +msgstr "" +"سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلە ياكى بىكار " +"قىل" #: src/core/org.freedesktop.systemd1.policy.in:55 msgid "" "Authentication is required to set or unset system and service manager " "environment variables." -msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلەش ياكى بىكار قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلەش ياكى بىكار " +"قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/core/org.freedesktop.systemd1.policy.in:64 msgid "Reload the systemd state" @@ -66,7 +76,9 @@ msgstr "سۈرئەت چەكلىمىسىز ھالدا systemd ھالىتىنى چ #: src/core/org.freedesktop.systemd1.policy.in:75 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" @@ -74,7 +86,8 @@ msgstr "باش مۇندەرىجە رايونىنى قۇر" #: src/home/org.freedesktop.home1.policy:14 msgid "Authentication is required to create a user's home area." -msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" @@ -82,7 +95,8 @@ msgstr "باش مۇندەرىجە رايونىنى ئۆچۈر" #: src/home/org.freedesktop.home1.policy:24 msgid "Authentication is required to remove a user's home area." -msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" @@ -91,7 +105,9 @@ msgstr "باش مۇندەرىجە رايونىنىڭ ئىسپات ئۇچۇرىن #: src/home/org.freedesktop.home1.policy:34 msgid "" "Authentication is required to check credentials against a user's home area." -msgstr "ئىسپات ئۇچۇرىنى ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى بىلەن سېلىشتۇرۇپ تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئىسپات ئۇچۇرىنى ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى بىلەن سېلىشتۇرۇپ تەكشۈرۈش " +"ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" @@ -99,7 +115,8 @@ msgstr "باش مۇندەرىجە رايونىنى يېڭىلا" #: src/home/org.freedesktop.home1.policy:44 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" @@ -115,7 +132,9 @@ msgstr "باش مۇندەرىجە رايونىنىڭ چوڭ-كىچىكلىكىن #: src/home/org.freedesktop.home1.policy:64 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" @@ -124,7 +143,9 @@ msgstr "باش مۇندەرىجە رايونىنىڭ پارولىنى ئۆزگە #: src/home/org.freedesktop.home1.policy:74 msgid "" "Authentication is required to change the password of a user's home area." -msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى پارولىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى پارولىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" @@ -132,7 +153,8 @@ msgstr "باش مۇندەرىجە رايونىنى ئاكتىپلا" #: src/home/org.freedesktop.home1.policy:84 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" @@ -140,19 +162,24 @@ msgstr "باش مۇندەرىجە ئىمزا ئاچقۇچلىرىنى باشقۇ #: src/home/org.freedesktop.home1.policy:94 msgid "Authentication is required to manage signing keys for home directories." -msgstr "باش مۇندەرىجىلەرنىڭ ئىمزا ئاچقۇچلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +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 ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر مەۋجۇت ئەمەس، زۆرۈر ساقلاش ئۈسكۈنىسى ياكى تۆۋەن قەۋەت ھۆججەت سىستېمىسىنى ئۇلاڭ." +msgstr "" +"%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر مەۋجۇت ئەمەس، زۆرۈر ساقلاش ئۈسكۈنىسى " +"ياكى تۆۋەن قەۋەت ھۆججەت سىستېمىسىنى ئۇلاڭ." #: src/home/pam_systemd_home.c:335 #, c-format msgid "Too frequent login attempts for user %s, try again later." -msgstr "%s ئىشلەتكۈچىنىڭ كىرىش سىنىقى بەك كۆپ قېتىم بولدى، كېيىن قايتا سىناپ بېقىڭ." +msgstr "" +"%s ئىشلەتكۈچىنىڭ كىرىش سىنىقى بەك كۆپ قېتىم بولدى، كېيىن قايتا سىناپ بېقىڭ." #: src/home/pam_systemd_home.c:347 msgid "Password: " @@ -176,7 +203,9 @@ msgstr "ئەسلىگە كەلتۈرۈش ئاچقۇچى: " msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." -msgstr "%s ئىشلەتكۈچىنىڭ پارول/ئەسلىگە كەلتۈرۈش ئاچقۇچى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." +msgstr "" +"%s ئىشلەتكۈچىنىڭ پارول/ئەسلىگە كەلتۈرۈش ئاچقۇچى خاتا ياكى دەلىللەش ئۈچۈن " +"يېتەرلىك ئەمەس." #: src/home/pam_systemd_home.c:375 msgid "Sorry, reenter recovery key: " @@ -196,7 +225,9 @@ msgstr "پارول بىلەن قايتا سىناڭ: " msgid "" "Password incorrect or not sufficient, and configured security token of user " "%s not inserted." -msgstr "پارول خاتا ياكى يېتەرلىك ئەمەس، شۇنداقلا %s ئىشلەتكۈچىگە سەپلىگەن بىخەتەرلىك توكىنى چېتىلمىغان." +msgstr "" +"پارول خاتا ياكى يېتەرلىك ئەمەس، شۇنداقلا %s ئىشلەتكۈچىگە سەپلىگەن بىخەتەرلىك " +"توكىنى چېتىلمىغان." #: src/home/pam_systemd_home.c:418 msgid "Security token PIN: " @@ -205,23 +236,29 @@ msgstr "بىخەتەرلىك توكىنى PIN: " #: src/home/pam_systemd_home.c:435 #, c-format msgid "Please authenticate physically on security token of user %s." -msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا فىزىكىلىق دەلىللەشنى تاماملاڭ." +msgstr "" +"ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا فىزىكىلىق دەلىللەشنى تاماملاڭ." #: src/home/pam_systemd_home.c:446 #, c-format msgid "Please confirm presence on security token of user %s." -msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا ھازىر ئىكەنلىكىڭىزنى جەزملەڭ." +msgstr "" +"ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا ھازىر ئىكەنلىكىڭىزنى جەزملەڭ." #: src/home/pam_systemd_home.c:457 #, c-format msgid "Please verify user on security token of user %s." -msgstr "ئىلتىماس، %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 قۇلۇپلانغان، ئالدى بىلەن قۇلۇپنى ئېچىڭ. (ئەسكەرتىش: چېتىپ تۇرغاننى چىقىرىپ قايتا چېتىش كۇپايە بولۇشى مۇمكىن.)" +msgstr "" +"بىخەتەرلىك توكىنى PIN قۇلۇپلانغان، ئالدى بىلەن قۇلۇپنى ئېچىڭ. (ئەسكەرتىش: " +"چېتىپ تۇرغاننى چىقىرىپ قايتا چېتىش كۇپايە بولۇشى مۇمكىن.)" #: src/home/pam_systemd_home.c:474 #, c-format @@ -236,27 +273,36 @@ 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 خاتا (پەقەت بىر قانچەلا سىناق پۇرسىتى قالدى!)" +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 خاتا (پەقەت بىرلا قېتىملىق سىناق پۇرسىتى قالدى!)" +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 ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر ئاكتىپ ئەمەس، ئالدى بىلەن يەرلىك كىرىڭ." +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 ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر قۇلۇپلانغان، ئالدى بىلەن يەرلىكتە قۇلۇپنى ئېچىڭ." +msgstr "" +"%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر قۇلۇپلانغان، ئالدى بىلەن يەرلىكتە " +"قۇلۇپنى ئېچىڭ." #: src/home/pam_systemd_home.c:715 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "%s ئىشلەتكۈچىنىڭ مۇۋەپپەقىيەتسىز كىرىش سىنىقى بەك كۆپ بولدى، رەت قىلىندى." +msgstr "" +"%s ئىشلەتكۈچىنىڭ مۇۋەپپەقىيەتسىز كىرىش سىنىقى بەك كۆپ بولدى، رەت قىلىندى." #: src/home/pam_systemd_home.c:1012 msgid "User record is blocked, prohibiting access." @@ -289,7 +335,8 @@ msgstr "پارولنىڭ مۇددىتى توشتى، ئۆزگەرتىش زۆرۈ #: src/home/pam_systemd_home.c:1056 msgid "Password is expired, but can't change, refusing login." -msgstr "پارولنىڭ مۇددىتى توشقان، ئەمما ئۆزگەرتكىلى بولمايدۇ، كىرىش رەت قىلىندى." +msgstr "" +"پارولنىڭ مۇددىتى توشقان، ئەمما ئۆزگەرتكىلى بولمايدۇ، كىرىش رەت قىلىندى." #: src/home/pam_systemd_home.c:1060 msgid "Password will expire soon, please change." @@ -311,7 +358,9 @@ msgstr "مۇقىم ساھىبجامال نامىنى بەلگىلە" msgid "" "Authentication is required to set the statically configured local hostname, " "as well as the pretty hostname." -msgstr "مۇقىم تەڭشەلگەن يەرلىك ساھىبجامال نامى ۋە چىرايلىق ساھىبجامال نامىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"مۇقىم تەڭشەلگەن يەرلىك ساھىبجامال نامى ۋە چىرايلىق ساھىبجامال نامىنى " +"بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/hostname/org.freedesktop.hostname1.policy:41 msgid "Set machine information" @@ -376,7 +425,9 @@ msgstr "دىسكا ئەكسىنى يوللاشنى بىكار قىل" #: src/import/org.freedesktop.import1.policy:53 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" @@ -392,7 +443,8 @@ msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگ #: src/locale/org.freedesktop.locale1.policy:34 msgid "Authentication is required to set the system keyboard settings." -msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:22 msgid "Allow applications to inhibit system shutdown" @@ -435,7 +487,9 @@ msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىل msgid "" "Authentication is required for an application to inhibit automatic system " "suspend." -msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:75 msgid "Allow applications to inhibit system handling of the power key" @@ -445,47 +499,64 @@ msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر msgid "" "Authentication is required for an application to inhibit system handling of " "the power key." -msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:86 msgid "Allow applications to inhibit system handling of the suspend key" -msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" +msgstr "" +"ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" #: src/login/org.freedesktop.login1.policy:87 msgid "" "Authentication is required for an application to inhibit system handling of " "the suspend key." -msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:97 msgid "Allow applications to inhibit system handling of the hibernate key" -msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" +msgstr "" +"ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول " +"قوي" #: src/login/org.freedesktop.login1.policy:98 msgid "" "Authentication is required for an application to inhibit system handling of " "the hibernate key." -msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:107 msgid "Allow applications to inhibit system handling of the lid switch" -msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" +msgstr "" +"ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى " +"توسۇشىغا يول قوي" #: src/login/org.freedesktop.login1.policy:108 msgid "" "Authentication is required for an application to inhibit system handling of " "the lid switch." -msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى " +"توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:117 msgid "Allow applications to inhibit system handling of the reboot key" -msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" +msgstr "" +"ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول " +"قوي" #: src/login/org.freedesktop.login1.policy:118 msgid "" "Authentication is required for an application to inhibit system handling of " "the reboot key." -msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:128 msgid "Allow non-logged-in user to run programs" @@ -493,7 +564,9 @@ msgstr "تىزىملاتمىغان ئىشلەتكۈچىنىڭ پروگرامما #: src/login/org.freedesktop.login1.policy:129 msgid "Explicit request is required to run programs as a non-logged-in user." -msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن ئېنىق ئىلتىماس تەلەپ قىلىنىدۇ." +msgstr "" +"تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن ئېنىق ئىلتىماس " +"تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:138 msgid "Allow non-logged-in users to run programs" @@ -501,7 +574,9 @@ msgstr "تىزىملاتمىغان ئىشلەتكۈچىلەرنىڭ پروگرا #: src/login/org.freedesktop.login1.policy:139 msgid "Authentication is required to run programs as a non-logged-in user." -msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:148 msgid "Allow attaching devices to seats" @@ -517,7 +592,9 @@ msgstr "ئۈسكۈنە بىلەن seat ئۇلىنىشىنى يېڭىلا" #: src/login/org.freedesktop.login1.policy:160 msgid "Authentication is required to reset how devices are attached to seats." -msgstr "ئۈسكۈنىلەرنىڭ seat قا قانداق ئۇلىنىدىغانلىقىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئۈسكۈنىلەرنىڭ seat قا قانداق ئۇلىنىدىغانلىقىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:169 msgid "Power off the system" @@ -535,7 +612,9 @@ msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەت msgid "" "Authentication is required to power off the system while other users are " "logged in." -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:191 msgid "Power off the system while an application is inhibiting this" @@ -563,7 +642,9 @@ msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەت msgid "" "Authentication is required to reboot the system while other users are logged " "in." -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغىتىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:224 msgid "Reboot the system while an application is inhibiting this" @@ -573,7 +654,8 @@ msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغا msgid "" "Authentication is required to reboot the system while an application is " "inhibiting this." -msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:235 msgid "Halt the system" @@ -591,7 +673,9 @@ msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەت msgid "" "Authentication is required to halt the system while other users are logged " "in." -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختىتىش ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:257 msgid "Halt the system while an application is inhibiting this" @@ -619,7 +703,9 @@ msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەت msgid "" "Authentication is required to suspend the system while other users are " "logged in." -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:289 msgid "Suspend the system while an application is inhibiting this" @@ -629,7 +715,8 @@ msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق تو msgid "" "Authentication is required to suspend the system while an application is " "inhibiting this." -msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:300 msgid "Hibernate the system" @@ -641,13 +728,16 @@ msgstr "سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈ #: src/login/org.freedesktop.login1.policy:310 msgid "Hibernate the system while other users are logged in" -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" #: src/login/org.freedesktop.login1.policy:311 msgid "" "Authentication is required to hibernate the system while other users are " "logged in." -msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش " +"ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:321 msgid "Hibernate the system while an application is inhibiting this" @@ -657,7 +747,9 @@ msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلا msgid "" "Authentication is required to hibernate the system while an application is " "inhibiting this." -msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:332 msgid "Manage active sessions, users and seats" @@ -665,7 +757,9 @@ msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats #: src/login/org.freedesktop.login1.policy:333 msgid "Authentication is required to manage active sessions, users and seats." -msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:342 msgid "Lock or unlock active sessions" @@ -673,7 +767,8 @@ msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلا ياكى قۇلۇپن #: src/login/org.freedesktop.login1.policy:343 msgid "Authentication is required to lock or unlock active sessions." -msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلاش ياكى قۇلۇپنى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"ئاكتىپ سۆھبەتلەرنى قۇلۇپلاش ياكى قۇلۇپنى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:352 msgid "Set the reboot \"reason\" in the kernel" @@ -681,7 +776,8 @@ msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەل #: src/login/org.freedesktop.login1.policy:353 msgid "Authentication is required to set the reboot \"reason\" in the kernel." -msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:363 msgid "Indicate to the firmware to boot to setup interface" @@ -691,7 +787,9 @@ msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگ msgid "" "Authentication is required to indicate to the firmware to boot to setup " "interface." -msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسىتىش ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:374 msgid "Indicate to the boot loader to boot to the boot loader menu" @@ -701,7 +799,9 @@ msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ msgid "" "Authentication is required to indicate to the boot loader to boot to the " "boot loader menu." -msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسىتىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:385 msgid "Indicate to the boot loader to boot a specific entry" @@ -711,7 +811,9 @@ msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلە msgid "" "Authentication is required to indicate to the boot loader to boot into a " "specific boot loader entry." -msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسىتىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/login/org.freedesktop.login1.policy:396 msgid "Set a wall message" @@ -759,7 +861,8 @@ msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىش #: src/machine/org.freedesktop.machine1.policy:54 msgid "Authentication is required to acquire a shell on the local host." -msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/machine/org.freedesktop.machine1.policy:64 msgid "Acquire a pseudo TTY in a local container" @@ -768,7 +871,8 @@ 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 غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك كونتېينېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/machine/org.freedesktop.machine1.policy:74 msgid "Acquire a pseudo TTY on the local host" @@ -776,7 +880,9 @@ 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 غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك ئاساسىي كومپيۇتېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/machine/org.freedesktop.machine1.policy:84 msgid "Manage local virtual machines and containers" @@ -785,35 +891,60 @@ msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى #: src/machine/org.freedesktop.machine1.policy:85 msgid "" "Authentication is required to manage local virtual machines and containers." -msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇر" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." -msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملا" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." -msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇر" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." -msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" @@ -901,7 +1032,8 @@ 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 سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"DNSSEC سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/network/org.freedesktop.network1.policy:121 msgid "Revert NTP settings" @@ -927,7 +1059,9 @@ msgstr "DHCP مۇلازىمېتىرى زورلاپ يېڭىلاش ئۇچۇرىن msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "DHCP مۇلازىمېتىرىدىن زورلاپ يېڭىلاش ئۇچۇرى ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"DHCP مۇلازىمېتىرىدىن زورلاپ يېڭىلاش ئۇچۇرى ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -955,13 +1089,17 @@ msgstr "تور ئۇلاش ئېغىزىنى قايتا سەپلەش ئۈچۈن د #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلە" +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 ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-" +"بولمايدىغانلىقىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" @@ -986,7 +1124,8 @@ 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 نى ئۇلاش ياكى ئايرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"portable service image نى ئۇلاش ياكى ئايرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/portable/org.freedesktop.portable1.policy:34 msgid "Delete or modify portable service image" @@ -995,7 +1134,9 @@ 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 نى ئۆچۈرۈش ياكى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"portable service image نى ئۆچۈرۈش ياكى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/resolve/org.freedesktop.resolve1.policy:22 msgid "Register a DNS-SD service" @@ -1093,7 +1234,9 @@ msgstr "بەلگىلەنگەن سىستېما نەشرىنى ئورنات" msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." -msgstr "سىستېمىنى بەلگىلەنگەن (بەلكىم كونا) نەشرگە يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"سىستېمىنى بەلگىلەنگەن (بەلكىم كونا) نەشرگە يېڭىلاش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." #: src/sysupdate/org.freedesktop.sysupdate1.policy:65 msgid "Cleanup old system updates" @@ -1135,7 +1278,9 @@ msgstr "RTC نى يەرلىك ۋاقىت رايونى ياكى UTC غا بەلگ msgid "" "Authentication is required to control whether the RTC stores the local or " "UTC time." -msgstr "RTC نىڭ يەرلىك ۋاقىتنى ياكى UTC نى ساقلىشىنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"RTC نىڭ يەرلىك ۋاقىتنى ياكى UTC نى ساقلىشىنى كونترول قىلىش ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/timedate/org.freedesktop.timedate1.policy:53 msgid "Turn network time synchronization on or off" @@ -1145,7 +1290,9 @@ msgstr "تور ۋاقتى ماس قەدەملىشىنى ئېچىش ياكى تا msgid "" "Authentication is required to control whether network time synchronization " "shall be enabled." -msgstr "تور ۋاقتى ماس قەدەملىشىنى قوزغىتىش-قوزغاتماسلىقنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"تور ۋاقتى ماس قەدەملىشىنى قوزغىتىش-قوزغاتماسلىقنى كونترول قىلىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/core/dbus-unit.c:372 msgid "Authentication is required to start '$(unit)'." @@ -1167,29 +1314,39 @@ msgstr "'$(unit)' نى قايتا قوزغىتىش ئۈچۈن دەلىللەش msgid "" "Authentication is required to send a UNIX signal to the processes of '$" "(unit)'." -msgstr "UNIX سىگنالىنى '$(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)' نىڭ تارماق گۇرۇپپا جەريانلىرىغا ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"UNIX سىگنالىنى '$(unit)' نىڭ تارماق گۇرۇپپا جەريانلىرىغا ئەۋەتىش ئۈچۈن " +"دەلىللەش تەلەپ قىلىنىدۇ." #: src/core/dbus-unit.c:649 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "'$(unit)' نىڭ «مەغلۇپ» ھالىتىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"'$(unit)' نىڭ «مەغلۇپ» ھالىتىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/core/dbus-unit.c:679 msgid "Authentication is required to set properties on '$(unit)'." -msgstr "'$(unit)' ئۈستىدىكى خاسلىقلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"'$(unit)' ئۈستىدىكى خاسلىقلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." #: src/core/dbus-unit.c:776 msgid "" "Authentication is required to delete files and directories associated with '$" "(unit)'." -msgstr "'$(unit)' بىلەن مۇناسىۋەتلىك ھۆججەت ۋە مۇندەرىجىلەرنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"'$(unit)' بىلەن مۇناسىۋەتلىك ھۆججەت ۋە مۇندەرىجىلەرنى ئۆچۈرۈش ئۈچۈن دەلىللەش " +"تەلەپ قىلىنىدۇ." #: src/core/dbus-unit.c:813 msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." -msgstr "'$(unit)' بىرىكىنىڭ جەريانلىرىنى توڭلىتىش ياكى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." +msgstr "" +"'$(unit)' بىرىكىنىڭ جەريانلىرىنى توڭلىتىش ياكى ئېچىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." diff --git a/po/uk.po b/po/uk.po index 45f816287fcc1..4a5999d0c98bb 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"POT-Creation-Date: 2026-05-18 12:18+0100\n" "PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" "Language-Team: Chinese (Simplified) \n" "Language-Team: Chinese (Traditional) Date: Mon, 18 May 2026 12:26:28 +0100 Subject: [PATCH 1936/2155] meson: bump library sonames for v261~rc1 --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index c69b96e1c6d50..dc4e83204eca1 100644 --- a/meson.build +++ b/meson.build @@ -26,8 +26,8 @@ else project_minor_version = '0' endif -libsystemd_version = '0.43.0' -libudev_version = '1.7.13' +libsystemd_version = '0.44.0' +libudev_version = '1.7.14' conf = configuration_data() conf.set_quoted('PROJECT_URL', 'https://systemd.io/') From 5d5483156d89b24258a3a321296e6277ba81792f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Behrmann?= Date: Mon, 18 May 2026 15:24:54 +0200 Subject: [PATCH 1937/2155] NEWS: typo fixes --- NEWS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index c7a61dc8fb90c..1eb4746fe8d03 100644 --- a/NEWS +++ b/NEWS @@ -65,9 +65,9 @@ CHANGES WITH 261 in spe: preserved and passed down to the unit after kexec. Units must set 'FileDescriptorStorePreserve=yes' in order to enable this feature. - * User session managers now supports persisting user unit's FD Stores + * User session managers now support persisting user units' FD Stores by receiving FDs via the notify socket, and passing them down via - $SLISTEN_FDS when the user session is restarted, when the + $LISTEN_FDS when the user session is restarted, when the 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' options are set in the user@.service unit. Combined with the LUO support, this lets user units persist state (e.g.: memfds) across @@ -94,7 +94,7 @@ CHANGES WITH 261 in spe: pressure events for the corresponding cgroup. * A new RestrictFileSystemAccess= setting has been added that uses a - BPF LSM program to restrict execution to only binares that are + BPF LSM program to restrict execution to only binaries that are stored on a signed and verified dm-verity protected filesystem. * The io.systemd.Unit.StartTransient Varlink method has been extended @@ -274,7 +274,7 @@ CHANGES WITH 261 in spe: * systemd-nspawn now supports persisting the payload's system manager FD Store by receiving FDs via the notify socket, and passing them - down via $SLISTEN_FDS when the container is restarted, when the + down via $LISTEN_FDS when the container is restarted, when the 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' options are set in the unit inside which systemd-nspawn is running. Combined with the LUO support in PID1, this lets containers persist From 6fa5ebc506fc75fa2398e10ad2506dedf70d44d6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:05:10 +0100 Subject: [PATCH 1938/2155] dhcp-client: reject messages larger than the maximum UDP payload dhcp_message_verify_header() only enforced a lower bound on the input length, so dhcp_message_parse() happily accepted arbitrarily large buffers. Such inputs could never have been received via UDP and would later fail in dhcp_message_build() with -E2BIG once the parsed options' combined size exceeds UDP_PAYLOAD_MAX_SIZE, which the fuzzer surfaced as an assertion failure. Reject inputs above UDP_PAYLOAD_MAX_SIZE up front, so the parse stage mirrors what the wire format can actually carry. Follow-up for 8c18bb6547c2138f2f17b921ec06f2c1f7cd17cd Fixes https://github.com/systemd/systemd/issues/42147 Co-developed-by: Claude Opus 4.7 --- src/libsystemd-network/dhcp-message.c | 5 +++++ test/fuzz/fuzz-dhcp-client/oversized-message | Bin 0 -> 69627 bytes 2 files changed, 5 insertions(+) create mode 100644 test/fuzz/fuzz-dhcp-client/oversized-message diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index bf85dd4233185..14671a0750825 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -1342,6 +1342,11 @@ static int dhcp_message_verify_header( if (iov->iov_len < sizeof(DHCPMessageHeader)) return -EBADMSG; + /* DHCP travels over UDP, so anything larger than the maximum UDP payload cannot be a valid + * message and would also be impossible to rebuild as a UDP packet. */ + if (iov->iov_len > UDP_PAYLOAD_MAX_SIZE) + return -EBADMSG; + const DHCPMessageHeader *header = iov->iov_base; if (!IN_SET(header->op, BOOTREQUEST, BOOTREPLY)) diff --git a/test/fuzz/fuzz-dhcp-client/oversized-message b/test/fuzz/fuzz-dhcp-client/oversized-message new file mode 100644 index 0000000000000000000000000000000000000000..a1b3f69aa864c382061683d99be179cbbac37ec7 GIT binary patch literal 69627 zcmeG_e{2=UncwWL4X+sR(Oe}%tFu$8n1^wn{cOj9B*vklBvAkh(pV5w8;8~eiXk|C zHgZC)Jw?hz>iLRVR6%jh{Be91}k~`H>t2T<%NR*s((ut;r+FW#((x|;0fwS5B zzL}l#cWoW@l$-zWKgyzF(`cdcdTm;o6FIpQBS9f7vH4nY!8k(6)}w z>_?cs^U+7Y|AbD0iEgFpP3Bzd<;;i7o6UJs+MDG$dgfO^+ug-r^x4l-j#3Z)OiH=j zE&ktpf67jHVWXL00LvFJy`gCazXyQc;MeX%uX7omFwIL@y?^9edqxg!)^5r)>?C@` z|KxI-Ga3MGIZYpabwp=y6=qjK1F)1nd~ig+ws#w>f?1yLfhKu*V)7IiOw$cuVCYYc zjZ@LXdg`7%cu#0yN}uRDMNNW{YreM*C*mn>*>b#{C;dsQ8Ol-rF8t;FfBNUld%wPa zzo<~m5hr&tr-{S!84b^%haY8Q<|p2TpEpqJki?PGunY4zdu|mn+D+X^vkI_dR?18KSJ!8i?Sk1ly zKiJ(brrte`r*&P^b$v+J;XmiC*MjG1G&TR2nkH+MHTZuTcPEuPsm|*2EwALujo^!? zh~;LQ9ZPAs$+;^qOjaU({rrekoSEorZw~FKH zpa0W8Yy(Gpyj{^AKE~Y@qiKUcHj)wpJYYTi<$PeST}Sv^ z?c!_%7iYs0-sPTtSgZX38rbQE9cg#EWTw-ZG#=9bHnf6Mc~P24r`z520_U=n8So!= z+w3&F%m0vjSO8)nC<|DFUlxtvolgatb1v8oQ(I%MgjJm0TJ^VB|1%ZnE%pEJmor)| zZ{u(4E?vUTy^soemJ7(6R`HwD1pN`uxB-=>0*5mkMUE0T4wZ2WXM!PV-Fn@0PWn$!Mol!joDPEMX0r41D z1M)!(pKuciA5sW>0^%ys4FkR~|B~3lA%Uw;J^JWr_~N6FK9%jvW;=o2NhI+`ysx2e zl!|(zK!+mU$QcFxz#s^o;X@QrD8?^wVJLXna90ra~tCd_S` ztDA$djDZ`Dx%SuRKJGIg_*Gv+AA1YT$%S(9y z<=mzIyPa}=mb9|-D0y36HXsC8T+k3zTI7_f8I$wkn$cx@A9r2#3j{cxYjoj@18@b} zVW8FGsGZ5eMl8@D3D&aCm+^D)*Lr=Xj#LINIHI680mE zn_YH(`a1`HYrY@3kw#cc%wd3m^Fz796A`5R284PGWUglA!3uznMw= zB0*}JbO?fXhCQ^FIc3Efdr_m9zasgl@z3FPE=&QE(SIUb*Syemeue&=}@yqb{gOsDt9#0@n}89o}pXQ=B+K~&JOK5^W!Ur zkHKMJ{|j}vL*|M+_8_qOZWL+VwVjyqQo$55Z6goAm7gKa1~3=#j;o^X(>&ej;H^BT8A3f`?O zts$wQb2MX!1%nzoowv0xe@5u%NJ$)Q3zv^~Pw{5*&(=OC{-8{qg@U{hb?GTqEzBHBpFJ8@b6sVw-C77JV2}{m7^F0B3U~TL?x5A zJ0dw-b&*FjRwFCDbfw8#xHcoyh&81(DVGf1nQA}93MWIcR4BHPgNQI;Nl0Op5MJ^m zN(i4HI@RuyC_y=Qmy)tn>3`{)+X8rsN^By@C@{$;GNUX^7R4qK*MWCa7fvF}%nK=X z4vH9*;ZqRus;gzh#b9p^?sNe{>D!9iysJ>!ohWH`N&DE?{Y~+OaRkGw%t1dg+tEU} zXykL0h-*8~SVX>%;Bz=x?wV_(S-gta(?a7o7&#$kA_5R2W5MVSrVnPx*lDXZdj-At}^z65#Qikl4;AE`GD z7^ydbH=yFdDb8e7Or(|jH)4*hAu=btZ1en_@k;Lf{iY**;b_Tfr-Cd^MYQAlJ5-L6{#Iv4q`=aF_iD(P)V6~*7Q{+=Qq`<+3;#Aj za?8M|_@j{@>tK;3p24y*OHdl9G~YD$i{* zNvNt6-+Tw63|E8J!%Fe36yKa~R74k|JO+uak{*L9?s*;`IC67Qitj)~xLW!sqEdY0 zf>w&}CiA77E3FUeTI3%G zD^ee%LhwlOvqH0+q4UY&tAS=!2p$!JXL29YhT7^N*Q|V}QAenZ=SUD+sFjim!J|U( zbY*sm&!_75vI@boG(zyWef?FKSHUJ% zdOy0@KUQ7jezc~HPzBoa9?{e^s{%ZZxQrQhA4MU8i1&y?<=a0-#NJ3O0IaSdn6gh) z_KBe|U0txmF4;bp6|sU+t{INcH=%#7@?>;h0vQJd@iON3F*s>a4e+SW2*2qo+ zT!U_=QDp2MW6#j7Y4g^Wb7zNko%!*V!^hz8VrO-0I51B&?GqnM_(|O*F5p%6iAYnG zePWc6Mf^@ll$L1U5wS-rUPy^~B>K~@~E=t_st##YJWuO!(K{iJzQf6krSOJ?Tu@u`jR7XN|7!zvi7Yks+ zRE#SXYeYAimw6#hW``5nso4BII zCYs*h_m$O03v6tzbR)sgdJ@VIaLVF1k z^zE?jUGCE4UrEs52&~mkK$AvBZaeTCpnY&~upK9N5MVR@?8MUg96+71;r{clT)ap< z7)wCUlzUyc<620B1(#b}L@C0-Fn-Q65T1dQA)nfL)ZXS zi2JydYb@gaBC$DS{QCqNw6w3^H~`B|UVKnX!{4B-#gQ-eKeVl*Gka!#$fbXrawiS_ z5-~ZAbaZx}+3%_|KQ*PXv;L%k|4N?x_s^bfimJkUARqa!^d^~X=8Wj2)xVK%?HLg- ziLWJI)kIUjvE$>1$BxkpQ0mukL=0;LmQe<}UFjC>%}nz~H~M1g1{cch9~&n{XJcgg zb4=6`etZ&7{%UFpy*Z&Rr|HA5j_3@o!t5$&0L$p*+TLxj3TAn}$EQQ(Po;jcarhi` z(Ai~p!Za^s_5Ka?f(JL>4C%<%jt+l4&abUo=bHw3^VF2M9q;BzXHO$s>G;FBUSXzm z_6X(a62Cy58+}HKd7-HVpry$zsgD;AwPdCDqUGwLd8#-f8V#x7gK z8(eSb-#C1%`TGnO=pip5Nc{-an@6!(Q6Zw7%wRX(0C<5MkJAQg26zXIfyVl2v*p%9 z^*h&OG_%wZT1xnjr)KLvKnsol zCL_(^4;eJE0NV7-Cc6Fxd=}Vu4d@v8mZNeWU8D19tB9pl#%w5Fw$1u*3kh< z;^fU^)`!0fSW&oGZ2{Nq^}$5yG}r$6+{b<91HbBP=wokzIXT+gk$Dda3uURit!vHO zx+=gMzb?YakRVY?FXyJ$a-4h;kXZ3Z-ok`aLkzvr%PGB_3aVZay&PZWWQ~ByWt>S_ zF8@vFASLgn`zX1Z`YOL5g~>eQl$Xm~51Un51w^^hieC(KU`Q$(_}ZG=z*9ZJ79=2e zYpF;EGcUjha+Zn@JK<2cM L&>-JUhv@YGYqh5v literal 0 HcmV?d00001 From 6080d1cc42b674515ac31f783a15477f47a28e92 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 18 May 2026 03:57:43 -0700 Subject: [PATCH 1939/2155] errno-util: include -ENOENT in ERRNO_IS_XATTR_ABSENT() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The getxattr(2) man page only enumerates xattr-specific errors (ENODATA, ENOTSUP, ERANGE, E2BIG, ...) in its own ERRORS section, but at the bottom of that section notes that "the errors documented in stat(2) can also be returned." stat(2) returns -ENOENT when a component of the path does not exist, so any xattr lookup against a path can fail with -ENOENT exactly the same way as -ENODATA — both mean "there is nothing here for me to read." The previous definition of ERRNO_IS_NEG_XATTR_ABSENT() reflected only the directly-enumerated errors and missed -ENOENT, so callers that should semantically swallow "the xattr is absent" instead bubbled -ENOENT up as a hard error. The most visible consequence on real fleets has been systemd-journald spamming dmesg with one line per dispatched log message whenever a unit's cgroup directory cannot be found at the time client_context_read_log_filter_patterns() is called — typically inside containers whose journald observes clients whose unit cgroup is no longer present in its view (cgroup-namespace boundary, unit teardown race, transient sub-scope already collapsed back to its unit cgroup, etc.). The same bug pattern lurks at every other cgroup-xattr callsite: systemd-oomd reading user.oomd_avoid / user.oomd_omit / user.oomd_ooms on cgroups it is concurrently killing; killall reading user.survive_final_kill_signal during shutdown; cg_is_delegated() / cg_has_coredump_receive() / cgroup_get_managed_oom_kill_last(); etc. For these, "path is gone" is by construction the same answer as "xattr is not set" — there is no way for the user to have attached an xattr to a path that does not exist. A quick survey of non-cgroup callers (src/portable/portable.c, src/home/{homework-luks,user-record-util}.c, src/random-seed/random-seed-tool.c, src/basic/os-util.c) confirms they all operate on fds or on paths whose absence is already the desired silent-skip outcome, so widening the macro to also fold in -ENOENT does not change observable behavior at any other site. Extend test-xattr-util's getxattr_at_malloc test with an explicit non-existent-path lookup that asserts ERRNO_IS_NEG_XATTR_ABSENT() now matches, alongside the pre-existing non-existent-xattr (-ENODATA) check. --- src/basic/errno-util.h | 7 +++++-- src/shared/dissect-image.c | 4 ++-- src/test/test-xattr-util.c | 10 ++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h index eb2941253dd73..195d13208b460 100644 --- a/src/basic/errno-util.h +++ b/src/basic/errno-util.h @@ -218,10 +218,13 @@ static inline bool ERRNO_IS_NEG_DEVICE_ABSENT_OR_EMPTY(intmax_t r) { } _DEFINE_ABS_WRAPPER(DEVICE_ABSENT_OR_EMPTY); -/* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and - * where it simply doesn't have the requested xattr the same way */ +/* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all, + * where the path component carrying the xattr is missing, and where it simply doesn't have the requested + * xattr the same way. Note that getxattr(2) does not enumerate -ENOENT in its own error list, but inherits + * it via stat(2) (see ERRORS in getxattr(2)) for the path-component-missing case. */ static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) { return r == -ENODATA || + r == -ENOENT || ERRNO_IS_NEG_NOT_SUPPORTED(r); } _DEFINE_ABS_WRAPPER(XATTR_ABSENT); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 23f804bd65943..c5bb52b2afe27 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -3820,7 +3820,7 @@ int verity_settings_load( if (r < 0) { _cleanup_free_ char *p = NULL; - if (r != -ENOENT && !ERRNO_IS_XATTR_ABSENT(r)) + if (!ERRNO_IS_XATTR_ABSENT(r)) return r; p = build_auxiliary_path(image, ".roothash"); @@ -3849,7 +3849,7 @@ int verity_settings_load( if (r < 0) { _cleanup_free_ char *p = NULL; - if (r != -ENOENT && !ERRNO_IS_XATTR_ABSENT(r)) + if (!ERRNO_IS_XATTR_ABSENT(r)) return r; p = build_auxiliary_path(image, ".usrhash"); diff --git a/src/test/test-xattr-util.c b/src/test/test-xattr-util.c index eeb0d8a8afc80..4d39a0a1f10a6 100644 --- a/src/test/test-xattr-util.c +++ b/src/test/test-xattr-util.c @@ -46,6 +46,16 @@ TEST(getxattr_at_malloc) { r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value, /* ret_size= */ NULL); ASSERT_TRUE(ERRNO_IS_NEG_XATTR_ABSENT(r)); + /* A non-existent path component must also be treated as xattr-absent. The kernel + * returns -ENOENT in this case, which is inherited from stat(2)'s error list + * (see ERRORS in getxattr(2)). Without this, callers that semantically just want + * to know "is the xattr there?" end up logging spurious errors for paths that + * are simply gone — e.g. journald reading a unit's cgroup xattr after the + * cgroup directory has been torn down. */ + r = getxattr_at_malloc(fd, "this-path-does-not-exist-XXXXXX", "user.idontexist", + 0, &value, /* ret_size= */ NULL); + ASSERT_TRUE(ERRNO_IS_NEG_XATTR_ABSENT(r)); + safe_close(fd); ASSERT_OK_ERRNO(fd = open(x, O_PATH|O_CLOEXEC)); ASSERT_OK(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value, /* ret_size= */ NULL)); From 08c281304cd6fd7bf20f5eb7aaa81c7ee4283b0c Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Mon, 18 May 2026 13:30:02 -0400 Subject: [PATCH 1940/2155] dissect: guard against ssize_t overflow in LUKS2 header parser The json_len variable is ssize_t, but the subtraction be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE can yield a value exceeding SSIZE_MAX when hdr_len is a large crafted value. This causes signed integer overflow and a subsequent oversized malloc() that fails with -ENOMEM, producing a misleading out-of-memory error. Add an explicit check against SSIZE_MAX before the cast to ssize_t. --- src/shared/dissect-image.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index c5bb52b2afe27..3aeb254fd4dde 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -446,6 +446,9 @@ static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t s if (be64toh(header.hdr_len) <= LUKS2_FIXED_HDR_SIZE || offset > UINT64_MAX - be64toh(header.hdr_len)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid LUKS header length: %" PRIu64 ".", be64toh(header.hdr_len)); + if (be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE > (uint64_t) SSIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header JSON area too large: %" PRIu64 ".", be64toh(header.hdr_len)); + json_len = be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE; json = malloc(json_len + 1); if (!json) From a190e64dfb348273c626267efa5876055939fdeb Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Mon, 18 May 2026 14:39:44 -0400 Subject: [PATCH 1941/2155] dissect: use practical 16 MiB limit instead of SSIZE_MAX As suggested by @yuwata, SSIZE_MAX is still too large and would cause malloc() to fail anyway. Use a 16 MiB limit which is generous compared to the typical 4 MiB maximum in cryptsetup (LUKS2_HDR_OFFSET_MAX). --- 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 3aeb254fd4dde..8483a16e9442e 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -446,7 +446,7 @@ static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t s if (be64toh(header.hdr_len) <= LUKS2_FIXED_HDR_SIZE || offset > UINT64_MAX - be64toh(header.hdr_len)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid LUKS header length: %" PRIu64 ".", be64toh(header.hdr_len)); - if (be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE > (uint64_t) SSIZE_MAX) + if (be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE > 16U * 1024U * 1024U) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header JSON area too large: %" PRIu64 ".", be64toh(header.hdr_len)); json_len = be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE; From 15783e4657bdc5b80f91cd20b1dc08b8e17b565c Mon Sep 17 00:00:00 2001 From: Matteo Croce Date: Thu, 25 Sep 2025 21:17:26 +0200 Subject: [PATCH 1942/2155] oomd: ruleset parsing Add to oomd the capability to define rulesets in /etc/systemd/oomd/rules.d/ and then reference them with a new config option OOMRule= --- man/oomd.conf.xml | 86 +++++ man/org.freedesktop.systemd1.xml | 66 +++- man/systemd.resource-control.xml | 29 ++ src/core/cgroup.c | 5 + src/core/cgroup.h | 1 + src/core/dbus-cgroup.c | 33 ++ src/core/execute-serialize.c | 8 + src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 59 +++ src/core/load-fragment.h | 1 + src/core/unit.c | 2 +- src/core/varlink-cgroup.c | 1 + src/core/varlink.c | 19 +- src/oom/oomd-conf.c | 176 +++++++++ src/oom/oomd-conf.h | 8 + src/oom/oomd-manager.c | 522 +++++++++++++++++++++++++- src/oom/oomd-manager.h | 31 ++ src/oom/oomd-util.c | 22 +- src/oom/oomd-util.h | 3 +- src/shared/bus-unit-util.c | 1 + src/shared/varlink-io.systemd.Unit.c | 2 + src/shared/varlink-io.systemd.oom.c | 3 +- test/units/TEST-55-OOMD.sh | 121 ++++++ 23 files changed, 1175 insertions(+), 25 deletions(-) diff --git a/man/oomd.conf.xml b/man/oomd.conf.xml index f8c3c0a173e15..1e33f3b3ded43 100644 --- a/man/oomd.conf.xml +++ b/man/oomd.conf.xml @@ -68,6 +68,92 @@ + + OOM Rulesets + + systemd-oomd supports custom rulesets that define conditions and actions for + OOM handling on a per-unit basis. Ruleset files use the .oomrule extension and are + loaded from /etc/systemd/oomd/rules.d/, + /run/systemd/oomd/rules.d/, + /usr/local/lib/systemd/oomd/rules.d/, and + /usr/lib/systemd/oomd/rules.d/. + Units opt into rulesets via the OOMRules= setting in + systemd.resource-control5, + which takes a space-separated list of ruleset names (the file name without the .oomrule + extension). + + Each ruleset file contains a [Rule] section with the following options. At least + one of MemoryPressureAbove= or SwapUsageMax= must be configured; + rulesets with no conditions are ignored. If both are set, the conditions are combined with AND, i.e. the + action is only triggered when both thresholds are exceeded simultaneously. + + + + MemoryPressureAbove= + + Sets the memory pressure threshold above which the rule's action will be triggered. + The memory pressure represents the fraction of time in a 10 second window in which all tasks in the + control group were delayed (PSI full avg10). Takes a value specified in percent + (when suffixed with %), permille () or permyriad + (), between 0% and 100%, inclusive. If unset, this condition is not + evaluated. A value of 100% can never be exceeded and is + therefore rejected with a warning; a value of 0% makes the condition true on any + observed pressure, which is usually not useful. + + + + + + SwapUsageMax= + + Sets the system-wide swap usage threshold above which the rule's action will be + triggered. Takes a value specified in percent (when suffixed with %), + permille () or permyriad (), + between 0% and 100%, inclusive. If unset, this condition is not evaluated. A value of + 100% can never be exceeded and is therefore rejected with + a warning; a value of 0% fires as soon as any swap is in use, which is usually + not useful. + + + + + + Action= + + Specifies the action to take when the rule's conditions are met. Takes one of + kill-all, kill-by-pgscan, or + kill-by-swap. This setting is mandatory; rulesets without + Action= are ignored. + + + kill-all sends SIGKILL to every process + in the unit's cgroup hierarchy, including any descendant cgroups. + + kill-by-pgscan selects and kills the descendant cgroup with + the highest recent page scan (reclaim) rate. + + kill-by-swap selects and kills the descendant cgroup with the + highest swap usage. + + + + + + + LastingSec= + + Sets the duration the conditions must be continuously met before the action is taken. + Takes a time span value, see + systemd.time7 + for details on the permitted syntax. Defaults to 0, i.e. the action is taken + immediately when the conditions are met. + + + + + + + [OOM] Section Options diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 7efc899dba251..2d63050a68620 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -3098,6 +3098,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -3777,6 +3779,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4479,6 +4483,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -5388,6 +5394,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -6083,6 +6091,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6759,6 +6769,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -7491,6 +7503,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -8110,6 +8124,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8694,6 +8710,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -9559,6 +9577,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -10160,6 +10180,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10726,6 +10748,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -11444,6 +11468,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -11627,6 +11653,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11825,6 +11853,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -12046,6 +12076,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -12243,6 +12275,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12465,6 +12499,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12801,8 +12837,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Socket Unit Objects @@ -12876,8 +12913,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Mount Unit Objects @@ -12946,8 +12984,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Swap Unit Objects @@ -13014,8 +13053,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Slice Unit Objects @@ -13052,8 +13092,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Scope Unit Objects @@ -13088,8 +13129,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ CPUPressureThresholdUSec, CPUPressureWatch, IOPressureThresholdUSec, - IOPressureWatch, and - CPUSetPartition were added in version 261. + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Job Objects diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index fcad4b31839ea..58e923b618491 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1630,6 +1630,35 @@ DeviceAllow=/dev/loop-control + + OOMRules= + + + Takes a space-separated list of OOM ruleset names. The rulesets are defined in + .oomrule files placed in + /etc/systemd/oomd/rules.d/, + /run/systemd/oomd/rules.d/, + /usr/local/lib/systemd/oomd/rules.d/, or + /usr/lib/systemd/oomd/rules.d/. When set, + systemd-oomd.service8 + will monitor this unit's cgroup and evaluate the specified rulesets against it. + Each ruleset defines conditions (such as memory pressure or swap usage thresholds) and an action + to take when those conditions are met. See + oomd.conf5 for + details on the available ruleset options. + + Setting this property will also result in After= and + Wants= dependencies on systemd-oomd.service unless + DefaultDependencies=no. + + Defaults to an empty list, which means no rulesets are applied. Note that each monitored + cgroup incurs a per-interval walk of its descendant cgroup tree, so monitoring very large numbers of + cgroups via OOMRules= may have a measurable performance impact. + + + + + MemoryPressureWatch= diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 48b7df0e00c08..543d1ac8e3c43 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -284,6 +284,8 @@ void cgroup_context_done(CGroupContext *c) { c->delegate_subgroup = mfree(c->delegate_subgroup); + c->moom_rules = strv_free(c->moom_rules); + nft_set_context_clear(&c->nft_set_context); } @@ -670,6 +672,9 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) fprintf(f, "%sNFTSet: %s:%s:%s:%s\n", prefix, nft_set_source_to_string(nft_set->source), nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set); + + STRV_FOREACH(rule, c->moom_rules) + fprintf(f, "%sOOMRules: %s\n", prefix, *rule); } void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) { diff --git a/src/core/cgroup.h b/src/core/cgroup.h index b7213d8d59494..e3d33ad5e0910 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -203,6 +203,7 @@ typedef struct CGroupContext { uint32_t moom_mem_pressure_limit; /* Normalized to 2^32-1 == 100% */ usec_t moom_mem_pressure_duration_usec; ManagedOOMPreference moom_preference; + char **moom_rules; /* Pressure logic */ CGroupPressure pressure[_PRESSURE_RESOURCE_MAX]; diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 6cecc8b9e7419..168bdf10c13da 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -424,6 +424,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("ManagedOOMMemoryPressureLimit", "u", NULL, offsetof(CGroupContext, moom_mem_pressure_limit), 0), SD_BUS_PROPERTY("ManagedOOMMemoryPressureDurationUSec", "t", bus_property_get_usec, offsetof(CGroupContext, moom_mem_pressure_duration_usec), 0), SD_BUS_PROPERTY("ManagedOOMPreference", "s", property_get_managed_oom_preference, offsetof(CGroupContext, moom_preference), 0), + SD_BUS_PROPERTY("OOMRules", "as", NULL, offsetof(CGroupContext, moom_rules), 0), SD_BUS_PROPERTY("BPFProgram", "a(ss)", property_get_bpf_foreign_program, 0, 0), SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0), SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0), @@ -1796,6 +1797,38 @@ int bus_cgroup_set_property( return 1; } + + if (streq(name, "OOMRules")) { + _cleanup_strv_free_ char **oom_rules = NULL; + + if (!UNIT_VTABLE(u)->can_set_managed_oom) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set %s for this unit type", name); + + r = sd_bus_message_read_strv(message, &oom_rules); + if (r < 0) + return r; + + STRV_FOREACH(rule, oom_rules) + if (!string_is_safe(*rule, STRING_FILENAME)) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid rule name: %s", *rule); + + strv_uniq(oom_rules); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *joined = strv_join(oom_rules, " "); + if (!joined) + return -ENOMEM; + + strv_free_and_replace(c->moom_rules, oom_rules); + + unit_write_settingf(u, flags, name, "OOMRules=\nOOMRules=%s", joined); + + (void) manager_varlink_send_managed_oom_update(u); + } + + return 1; + } + if (STR_IN_SET(name, "SocketBindAllow", "SocketBindDeny")) { CGroupSocketBindItem **list; uint16_t nr_ports, port_min; diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 5f205772fd81a..953b0d9894845 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -285,6 +285,10 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; + r = serialize_strv(f, "exec-cgroup-context-managed-oom-rules", c->moom_rules); + if (r < 0) + return r; + 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; @@ -650,6 +654,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = deserialize_usec(val, &c->moom_mem_pressure_duration_usec); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-managed-oom-rules="))) { + r = deserialize_strv(val, &c->moom_rules); + if (r < 0) + return r; } else if ((val = startswith(l, "exec-cgroup-context-memory-pressure-watch="))) { c->pressure[PRESSURE_MEMORY].watch = cgroup_pressure_watch_from_string(val); if (c->pressure[PRESSURE_MEMORY].watch < 0) diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index b8d744c1f4959..0e2d679f97805 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -272,6 +272,7 @@ {{type}}.ManagedOOMMemoryPressureLimit, config_parse_managed_oom_mem_pressure_limit, 0, offsetof({{type}}, cgroup_context.moom_mem_pressure_limit) {{type}}.ManagedOOMMemoryPressureDurationSec, config_parse_managed_oom_mem_pressure_duration_sec, 0, offsetof({{type}}, cgroup_context.moom_mem_pressure_duration_usec) {{type}}.ManagedOOMPreference, config_parse_managed_oom_preference, 0, offsetof({{type}}, cgroup_context.moom_preference) +{{type}}.OOMRules, config_parse_managed_oom_rules, 1, offsetof({{type}}, cgroup_context.moom_rules) {{type}}.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0 {{type}}.BPFProgram, config_parse_bpf_foreign_program, 0, offsetof({{type}}, cgroup_context) {{type}}.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_allow) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 2a268d813b5bb..9b2fa71be398f 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -4090,6 +4090,65 @@ int config_parse_managed_oom_mem_pressure_duration_sec( return 0; } +int config_parse_managed_oom_rules( + 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) { + + char ***sv = ASSERT_PTR(data); + UnitType t; + int r; + + assert(rvalue); + + t = unit_name_to_type(unit); + assert(t != _UNIT_TYPE_INVALID); + + if (!unit_vtable[t]->can_set_managed_oom) + return log_syntax(unit, LOG_WARNING, filename, line, 0, "%s= is not supported for this unit type, ignoring.", lvalue); + + if (isempty(rvalue)) { + *sv = strv_free(*sv); + return 0; + } + + /* Tokenize once: validate each rule name (rulesets are loaded from .oomrule files) + * and accumulate into a local strv. Invalid rule names are skipped individually + * with a warning so the rest of the line still applies. */ + _cleanup_strv_free_ char **strv = NULL; + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r == 0) + break; + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + if (!string_is_safe(word, STRING_FILENAME)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid rule name in %s=, ignoring: %s", lvalue, word); + continue; + } + + r = strv_consume(&strv, TAKE_PTR(word)); + if (r < 0) + return log_oom(); + } + + r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates= */ ltype); + if (r < 0) + return log_oom(); + + return 0; +} + int config_parse_device_allow( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index fafb00402830e..99b53626203ec 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -144,6 +144,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_pid_file); CONFIG_PARSER_PROTOTYPE(config_parse_exit_status); CONFIG_PARSER_PROTOTYPE(config_parse_disable_controllers); CONFIG_PARSER_PROTOTYPE(config_parse_oom_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_managed_oom_rules); CONFIG_PARSER_PROTOTYPE(config_parse_numa_policy); CONFIG_PARSER_PROTOTYPE(config_parse_numa_mask); CONFIG_PARSER_PROTOTYPE(config_parse_ip_filter_bpf_progs); diff --git a/src/core/unit.c b/src/core/unit.c index 8ed74b080d144..f81083a70f753 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1626,7 +1626,7 @@ static int unit_add_oomd_dependencies(Unit *u) { if (!c) return 0; - bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL; + bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL || !strv_isempty(c->moom_rules); if (!wants_oomd) return 0; diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index 9953707417d5d..e031f00368bab 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -326,6 +326,7 @@ 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), JSON_BUILD_PAIR_ENUM("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), + JSON_BUILD_PAIR_STRV_NON_EMPTY("OOMRules", c->moom_rules), 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), JSON_BUILD_PAIR_ENUM("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), diff --git a/src/core/varlink.c b/src/core/varlink.c index 09817b6dce2c4..f6b474c636307 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -11,6 +11,7 @@ #include "path-util.h" #include "pidref.h" #include "string-util.h" +#include "strv.h" #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" @@ -28,10 +29,11 @@ static const char* const managed_oom_mode_properties[] = { "ManagedOOMSwap", "ManagedOOMMemoryPressure", + "OOMRules", }; static int build_managed_oom_json_array_element(Unit *u, const char *property, sd_json_variant **ret_v) { - bool use_limit = false, use_duration = false; + bool use_limit = false, use_duration = false, use_rules = false; CGroupContext *c; const char *mode; @@ -60,15 +62,25 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, s mode = managed_oom_mode_to_string(c->moom_mem_pressure); use_limit = c->moom_mem_pressure_limit > 0; use_duration = c->moom_mem_pressure_duration_usec != USEC_INFINITY; + } else if (streq(property, "OOMRules")) { + if (strv_isempty(c->moom_rules)) + mode = managed_oom_mode_to_string(MANAGED_OOM_AUTO); + else { + mode = managed_oom_mode_to_string(MANAGED_OOM_KILL); + use_rules = true; + } } else return -EINVAL; + assert(mode); + return sd_json_buildo(ret_v, 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)), - SD_JSON_BUILD_PAIR_CONDITION(use_duration, "duration", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_duration_usec))); + SD_JSON_BUILD_PAIR_CONDITION(use_duration, "duration", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_duration_usec)), + SD_JSON_BUILD_PAIR_CONDITION(use_rules, "rules", SD_JSON_BUILD_STRV(c->moom_rules))); } static int build_managed_oom_cgroups_json(Manager *m, bool allow_empty, sd_json_variant **ret) { @@ -109,7 +121,8 @@ static int build_managed_oom_cgroups_json(Manager *m, bool allow_empty, sd_json_ /* For the initial varlink call we only care about units that enabled (i.e. mode is not * set to "auto") oomd properties. */ if (!(streq(*i, "ManagedOOMSwap") && c->moom_swap == MANAGED_OOM_KILL) && - !(streq(*i, "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL)) + !(streq(*i, "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL) && + !(streq(*i, "OOMRules") && !strv_isempty(c->moom_rules))) continue; r = build_managed_oom_json_array_element(u, *i, &e); diff --git a/src/oom/oomd-conf.c b/src/oom/oomd-conf.c index f0091e27561c7..bd3d0003b07e5 100644 --- a/src/oom/oomd-conf.c +++ b/src/oom/oomd-conf.c @@ -1,11 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "conf-files.h" #include "conf-parser.h" +#include "hashmap.h" #include "log.h" #include "oomd-conf.h" #include "oomd-manager.h" #include "parse-util.h" +#include "path-util.h" +#include "percent-util.h" +#include "stat-util.h" +#include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" static int config_parse_duration( @@ -66,7 +73,143 @@ void manager_set_defaults(Manager *m) { log_warning_errno(r, "Failed to set default for default_mem_pressure_limit, ignoring: %m"); } +/* OOMD_ACTION_NONE is intentionally omitted — it's the "unset" sentinel. Rulesets with + * .action == OOMD_ACTION_NONE are rejected at load time, so oomd_action_to_string() must + * only be called on rulesets that have already passed ruleset_load_one's validation + * (otherwise it returns NULL). */ +static const char* const oomd_action_table[] = { + [OOMD_ACTION_KILL_ALL] = "kill-all", + [OOMD_ACTION_KILL_BY_PGSCAN] = "kill-by-pgscan", + [OOMD_ACTION_KILL_BY_SWAP] = "kill-by-swap", +}; + +DEFINE_STRING_TABLE_LOOKUP(oomd_action, OomdAction); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_oomd_action, oomd_action, OomdAction); + +void oomd_ruleset_free(OomdRuleset *ruleset) { + if (!ruleset) + return; + hashmap_free(ruleset->start_times); + free(ruleset->name); + free(ruleset); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OomdRuleset*, oomd_ruleset_free, NULL); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(oomd_ruleset_hash_ops, char, string_hash_func, string_compare_func, OomdRuleset, oomd_ruleset_free); + +static int ruleset_load_one(Manager *m, const char *filename) { + _cleanup_free_ char *name = NULL; + _cleanup_(oomd_ruleset_freep) OomdRuleset *ruleset = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct stat st; + int r; + + assert(m); + assert(filename); + + /* Pin the file via an fd so the empty-file check and the parse operate on the same + * inode (avoids TOCTOU between null_or_empty_path() and a subsequent open()). */ + f = fopen(filename, "re"); + if (!f) + return log_warning_errno(errno, "Failed to open '%s': %m", filename); + + if (fstat(fileno(f), &st) < 0) + return log_warning_errno(errno, "Failed to stat '%s': %m", filename); + + if (null_or_empty(&st)) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + r = path_extract_filename(filename, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract file name of '%s': %m", filename); + + char *e = ASSERT_PTR(endswith(name, ".oomrule")); + *e = 0; + + /* Apply the same validation the DBus setter and the config parser use, so that any + * ruleset we accept here is actually referenceable via OOMRules= from a unit. */ + if (!string_is_safe(name, STRING_FILENAME)) { + log_warning("Invalid ruleset name '%s' derived from '%s', ignoring.", name, filename); + return 0; + } + + ruleset = new(OomdRuleset, 1); + if (!ruleset) + return log_oom(); + + *ruleset = (OomdRuleset) { + .name = TAKE_PTR(name), + .memory_pressure_above = -1, + .swap_above = -1, + }; + + const ConfigTableItem items[] = { + { "Rule", "MemoryPressureAbove", config_parse_permyriad, 0, &ruleset->memory_pressure_above }, + { "Rule", "SwapUsageMax", config_parse_permyriad, 0, &ruleset->swap_above }, + { "Rule", "Action", config_parse_oomd_action, 0, &ruleset->action }, + { "Rule", "LastingSec", config_parse_sec, 0, &ruleset->lasting_usec }, + {} + }; + + r = config_parse( + /* unit= */ NULL, + filename, + f, + "Rule\0", + config_item_table_lookup, + items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stat= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse ruleset file '%s': %m", filename); + + if (ruleset->memory_pressure_above < 0 && ruleset->swap_above < 0) { + log_warning("Ruleset '%s' has no conditions configured (MemoryPressureAbove= or SwapUsageMax=), ignoring.", ruleset->name); + return 0; + } + + if (ruleset->action == OOMD_ACTION_NONE) { + log_warning("Ruleset '%s' has no Action= configured, ignoring.", ruleset->name); + return 0; + } + + if (ruleset->lasting_usec == USEC_INFINITY) { + log_warning("Ruleset '%s' has LastingSec=infinity which can never be satisfied, ignoring.", ruleset->name); + return 0; + } + + /* A threshold at the maximum can never be exceeded, so the condition would never fire. + * Report the normalized percent value so the warning matches regardless of whether the + * user wrote '100%', '1000‰' or '10000‱'. */ + if (ruleset->memory_pressure_above == 10000) { + log_warning("Ruleset '%s' has MemoryPressureAbove=" PERMYRIAD_AS_PERCENT_FORMAT_STR " (the maximum) which can never be exceeded, ignoring.", + ruleset->name, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->memory_pressure_above)); + return 0; + } + + if (ruleset->swap_above == 10000) { + log_warning("Ruleset '%s' has SwapUsageMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR " (the maximum) which can never be exceeded, ignoring.", + ruleset->name, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->swap_above)); + return 0; + } + + /* Duplicates cannot occur here: conf_files_list_strv deduplicates filenames across + * directories, and hashmap_clear is called before loading. The value destructor in + * oomd_ruleset_hash_ops handles cleanup during hashmap_clear/hashmap_free. */ + r = hashmap_ensure_replace(&m->rulesets, &oomd_ruleset_hash_ops, ruleset->name, ruleset); + if (r < 0) + return log_error_errno(r, "Failed to register ruleset '%s': %m", ruleset->name); + + TAKE_PTR(ruleset); + + return 0; +} + void manager_parse_config_file(Manager *m) { + _cleanup_strv_free_ char **files = NULL; int r; assert(m); @@ -88,4 +231,37 @@ void manager_parse_config_file(Manager *m) { /* userdata= */ m); if (r >= 0) log_debug("Config file successfully parsed."); + + r = conf_files_list_strv(&files, ".oomrule", /* root= */ NULL, CONF_FILES_WARN, RULESET_DIRS); + if (r < 0) { + /* On enumeration failure, keep the previously loaded rulesets rather than clearing them — + * a transient I/O error shouldn't cause in-flight units to silently lose their OOM policy. */ + log_error_errno(r, "Failed to enumerate ruleset files, keeping previously loaded rulesets: %m"); + return; + } + + /* Clear all rulesets and re-parse. This intentionally resets any accumulated + * start_times (LastingSec timers), since the ruleset definitions may have changed. */ + hashmap_clear(m->rulesets); + + STRV_FOREACH(f, files) + (void) ruleset_load_one(m, *f); + + if (DEBUG_LOGGING) { + char *name; + OomdRuleset *ruleset; + HASHMAP_FOREACH_KEY(ruleset, name, m->rulesets) { + log_debug("Registered ruleset: %s", name); + if (ruleset->memory_pressure_above >= 0) + log_debug(" MemoryPressureAbove=" PERMYRIAD_AS_PERCENT_FORMAT_STR, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->memory_pressure_above)); + else + log_debug(" MemoryPressureAbove=unset"); + if (ruleset->swap_above >= 0) + log_debug(" SwapUsageMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->swap_above)); + else + log_debug(" SwapUsageMax=unset"); + log_debug(" Action=%s", oomd_action_to_string(ruleset->action)); + log_debug(" LastingSec=%s", FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + } + } } diff --git a/src/oom/oomd-conf.h b/src/oom/oomd-conf.h index 429b976b91be2..8f715e81c6fe0 100644 --- a/src/oom/oomd-conf.h +++ b/src/oom/oomd-conf.h @@ -1,8 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "string-table.h" /* IWYU pragma: keep */ + typedef struct Manager Manager; +typedef struct OomdRuleset OomdRuleset; +typedef enum OomdAction OomdAction; + +void oomd_ruleset_free(OomdRuleset *ruleset); void manager_set_defaults(Manager *m); void manager_parse_config_file(Manager *m); + +DECLARE_STRING_TABLE_LOOKUP(oomd_action, OomdAction); diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index 382a246c2dddb..7ec6684f6e2bd 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -24,6 +24,7 @@ #include "percent-util.h" #include "set.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "varlink-io.systemd.oom.h" #include "varlink-io.systemd.service.h" @@ -35,12 +36,14 @@ typedef struct ManagedOOMMessage { char *property; uint32_t limit; usec_t duration; + char **rules; } ManagedOOMMessage; static void managed_oom_message_destroy(ManagedOOMMessage *message) { assert(message); free(message->path); free(message->property); + strv_free(message->rules); } static JSON_DISPATCH_ENUM_DEFINE(dispatch_managed_oom_mode, ManagedOOMMode, managed_oom_mode_from_string); @@ -55,6 +58,7 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p { "property", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ManagedOOMMessage, property), SD_JSON_MANDATORY }, { "limit", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 }, { "duration", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ManagedOOMMessage, duration), 0 }, + { "rules", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv, offsetof(ManagedOOMMessage, rules), 0 }, {}, }; @@ -101,11 +105,31 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p "(" UID_FMT " != " UID_FMT ")", uid, cg_uid); } - monitor_hm = streq(message.property, "ManagedOOMSwap") ? - m->monitored_swap_cgroup_contexts : m->monitored_mem_pressure_cgroup_contexts; + if (streq(message.property, "ManagedOOMSwap")) + monitor_hm = m->monitored_swap_cgroup_contexts; + else if (streq(message.property, "OOMRules")) + monitor_hm = m->monitored_rules_cgroup_contexts; + else if (streq(message.property, "ManagedOOMMemoryPressure")) + monitor_hm = m->monitored_mem_pressure_cgroup_contexts; + else { + log_debug("Unknown property '%s', ignoring.", message.property); + continue; + } if (message.mode == MANAGED_OOM_AUTO) { (void) oomd_cgroup_context_unref(hashmap_remove(monitor_hm, empty_to_root(message.path))); + + /* Clean up start_times entries for this cgroup across all rulesets + * to prevent stale timers from causing premature action triggers + * if the cgroup re-subscribes later. */ + if (streq(message.property, "OOMRules")) { + OomdRuleset *ruleset; + HASHMAP_FOREACH(ruleset, m->rulesets) { + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(ruleset->start_times, empty_to_root(message.path), (void **) &key)); + } + } + continue; } @@ -124,6 +148,57 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p else duration = m->default_mem_pressure_duration_usec; + /* For OOMRules, only insert/update if rules are actually provided */ + if (streq(message.property, "OOMRules")) { + if (strv_isempty(message.rules)) + continue; + + /* Avoid re-reading memory.pressure/pgscan/etc. on every OOMRules update for a + * cgroup we already track — fetch the existing context first and only acquire + * a fresh one if the cgroup is new. */ + ctx = hashmap_get(monitor_hm, empty_to_root(message.path)); + if (!ctx) { + r = oomd_insert_cgroup_context(NULL, monitor_hm, message.path); + if (r == -ENOMEM) + return r; + if (r < 0) { + log_debug_errno(r, "Failed to insert message, ignoring: %m"); + continue; + } + ctx = hashmap_get(monitor_hm, empty_to_root(message.path)); + } + + if (ctx) { + /* For each rule being dropped from this cgroup's subscription, + * remove its start_times entry so the timer doesn't linger. */ + STRV_FOREACH(old_rule, ctx->rules) { + if (strv_contains(message.rules, *old_rule)) + continue; + OomdRuleset *dropped = hashmap_get(m->rulesets, *old_rule); + if (!dropped) + continue; + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(dropped->start_times, empty_to_root(message.path), (void**) &key)); + } + + strv_free_and_replace(ctx->rules, message.rules); + + /* Defensively deduplicate: the DBus setter and config parser both + * dedupe, but another varlink client could in principle send + * duplicates, which would cause redundant per-interval evaluation. */ + strv_uniq(ctx->rules); + + /* Warn about any referenced rules that don't exist. Done here + * (once per subscription change) rather than per-interval to avoid + * log spam when a unit references a missing ruleset. */ + STRV_FOREACH(new_rule, ctx->rules) + if (!hashmap_contains(m->rulesets, *new_rule)) + log_warning("Cgroup %s references undefined ruleset '%s', it will be ignored.", + ctx->path, *new_rule); + } + continue; + } + r = oomd_insert_cgroup_context(NULL, monitor_hm, message.path); if (r == -ENOMEM) return r; @@ -145,6 +220,12 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p if (r < 0) return log_error_errno(r, "Failed to toggle enabled state of swap context source: %m"); + /* Toggle wake-ups for "OOMRules" if entries are present. */ + r = sd_event_source_set_enabled(m->rules_context_event_source, + hashmap_isempty(m->monitored_rules_cgroup_contexts) ? SD_EVENT_OFF : SD_EVENT_ON); + if (r < 0) + return log_error_errno(r, "Failed to toggle enabled state of rules context source: %m"); + return 0; } @@ -408,7 +489,7 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void log_debug_errno(r, "Failed to get monitored swap cgroup candidates, ignoring: %m"); threshold = m->system_context.swap_total * THRESHOLD_SWAP_USED_PERCENT / 100; - r = oomd_select_by_swap_usage(candidates, threshold, &selected); + r = oomd_select_by_swap_usage(candidates, /* prefix= */ NULL, threshold, &selected); if (r < 0) return log_error_errno(r, "Failed to select any cgroups based on swap: %m"); if (r == 0) { @@ -584,6 +665,398 @@ static int monitor_memory_pressure_contexts_handler(sd_event_source *s, uint64_t return 0; } +static int ruleset_execute_action( + Manager *m, + OomdCGroupContext *ctx, + OomdRuleset *ruleset, + const char *rule_name, + usec_t usec_now) { + + _cleanup_free_ char *reason = NULL; + int r; + + assert(m); + assert(ctx); + assert(ruleset); + assert(rule_name); + + if (ruleset->lasting_usec > 0) + log_notice("Rule '%s' conditions met for cgroup %s (lasting %s), taking action %s", + rule_name, + ctx->path, + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC), + oomd_action_to_string(ruleset->action)); + else + log_notice("Rule '%s' conditions met for cgroup %s, taking action %s", + rule_name, + ctx->path, + oomd_action_to_string(ruleset->action)); + + reason = strjoin("rule ", rule_name); + if (!reason) + return log_oom(); + + if (ruleset->action == OOMD_ACTION_KILL_ALL) { + r = oomd_cgroup_kill_mark(m, ctx, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill all processes in %s: %m", ctx->path); + return 0; + } + } else if (ruleset->action == OOMD_ACTION_KILL_BY_PGSCAN) { + OomdCGroupContext *selected = NULL; + + /* Check if there was reclaim activity in the given interval. If there isn't any reclaim + * pressure, killing won't help — well-behaved processes faulting in recently resident + * pages will keep pressure high even after the offending cgroup is killed. */ + if (usec_sub_unsigned(usec_now, ctx->last_had_mem_reclaim) > RECLAIM_DURATION_USEC) { + log_debug("No reclaim activity for %s, skipping pgscan-based action", ctx->path); + return 0; + } + + r = oomd_select_by_pgscan_rate(m->monitored_rules_cgroup_contexts_candidates, ctx->path, &selected); + if (r < 0) { + log_notice_errno(r, "Failed to select cgroup by pgscan rate for %s: %m", ctx->path); + return 0; + } + if (r == 0) { + log_debug("No cgroup candidates found for pgscan-based action for %s", ctx->path); + return 0; + } + + r = oomd_cgroup_kill_mark(m, selected, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill processes in %s: %m", selected->path); + return 0; + } + } else if (ruleset->action == OOMD_ACTION_KILL_BY_SWAP) { + OomdCGroupContext *selected = NULL; + uint64_t threshold; + + if (m->system_context.swap_total == 0) { + if (!ruleset->warned_no_swap) { + log_warning("Rule '%s' uses kill-by-swap action but no swap is configured, skipping (further occurrences suppressed)", rule_name); + ruleset->warned_no_swap = true; + } + return 0; + } + + /* Swap came back — clear the latch so re-disabling swap warns again. */ + ruleset->warned_no_swap = false; + + threshold = m->system_context.swap_total * THRESHOLD_SWAP_USED_PERCENT / 100; + r = oomd_select_by_swap_usage(m->monitored_rules_cgroup_contexts_candidates, ctx->path, threshold, &selected); + if (r < 0) { + log_notice_errno(r, "Failed to select cgroup by swap usage for %s: %m", ctx->path); + return 0; + } + if (r == 0) { + log_debug("No cgroup candidates found for swap-based action for %s", ctx->path); + return 0; + } + + r = oomd_cgroup_kill_mark(m, selected, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill processes in %s: %m", selected->path); + return 0; + } + } else + assert_not_reached(); + + return 1; +} + +static int ruleset_check_conditions( + Manager *m, + OomdCGroupContext *ctx, + OomdRuleset *ruleset, + const char *rule_name, + usec_t usec_now) { + + int r; + + assert(m); + assert(ctx); + assert(ruleset); + assert(rule_name); + + /* Check memory pressure condition. + * memory_pressure_above is in permyriad (0-10000, i.e. 6050 = 60.50%). + * store_loadavg_fixed_point takes integer and decimal parts of a percentage, + * so divide/modulo by 100 to split permyriad into percent + centipercent. */ + if (ruleset->memory_pressure_above >= 0) { + loadavg_t threshold; + r = store_loadavg_fixed_point(ruleset->memory_pressure_above / 100, + ruleset->memory_pressure_above % 100, + &threshold); + if (r < 0) + return log_debug_errno(r, "Failed to convert pressure threshold for rule '%s': %m", rule_name); + + if (ctx->memory_pressure.avg10 <= threshold) + goto reset; + } + + /* swap_above means take action when swap usage is above threshold. + * oomd_swap_free_below returns true when swap free is below threshold, + * so if swap_above is X%, check if swap free is below (100-X)%. + * When no swap is configured, the condition cannot be meaningfully evaluated. */ + if (ruleset->swap_above >= 0) { + if (m->system_context.swap_total == 0 || + !oomd_swap_free_below(&m->system_context, 10000 - ruleset->swap_above)) + goto reset; + } + + /* All conditions met, check if LastingSec requirement is satisfied */ + usec_t *start_time = hashmap_get(ruleset->start_times, ctx->path); + if (!start_time) { + /* First time seeing this condition - record the start time */ + _cleanup_free_ usec_t *new_start_time = new(usec_t, 1); + if (!new_start_time) + return log_oom(); + + *new_start_time = usec_now; + + _cleanup_free_ char *path_copy = strdup(ctx->path); + if (!path_copy) + return log_oom(); + + r = hashmap_ensure_put(&ruleset->start_times, &string_hash_ops_free_free, path_copy, new_start_time); + if (r < 0) + return log_error_errno(r, "Failed to record start time for rule '%s' on %s: %m", + rule_name, ctx->path); + TAKE_PTR(path_copy); + TAKE_PTR(new_start_time); + + /* If lasting_usec is 0, take action immediately */ + if (ruleset->lasting_usec == 0) + return true; + + log_debug("Rule '%s' conditions met for cgroup %s, waiting for %s", + rule_name, ctx->path, + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + return false; + } + + /* Check if the condition has been true for long enough */ + usec_t duration = usec_sub_unsigned(usec_now, *start_time); + if (duration >= ruleset->lasting_usec) + return true; + + log_debug("Rule '%s' conditions met for cgroup %s for %s (need %s)", + rule_name, ctx->path, + FORMAT_TIMESPAN(duration, USEC_PER_SEC), + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + return false; + +reset: + /* Conditions no longer met — remove start time if it exists. */ + { + _cleanup_free_ char *old_key = NULL; + _cleanup_free_ usec_t *old_start_time = + hashmap_remove2(ruleset->start_times, ctx->path, (void**) &old_key); + if (old_start_time) + log_debug("Rule '%s' conditions no longer met for cgroup %s, resetting timer", + rule_name, ctx->path); + } + return false; +} + +/* After a reload, some cgroups may reference rulesets that no longer exist (or didn't exist yet + * when the cgroup subscribed). Warn once per (cgroup, rule) pair so the operator sees the mismatch, + * without spamming the per-interval evaluation loop. */ +static void warn_missing_rulesets(Manager *m) { + OomdCGroupContext *ctx; + + assert(m); + + HASHMAP_FOREACH(ctx, m->monitored_rules_cgroup_contexts) + STRV_FOREACH(rule, ctx->rules) + if (!hashmap_contains(m->rulesets, *rule)) + log_warning("Cgroup %s references undefined ruleset '%s', it will be ignored.", + ctx->path, *rule); +} + +/* Remove start_times entries for cgroups that are no longer in monitored_rules_cgroup_contexts. + * Cgroups can vanish silently (unit stops, cgroup destroyed) without an explicit unsubscribe + * message, so we periodically reconcile to prevent unbounded growth of start_times. */ +static int prune_stale_ruleset_start_times(Manager *m) { + OomdRuleset *ruleset; + int r; + + assert(m); + + HASHMAP_FOREACH(ruleset, m->rulesets) { + _cleanup_strv_free_ char **to_remove = NULL; + const char *path; + void *v; + + HASHMAP_FOREACH_KEY(v, path, ruleset->start_times) + if (!hashmap_contains(m->monitored_rules_cgroup_contexts, path)) { + r = strv_extend(&to_remove, path); + if (r < 0) + return log_oom(); + } + + STRV_FOREACH(p, to_remove) { + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(ruleset->start_times, *p, (void**) &key)); + } + } + + return 0; +} + +static int process_rules_cgroup_context(Manager *m, OomdCGroupContext *ctx, usec_t usec_now) { + int r; + + assert(m); + assert(ctx); + + if (strv_isempty(ctx->rules)) + return 0; + + STRV_FOREACH(rule_name, ctx->rules) { + OomdRuleset *ruleset = hashmap_get(m->rulesets, *rule_name); + if (!ruleset) + /* Silently skip: already warned once when the subscription was attached or when + * rulesets were loaded. Repeating here would fire every interval. */ + continue; + + r = ruleset_check_conditions(m, ctx, ruleset, *rule_name, usec_now); + if (r < 0) + continue; + if (r == 0) + continue; + + r = ruleset_execute_action(m, ctx, ruleset, *rule_name, usec_now); + if (r < 0) + return r; + + /* Only remove start time if the action actually killed something, so that + * LastingSec must be satisfied again before re-triggering. If the action + * failed to kill, keep the timer running to retry on the next interval. */ + if (r > 0) { + _cleanup_free_ char *action_key = NULL; + free(hashmap_remove2(ruleset->start_times, ctx->path, (void **) &action_key)); + + /* Global (not per-cgroup/per-ruleset) post-action delay: after any + * successful ruleset kill we suppress *all* subsequent rule evaluations + * until POST_ACTION_DELAY_USEC elapses. This is intentional — pressure + * and swap metrics need time to reflect the effect of a kill before we + * act again, otherwise a single overload could cascade into multiple + * unrelated kills across sibling cgroups within the same interval. */ + m->rules_post_action_delay_start = usec_now; + return 0; + } + } + + return 0; +} + +static int monitor_rules_contexts_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + bool in_post_action_delay = false; + usec_t usec_now; + int r; + + assert(s); + + /* Reset timer */ + r = sd_event_now(sd_event_source_get_event(s), CLOCK_MONOTONIC, &usec_now); + if (r < 0) + return log_error_errno(r, "Failed to reset event timer: %m"); + + r = sd_event_source_set_time_relative(s, RULESETS_INTERVAL_USEC); + if (r < 0) + return log_error_errno(r, "Failed to set relative time for timer: %m"); + + /* Reconnect if our connection dropped */ + if (!m->varlink_client) { + r = acquire_managed_oom_connect(m); + if (r < 0) + return log_error_errno(r, "Failed to acquire varlink connection: %m"); + } + + /* Return early if no rules are set */ + if (hashmap_isempty(m->monitored_rules_cgroup_contexts)) + return 0; + + /* Determine whether we're still inside the post-action delay window before doing any + * heavy lifting, so we can short-circuit the expensive descendant walk below. */ + if (m->rules_post_action_delay_start > 0) { + if (usec_add(m->rules_post_action_delay_start, POST_ACTION_DELAY_USEC) > usec_now) + in_post_action_delay = true; + else + m->rules_post_action_delay_start = 0; + } + + /* Always keep the subscribed (parent) cgroup contexts fresh so pgscan rate differentials + * stay accurate across intervals, even during the post-action delay. Only suppress the + * kill action itself. + * + * Note: update_monitored_cgroup_contexts() rebuilds the hashmap by calling + * oomd_insert_cgroup_context(), which also carries over the per-cgroup 'rules' strv + * from the old context. We rely on that implicit rule propagation here — the + * rules attached to each cgroup context persist across refreshes. */ + r = update_monitored_cgroup_contexts(&m->monitored_rules_cgroup_contexts); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_debug_errno(r, "Failed to update monitored rules cgroup contexts, ignoring: %m"); + + /* The candidate refresh is the expensive part — it recursively walks descendants of every + * monitored cgroup. Since candidates are only consumed by kill-by-pgscan / kill-by-swap + * (both suppressed during the delay), skip the walk while we're not going to act. */ + if (!in_post_action_delay) { + r = update_monitored_cgroup_contexts_candidates( + m->monitored_rules_cgroup_contexts, &m->monitored_rules_cgroup_contexts_candidates); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_debug_errno(r, "Failed to update monitored rules cgroup candidates, ignoring: %m"); + } + + r = prune_stale_ruleset_start_times(m); + if (r < 0) + return r; + + if (in_post_action_delay) + return 0; + + /* Only read /proc/meminfo if at least one ruleset actually needs swap info — either as + * a SwapUsageMax= condition or as a kill-by-swap action (which uses swap_total to + * compute the per-cgroup selection threshold). */ + OomdRuleset *ruleset; + HASHMAP_FOREACH(ruleset, m->rulesets) + if (ruleset->swap_above >= 0 || ruleset->action == OOMD_ACTION_KILL_BY_SWAP) { + r = oomd_system_context_acquire("/proc/meminfo", &m->system_context); + if (r < 0) + return log_error_errno(r, "Failed to acquire system context: %m"); + break; + } + + OomdCGroupContext *ctx; + HASHMAP_FOREACH(ctx, m->monitored_rules_cgroup_contexts) { + r = process_rules_cgroup_context(m, ctx, usec_now); + if (r < 0) + return r; + + /* process_rules_cgroup_context() sets rules_post_action_delay_start when it queues + * a kill. Honor the delay *within the same tick* too: otherwise a single overload + * could cascade into kills across unrelated sibling cgroups before pressure metrics + * have a chance to reflect the first kill. */ + if (m->rules_post_action_delay_start > 0) + break; + } + + return 0; +} + static int monitor_swap_contexts(Manager *m) { _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; int r; @@ -634,6 +1107,31 @@ static int monitor_memory_pressure_contexts(Manager *m) { return 0; } +static int monitor_rules_contexts(Manager *m) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(m); + assert(m->event); + + r = sd_event_add_time(m->event, &s, CLOCK_MONOTONIC, 0, 0, monitor_rules_contexts_handler, m); + if (r < 0) + return r; + + r = sd_event_source_set_exit_on_failure(s, true); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s, "oomd-rules-timer"); + + m->rules_context_event_source = TAKE_PTR(s); + return 0; +} + Manager* manager_free(Manager *m) { assert(m); @@ -641,6 +1139,7 @@ Manager* manager_free(Manager *m) { sd_varlink_close_unref(m->varlink_client); sd_event_source_unref(m->swap_context_event_source); sd_event_source_unref(m->mem_pressure_context_event_source); + sd_event_source_unref(m->rules_context_event_source); sd_event_unref(m->event); hashmap_free(m->polkit_registry); @@ -649,9 +1148,13 @@ Manager* manager_free(Manager *m) { hashmap_free(m->monitored_swap_cgroup_contexts); hashmap_free(m->monitored_mem_pressure_cgroup_contexts); hashmap_free(m->monitored_mem_pressure_cgroup_contexts_candidates); + hashmap_free(m->monitored_rules_cgroup_contexts); + hashmap_free(m->monitored_rules_cgroup_contexts_candidates); set_free(m->kill_states); + hashmap_free(m->rulesets); + return mfree(m); } @@ -662,6 +1165,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa manager_set_defaults(m); manager_parse_config_file(m); + warn_missing_rulesets(m); (void) sd_notify(/* unset_environment= */ false, NOTIFY_READY_MESSAGE); return 0; @@ -706,6 +1210,14 @@ int manager_new(Manager **ret) { if (!m->monitored_mem_pressure_cgroup_contexts_candidates) return -ENOMEM; + m->monitored_rules_cgroup_contexts = hashmap_new(&oomd_cgroup_ctx_hash_ops); + if (!m->monitored_rules_cgroup_contexts) + return -ENOMEM; + + m->monitored_rules_cgroup_contexts_candidates = hashmap_new(&oomd_cgroup_ctx_hash_ops); + if (!m->monitored_rules_cgroup_contexts_candidates) + return -ENOMEM; + *ret = TAKE_PTR(m); return 0; } @@ -815,6 +1327,10 @@ int manager_start( if (r < 0) return r; + r = monitor_rules_contexts(m); + if (r < 0) + return r; + return 0; } diff --git a/src/oom/oomd-manager.h b/src/oom/oomd-manager.h index 8b9476232fb59..cc588461b0860 100644 --- a/src/oom/oomd-manager.h +++ b/src/oom/oomd-manager.h @@ -2,15 +2,22 @@ #pragma once #include "conf-parser-forward.h" +#include "constants.h" #include "shared-forward.h" #include "oomd-conf.h" #include "oomd-util.h" +#define RULESET_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/oomd/rules.d")) + /* Polling interval for monitoring stats */ #define SWAP_INTERVAL_USEC 150000 /* 0.15 seconds */ /* Pressure counters are lagging (~2 seconds) compared to swap so polling too frequently just wastes CPU */ #define MEM_PRESSURE_INTERVAL_USEC (1 * USEC_PER_SEC) +/* Rules evaluate both pressure and swap metrics; align on the slower-moving metric + * (pressure counters lag ~2s), so polling faster than 1s just wastes CPU. */ +#define RULESETS_INTERVAL_USEC MEM_PRESSURE_INTERVAL_USEC + /* Take action if 10s of memory pressure > 60 for more than 30s. We use the "full" value from PSI so this is the * percentage of time all tasks were delayed (i.e. unproductive). * Generally 60 or higher might be acceptable for something like system.slice with no memory.high set; processes in @@ -25,6 +32,25 @@ #define RECLAIM_DURATION_USEC (30 * USEC_PER_SEC) #define POST_ACTION_DELAY_USEC (15 * USEC_PER_SEC) +typedef enum OomdAction { + OOMD_ACTION_NONE, + OOMD_ACTION_KILL_ALL, + OOMD_ACTION_KILL_BY_PGSCAN, + OOMD_ACTION_KILL_BY_SWAP, + _OOMD_ACTION_MAX, + _OOMD_ACTION_INVALID = -EINVAL, +} OomdAction; + +typedef struct OomdRuleset { + char *name; + int memory_pressure_above; /* permyriad (0-10000), or -1 for unset */ + int swap_above; /* permyriad (0-10000), or -1 for unset */ + OomdAction action; + usec_t lasting_usec; + Hashmap *start_times; /* key: cgroup path (char*) -> value: heap-allocated timestamp (usec_t*) */ + bool warned_no_swap; /* latched once we've warned that kill-by-swap is misconfigured */ +} OomdRuleset; + typedef struct Manager { sd_bus *bus; sd_event *event; @@ -41,13 +67,17 @@ typedef struct Manager { Hashmap *monitored_swap_cgroup_contexts; Hashmap *monitored_mem_pressure_cgroup_contexts; Hashmap *monitored_mem_pressure_cgroup_contexts_candidates; + Hashmap *monitored_rules_cgroup_contexts; + Hashmap *monitored_rules_cgroup_contexts_candidates; OomdSystemContext system_context; usec_t mem_pressure_post_action_delay_start; + usec_t rules_post_action_delay_start; sd_event_source *swap_context_event_source; sd_event_source *mem_pressure_context_event_source; + sd_event_source *rules_context_event_source; /* This varlink object is used to manage the subscription from systemd-oomd to PID1 which it uses to * listen for changes in ManagedOOM settings (oomd client - systemd server). */ @@ -58,6 +88,7 @@ typedef struct Manager { usec_t prekill_timeout; Set *kill_states; /* currently ongoing OomdKillState operations */ + Hashmap *rulesets; } Manager; Manager* manager_free(Manager *m); diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index c0e04041a7e6a..4128d315a635e 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -21,13 +21,14 @@ #include "sort-util.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "varlink-util.h" typedef struct OomdKillState { Manager *manager; OomdCGroupContext *ctx; - const char *reason; + char *reason; /* This holds sd_varlink references */ Set *links; } OomdKillState; @@ -80,6 +81,7 @@ static OomdCGroupContext *oomd_cgroup_context_free(OomdCGroupContext *ctx) { return NULL; free(ctx->path); + strv_free(ctx->rules); return mfree(ctx); } @@ -305,6 +307,7 @@ static void oomd_kill_state_free(OomdKillState *ks) { set_remove(ks->manager->kill_states, ks); oomd_cgroup_context_unref(ks->ctx); + free(ks->reason); free(ks); } @@ -485,6 +488,10 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason return 0; } + _cleanup_free_ char *reason_copy = strdup(reason); + if (!reason_copy) + return log_oom_debug(); + _cleanup_(oomd_kill_state_removep) OomdKillState *ks = new(OomdKillState, 1); if (!ks) return log_oom_debug(); @@ -492,7 +499,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason *ks = (OomdKillState) { .manager = m, .ctx = oomd_cgroup_context_ref(ctx), - .reason = reason, + .reason = TAKE_PTR(reason_copy), }; r = set_ensure_put(&m->kill_states, &oomd_kill_state_hash_ops, ks); @@ -503,6 +510,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason * cleanup path would remove by cgroup path key and could interfere with the existing queued * kill state. */ oomd_cgroup_context_unref(ks->ctx); + free(ks->reason); ks = mfree(ks); return 0; } @@ -585,14 +593,14 @@ int oomd_select_by_pgscan_rate(Hashmap *h, const char *prefix, OomdCGroupContext return ret; } -int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupContext **ret_selected) { +int oomd_select_by_swap_usage(Hashmap *h, const char *prefix, uint64_t threshold_usage, OomdCGroupContext **ret_selected) { _cleanup_free_ OomdCGroupContext **sorted = NULL; int r, n, ret = 0; assert(h); assert(ret_selected); - n = oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted); + n = oomd_sort_cgroup_contexts(h, compare_swap_usage, prefix, &sorted); if (n < 0) return n; @@ -786,6 +794,9 @@ int oomd_insert_cgroup_context(Hashmap *old_h, Hashmap *new_h, const char *path) curr_ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start; curr_ctx->mem_pressure_duration_usec = old_ctx->mem_pressure_duration_usec; curr_ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim; + curr_ctx->rules = strv_copy(old_ctx->rules); + if (old_ctx->rules && !curr_ctx->rules) + return -ENOMEM; } if (oomd_pgscan_rate(curr_ctx) > 0) @@ -817,6 +828,9 @@ void oomd_update_cgroup_contexts_between_hashmaps(Hashmap *old_h, Hashmap *curr_ ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start; ctx->mem_pressure_duration_usec = old_ctx->mem_pressure_duration_usec; ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim; + /* Note: rules are intentionally not copied here. This function is only used on + * candidate hashmaps (populated by recursively_get_cgroup_context for descendant + * cgroups), which never carry rules. */ if (oomd_pgscan_rate(ctx) > 0) ctx->last_had_mem_reclaim = now(CLOCK_MONOTONIC); diff --git a/src/oom/oomd-util.h b/src/oom/oomd-util.h index d4e1a9207bd50..a76454f812393 100644 --- a/src/oom/oomd-util.h +++ b/src/oom/oomd-util.h @@ -40,6 +40,7 @@ struct OomdCGroupContext { usec_t mem_pressure_limit_hit_start; usec_t last_had_mem_reclaim; usec_t mem_pressure_duration_usec; + char **rules; }; struct OomdSystemContext { @@ -132,7 +133,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason * everything in `h` is a candidate. * Returns the killed cgroup in ret_selected. */ int oomd_select_by_pgscan_rate(Hashmap *h, const char *prefix, OomdCGroupContext **ret_selected); -int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupContext **ret_selected); +int oomd_select_by_swap_usage(Hashmap *h, const char *prefix, uint64_t threshold_usage, OomdCGroupContext **ret_selected); int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret); int oomd_system_context_acquire(const char *proc_meminfo_path, OomdSystemContext *ret); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 9b69ebd1a9361..e3a1fcd8934b2 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2384,6 +2384,7 @@ static const BusProperty cgroup_properties[] = { { "ManagedOOMSwap", bus_append_string }, { "ManagedOOMMemoryPressure", bus_append_string }, { "ManagedOOMPreference", bus_append_string }, + { "OOMRules", bus_append_strv }, { "MemoryPressureWatch", bus_append_string }, { "CPUPressureWatch", bus_append_string }, { "IOPressureWatch", bus_append_string }, diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index c9f2a59728c1f..2ed91121e4875 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -409,6 +409,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( 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_BY_TYPE(ManagedOOMPreference, ManagedOOMPreference, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#OOMRules="), + SD_VARLINK_DEFINE_FIELD(OOMRules, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureWatch="), 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="), diff --git a/src/shared/varlink-io.systemd.oom.c b/src/shared/varlink-io.systemd.oom.c index 80fa50a73a92c..15e28b3e1b0c7 100644 --- a/src/shared/varlink-io.systemd.oom.c +++ b/src/shared/varlink-io.systemd.oom.c @@ -14,7 +14,8 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( 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), - SD_VARLINK_DEFINE_FIELD(duration, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD(duration, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(rules, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( ReportManagedOOMCGroups, diff --git a/test/units/TEST-55-OOMD.sh b/test/units/TEST-55-OOMD.sh index 96a15989c745a..6689bbdd733c9 100755 --- a/test/units/TEST-55-OOMD.sh +++ b/test/units/TEST-55-OOMD.sh @@ -353,6 +353,127 @@ EOF systemctl reset-failed } +testcase_oom_rulesets() { + # Create a ruleset that triggers on any memory pressure with no delay + mkdir -p /run/systemd/oomd/rules.d/ + cat >/run/systemd/oomd/rules.d/testrule.oomrule <<'EOF' +[Rule] +MemoryPressureAbove=0% +Action=kill-all +LastingSec=0 +EOF + + systemctl reload systemd-oomd.service + + # Run a transient service with OOMRules=testrule that generates memory pressure + (! systemd-run --wait --unit=TEST-55-OOMD-testrules \ + -p MemoryHigh=3M \ + -p OOMRules=testrule \ + stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep) + + # Verify in the journal that the rule triggered + journalctl --sync + journalctl -u systemd-oomd.service --since "-2min" | grep "Rule 'testrule' conditions met" >/dev/null + + # clean up + rm -f /run/systemd/oomd/rules.d/testrule.oomrule + systemctl reload systemd-oomd.service +} + +testcase_oom_rulesets_invalid_name() { + # Invalid rule names must be rejected at property-set time (filename_is_valid check). + # "foo/bar" contains a slash and "." and ".." are disallowed by filename_is_valid. + set +e + err=$(systemd-run --wait --unit=TEST-55-OOMD-badname1 -p 'OOMRules=foo/bar' true 2>&1) + rc=$? + set -e + [[ $rc -ne 0 ]] + echo "$err" | grep "Invalid rule name" >/dev/null + + set +e + err=$(systemd-run --wait --unit=TEST-55-OOMD-badname2 -p 'OOMRules=.' true 2>&1) + rc=$? + set -e + [[ $rc -ne 0 ]] + echo "$err" | grep "Invalid rule name" >/dev/null +} + +testcase_oom_rulesets_missing_warning() { + # A unit that references a ruleset which does not exist must produce a + # warn_missing_rulesets warning in oomd's journal (once, at subscription time). + mkdir -p /run/systemd/oomd/rules.d/ + rm -f /run/systemd/oomd/rules.d/absentrule.oomrule + systemctl reload systemd-oomd.service + + # Start a long-lived transient unit that references a ruleset that doesn't exist. + systemd-run --unit=TEST-55-OOMD-missing --remain-after-exit \ + -p OOMRules=absentrule \ + sleep infinity + + # Give oomd a moment to receive the subscription, then verify the warning fires once. + timeout 30 bash -c ' + until journalctl --sync && journalctl -u systemd-oomd.service --since "-1min" 2>/dev/null | grep "references undefined ruleset .absentrule." >/dev/null; do + sleep 1 + done + ' + + # And when we now add the ruleset and reload, oomd must pick it up without + # the unit needing to restart. Verify by checking for the debug-log line that + # reports the ruleset was registered. + cat >/run/systemd/oomd/rules.d/absentrule.oomrule <<'EOF' +[Rule] +SwapUsageMax=99% +Action=kill-all +LastingSec=0 +EOF + systemctl reload systemd-oomd.service + + journalctl --sync + journalctl -u systemd-oomd.service --since "-1min" | grep "Registered ruleset: absentrule" >/dev/null + + # cleanup + systemctl stop TEST-55-OOMD-missing.service + rm -f /run/systemd/oomd/rules.d/absentrule.oomrule + systemctl reload systemd-oomd.service +} + +testcase_oom_rulesets_lasting_sec() { + # A rule with LastingSec > 0 must NOT trigger during the waiting period. + # Baseline proof: with the same workload but LastingSec=0 (testcase_oom_rulesets + # above) oomd kills the unit within a couple of seconds, so an active unit after + # ~6 s demonstrates LastingSec is being respected. + mkdir -p /run/systemd/oomd/rules.d/ + cat >/run/systemd/oomd/rules.d/slowrule.oomrule <<'EOF' +[Rule] +MemoryPressureAbove=0% +Action=kill-all +LastingSec=1h +EOF + + systemctl reload systemd-oomd.service + + # Start the unit without --wait so we can check mid-run state. The + # stress-ng timeout bounds the test if anything goes wrong. + systemd-run --unit=TEST-55-OOMD-slowrule \ + -p MemoryHigh=3M \ + -p OOMRules=slowrule \ + stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep + + # Wait long enough for oomd's 1s rule-check loop to evaluate the condition + # many times. With LastingSec=1h the kill must not fire. + sleep 6 + + # Unit must still be active — if it were killed, Result= would be oom-kill. + assert_eq "$(systemctl show TEST-55-OOMD-slowrule.service -P ActiveState)" "active" + assert_eq "$(systemctl show TEST-55-OOMD-slowrule.service -P Result)" "success" + + systemctl stop TEST-55-OOMD-slowrule.service 2>/dev/null || true + + # cleanup + rm -f /run/systemd/oomd/rules.d/slowrule.oomrule + systemctl reload systemd-oomd.service +} + testcase_prekill_hook() { cat >/run/systemd/oomd.conf.d/99-oomd-prekill-test.conf <<'EOF' [OOM] From 7d822ca8afc52f9dd9e32b83b275bf9f1abaa838 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 17:13:06 +0000 Subject: [PATCH 1943/2155] bpf-util: rename from bpf-dlopen, unify version-specific symbol handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames src/shared/bpf-dlopen.{c,h} to src/shared/bpf-util.{c,h} and folds the former src/shared/bpf-compat.h (struct forward decl and compat_bpf_map_create() helper) into the new header. Aligns dlopen_bpf() with the standard wrapper pattern: drops the manual dlopen_safe()/dlsym_many_or_warn()/TAKE_PTR(dl) plumbing and the bespoke 'cached' int in favor of dlopen_many_sym_or_warn() inside a FOREACH_STRING() soname-fallback loop. Unifies declaration of the version-specific symbols (bpf_create_map, bpf_map_create, bpf_object__next_map, bpf_token_create) into a single DISABLE_WARNING_REDUNDANT_DECLS block in the header, and alphabetically merges the DLSYM_PROTOTYPE list. DLSYM_OPTIONAL is used to load each one — call sites already handle NULL (compat_bpf_map_create() and the sym_bpf_object__next_map guard in userns-restrict.c). bpf_token_create additionally defaults to a missing_bpf_token_create() stub returning -ENOSYS, so callers can branch on the errno instead of NULL-checking the pointer. Updates test-bpf-token to match: drops the compile-time LIBBPF_MAJOR_VERSION ≥ 1.5 gate and the direct include in favor of dlopen_bpf() + sym_bpf_token_create(), and treats -ENOSYS as the test-skip path (covering both 'libbpf too old' and 'kernel lacks BPF_TOKEN_CREATE support'). --- src/bpf/bpf-skel-wrapper.h.in | 2 +- src/bpf/meson.build | 2 +- src/core/bpf-bind-iface.c | 2 +- src/core/bpf-restrict-fs.c | 2 +- src/core/bpf-restrict-fsaccess.c | 2 +- src/core/bpf-restrict-ifaces.c | 2 +- src/core/bpf-socket-bind.c | 2 +- src/core/exec-invoke.c | 2 +- src/network/networkd-sysctl.c | 2 +- src/nsresourced/nsresourced-manager.c | 2 +- src/nsresourced/userns-restrict.c | 2 +- src/shared/bpf-compat.h | 49 ------ src/shared/bpf-dlopen.c | 221 ------------------------ src/shared/bpf-link.c | 2 +- src/shared/bpf-util.c | 178 +++++++++++++++++++ src/shared/{bpf-dlopen.h => bpf-util.h} | 39 ++++- src/shared/meson.build | 2 +- src/test/meson.build | 2 +- src/test/test-bpf-restrict-fsaccess.c | 2 +- src/test/test-bpf-token.c | 22 ++- src/test/test-dlopen-so.c | 2 +- 21 files changed, 247 insertions(+), 294 deletions(-) delete mode 100644 src/shared/bpf-compat.h delete mode 100644 src/shared/bpf-dlopen.c create mode 100644 src/shared/bpf-util.c rename src/shared/{bpf-dlopen.h => bpf-util.h} (52%) diff --git a/src/bpf/bpf-skel-wrapper.h.in b/src/bpf/bpf-skel-wrapper.h.in index 3dc3cede3e2e1..d9450a12b0ada 100644 --- a/src/bpf/bpf-skel-wrapper.h.in +++ b/src/bpf/bpf-skel-wrapper.h.in @@ -7,7 +7,7 @@ * fine given that LGPL-2.1-or-later downgrades to GPL if needed. */ -#include "bpf-dlopen.h" /* IWYU pragma: keep */ +#include "bpf-util.h" /* IWYU pragma: keep */ /* libbpf is used via dlopen(), so rename symbols */ #define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton diff --git a/src/bpf/meson.build b/src/bpf/meson.build index 13af4c968e825..ac05eb4b1840f 100644 --- a/src/bpf/meson.build +++ b/src/bpf/meson.build @@ -394,7 +394,7 @@ foreach program : bpf_programs # 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. + # to resolve bpf-util.h. configure_file( input : 'bpf-skel-wrapper.h.in', output : name + '-skel.h', diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index 74cb2b3d52374..1ba643a1166b3 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -12,7 +12,7 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "bind-iface-skel.h" diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index d3c6adc145251..d28abfe4dd163 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -15,7 +15,7 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-fs-skel.h" diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index be66f7da01c92..e043bf9fd66ef 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -37,7 +37,7 @@ const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_ }; #if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-fsaccess-skel.h" diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index 3c73a682a3d38..e30d15cd256ef 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -14,7 +14,7 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-ifaces-skel.h" diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index 87e7d60c055a9..b4bf37b41e0f4 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -9,7 +9,7 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "socket-bind-api.bpf.h" #include "socket-bind-skel.h" diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 260e7a36afb08..b82f7b995b638 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -21,7 +21,7 @@ #include "ask-password-api.h" #include "barrier.h" #include "bitfield.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-restrict-fs.h" #include "btrfs-util.h" #include "capability-util.h" diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 0801ba977a643..ba90d342c5a8b 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -5,7 +5,7 @@ #include "sd-messages.h" #include "af-list.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "conf-parser.h" #include "alloc-util.h" #include "cgroup-util.h" diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 406db72e7d72a..c2a6cd6eab035 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -5,7 +5,7 @@ #include "sd-daemon.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #if HAVE_VMLINUX_H #include "bpf-link.h" #include "userns-restrict-skel.h" diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index 11bb2e7d8fd36..1acc42cd02d72 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -6,7 +6,7 @@ #include "userns-restrict-skel.h" #endif -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "fd-util.h" #include "log.h" diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h deleted file mode 100644 index 7f76ee87a0781..0000000000000 --- a/src/shared/bpf-compat.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -/* libbpf has been moving quickly. - * They added new symbols in the 0.x versions and shortly after removed - * deprecated symbols in 1.0. - * We only need bpf_map_create and libbpf_probe_bpf_prog_type so we work - * around the incompatibility here by: - * - declaring both symbols, and looking for either depending on the libbpf - * so version we found - * - having helpers that automatically use the appropriate version behind the - * new API for easy cleanup later - * - * The advantage of doing this instead of only looking for the symbols declared at - * compile time is that we can then load either the old or the new symbols at runtime - * regardless of the version we were compiled with */ - -/* declare the struct for libbpf <= 0.6.0 -- it causes no harm on newer versions */ -struct bpf_map_create_opts; - -/* new symbols available from 0.7.0. - * We need the symbols here: - * - after bpf_map_create_opts struct has been defined for older libbpf - * - before the compat static inline helpers that use them. - * When removing this file move these back to bpf-dlopen.h */ -extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); - -/* compat symbols removed in libbpf 1.0 */ -extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -/* helpers to use the available variant behind new API */ -static inline int compat_bpf_map_create( - enum bpf_map_type map_type, - const char *map_name, - __u32 key_size, - __u32 value_size, - __u32 max_entries, - const struct bpf_map_create_opts *opts) { - - if (sym_bpf_map_create) - return sym_bpf_map_create(map_type, map_name, key_size, - value_size, max_entries, opts); - - return sym_bpf_create_map(map_type, key_size, value_size, max_entries, - 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); -} diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c deleted file mode 100644 index ca746828233c7..0000000000000 --- a/src/shared/bpf-dlopen.c +++ /dev/null @@ -1,221 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-dlopen.h" - -#include "bpf-dlopen.h" -#include "dlfcn-util.h" -#include "errno-util.h" -#include "initrd-util.h" -#include "log.h" - -#if HAVE_LIBBPF - -/* libbpf changed types of function prototypes around, so we need to disable some type checking for older - * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ -#if defined(__LIBBPF_CURRENT_VERSION_GEQ) -#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) -#define MODERN_LIBBPF 1 -#endif -#endif -#if !defined(MODERN_LIBBPF) -#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; -DLSYM_PROTOTYPE(bpf_link__pin) = NULL; -DLSYM_PROTOTYPE(bpf_map__fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__name) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; -DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; -DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; -DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; -DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__name) = NULL; -DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; -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(bpf_program__set_autoload) = 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; - -/* 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); - -/* compat symbols removed in libbpf 1.0 */ -int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -_printf_(2,0) -static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { -#if !LOG_TRACE - /* libbpf logs a lot of details at its debug level, which we don't need to see. */ - if (level == LIBBPF_DEBUG) - return 0; -#endif - /* All other levels are downgraded to LOG_DEBUG */ - - /* errno is used here, on the assumption that if the log message uses %m, errno will be set to - * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ - return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); -} - -int dlopen_bpf(int log_level) { - static int cached = 0; - int r; - - if (cached != 0) - return cached; - - SD_ELF_NOTE_DLOPEN( - "bpf", - "Support firewalling and sandboxing with BPF", - SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libbpf.so.1", "libbpf.so.0"); - - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - - _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_safe("libbpf.so.1", &dl, /* reterr_dlerror= */ NULL); - if (r < 0) { - /* libbpf < 1.0.0 (we rely on 0.1.0+) provide most symbols we care about, but - * unfortunately not all until 0.7.0. See bpf-compat.h for more details. - * Once we consider we can assume 0.7+ is present we can just use the same symbol - * list for both files, and when we assume 1.0+ is present we can remove this dlopen */ - const char *dle = NULL; - r = dlopen_safe("libbpf.so.0", &dl, &dle); - if (r < 0) { - log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r, - "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ - } - - log_debug("Loaded 'libbpf.so.0' via dlopen()"); - - /* symbols deprecated in 1.0 we use as compat */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - /* Don't exist anymore in new libbpf, hence cannot type check them */ - DLSYM_ARG_FORCE(bpf_create_map) -#else - DLSYM_ARG(bpf_create_map) -#endif - ); - - /* NB: we don't try to load bpf_object__next_map() on old versions */ - } else { - log_debug("Loaded 'libbpf.so.1' via dlopen()"); - - /* symbols available from 0.7.0 */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - DLSYM_ARG(bpf_map_create), - DLSYM_ARG(bpf_object__next_map) -#else - /* These symbols did not exist in old libbpf, hence we cannot type check them */ - DLSYM_ARG_FORCE(bpf_map_create), - DLSYM_ARG_FORCE(bpf_object__next_map) -#endif - ); - } - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - r = dlsym_many_or_warn( - dl, LOG_DEBUG, - DLSYM_ARG(bpf_link__destroy), - DLSYM_ARG(bpf_link__fd), - DLSYM_ARG(bpf_link__open), - DLSYM_ARG(bpf_link__pin), - DLSYM_ARG(bpf_map__fd), - DLSYM_ARG(bpf_map__name), - DLSYM_ARG(bpf_map__set_inner_map_fd), - DLSYM_ARG(bpf_map__set_max_entries), - DLSYM_ARG(bpf_map__set_pin_path), - DLSYM_ARG(bpf_map_delete_elem), - DLSYM_ARG(bpf_map_get_fd_by_id), - DLSYM_ARG(bpf_map_lookup_elem), - DLSYM_ARG(bpf_map_update_elem), - DLSYM_ARG(bpf_obj_get_info_by_fd), - DLSYM_ARG(bpf_object__attach_skeleton), - DLSYM_ARG(bpf_object__destroy_skeleton), - DLSYM_ARG(bpf_object__detach_skeleton), - DLSYM_ARG(bpf_object__load_skeleton), - DLSYM_ARG(bpf_object__name), - DLSYM_ARG(bpf_object__open_skeleton), - DLSYM_ARG(bpf_object__pin_maps), -#if MODERN_LIBBPF - DLSYM_ARG(bpf_program__attach), - DLSYM_ARG(bpf_program__attach_cgroup), - DLSYM_ARG(bpf_program__attach_lsm), -#else - /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ - DLSYM_ARG_FORCE(bpf_program__attach), - DLSYM_ARG_FORCE(bpf_program__attach_cgroup), - DLSYM_ARG_FORCE(bpf_program__attach_lsm), -#endif - DLSYM_ARG(bpf_program__name), - DLSYM_ARG(bpf_program__set_autoload), - DLSYM_ARG(libbpf_get_error), - DLSYM_ARG(libbpf_set_print), - DLSYM_ARG(ring_buffer__epoll_fd), - DLSYM_ARG(ring_buffer__free), - DLSYM_ARG(ring_buffer__new), - DLSYM_ARG(ring_buffer__poll)); - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ - (void) sym_libbpf_set_print(bpf_print_func); - - REENABLE_WARNING; - - bpf_dl = TAKE_PTR(dl); - - return cached = true; -} - -int bpf_get_error_translated(const void *ptr) { - int r; - - r = sym_libbpf_get_error(ptr); - - switch (r) { - case -524: - /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until - * it is fixed: - * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 - */ - return -EOPNOTSUPP; - default: - return r; - } -} - -#else - -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."); -} -#endif diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 80a6db47ded23..4ae402625b8e8 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "serialize.h" diff --git a/src/shared/bpf-util.c b/src/shared/bpf-util.c new file mode 100644 index 0000000000000..03f3281b07548 --- /dev/null +++ b/src/shared/bpf-util.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "bpf-util.h" +#include "dlfcn-util.h" +#include "initrd-util.h" +#include "log.h" +#include "strv.h" + +#if HAVE_LIBBPF + +/* libbpf changed types of function prototypes around, so we need to disable some type checking for older + * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ +#if defined(__LIBBPF_CURRENT_VERSION_GEQ) +#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) +#define MODERN_LIBBPF 1 +#endif +#endif +#if !defined(MODERN_LIBBPF) +#define MODERN_LIBBPF 0 +#endif + +static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; + +DLSYM_PROTOTYPE(bpf_create_map) = NULL; +DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; +DLSYM_PROTOTYPE(bpf_link__fd) = NULL; +DLSYM_PROTOTYPE(bpf_link__open) = NULL; +DLSYM_PROTOTYPE(bpf_link__pin) = NULL; +DLSYM_PROTOTYPE(bpf_map__fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__name) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; +DLSYM_PROTOTYPE(bpf_map_create) = NULL; +DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; +DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; +DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; +DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__name) = NULL; +DLSYM_PROTOTYPE(bpf_object__next_map) = NULL; +DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; +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(bpf_program__set_autoload) = NULL; +static int missing_bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(bpf_token_create) = missing_bpf_token_create; +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; + +_printf_(2,0) +static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { +#if !LOG_TRACE + /* libbpf logs a lot of details at its debug level, which we don't need to see. */ + if (level == LIBBPF_DEBUG) + return 0; +#endif + /* All other levels are downgraded to LOG_DEBUG */ + + /* errno is used here, on the assumption that if the log message uses %m, errno will be set to + * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ + return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); +} + +int dlopen_bpf(int log_level) { + static void *bpf_dl = NULL; + int r; + + SD_ELF_NOTE_DLOPEN( + "bpf", + "Support firewalling and sandboxing with BPF", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbpf.so.1", "libbpf.so.0"); + + DISABLE_WARNING_DEPRECATED_DECLARATIONS; + + FOREACH_STRING(soname, "libbpf.so.1", "libbpf.so.0") { + r = dlopen_many_sym_or_warn( + &bpf_dl, soname, LOG_DEBUG, + DLSYM_ARG(bpf_link__destroy), + DLSYM_ARG(bpf_link__fd), + DLSYM_ARG(bpf_link__open), + DLSYM_ARG(bpf_link__pin), + DLSYM_ARG(bpf_map__fd), + DLSYM_ARG(bpf_map__name), + DLSYM_ARG(bpf_map__set_inner_map_fd), + DLSYM_ARG(bpf_map__set_max_entries), + DLSYM_ARG(bpf_map__set_pin_path), + DLSYM_ARG(bpf_map_delete_elem), + DLSYM_ARG(bpf_map_get_fd_by_id), + DLSYM_ARG(bpf_map_lookup_elem), + DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_obj_get_info_by_fd), + DLSYM_ARG(bpf_object__attach_skeleton), + DLSYM_ARG(bpf_object__destroy_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_object__name), + DLSYM_ARG(bpf_object__open_skeleton), + DLSYM_ARG(bpf_object__pin_maps), +#if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach), + DLSYM_ARG(bpf_program__attach_cgroup), + DLSYM_ARG(bpf_program__attach_lsm), +#else + /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ + DLSYM_ARG_FORCE(bpf_program__attach), + DLSYM_ARG_FORCE(bpf_program__attach_cgroup), + DLSYM_ARG_FORCE(bpf_program__attach_lsm), +#endif + DLSYM_ARG(bpf_program__name), + DLSYM_ARG(bpf_program__set_autoload), + DLSYM_ARG(libbpf_get_error), + DLSYM_ARG(libbpf_set_print), + DLSYM_ARG(ring_buffer__epoll_fd), + DLSYM_ARG(ring_buffer__free), + DLSYM_ARG(ring_buffer__new), + DLSYM_ARG(ring_buffer__poll)); + if (r >= 0) + break; + } + REENABLE_WARNING; + if (r < 0) + return log_full_errno(in_initrd() ? LOG_DEBUG : log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled."); + + /* Version-specific symbols: bpf_create_map exists only in libbpf < 1.0; bpf_map_create and + * bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. Unresolved prototypes keep + * their initializers (NULL, or a fallback returning -ENOSYS for bpf_token_create). */ + DLSYM_OPTIONAL(bpf_dl, bpf_create_map); + DLSYM_OPTIONAL(bpf_dl, bpf_map_create); + DLSYM_OPTIONAL(bpf_dl, bpf_object__next_map); + DLSYM_OPTIONAL(bpf_dl, bpf_token_create); + + /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ + (void) sym_libbpf_set_print(bpf_print_func); + + return 1; +} + +int bpf_get_error_translated(const void *ptr) { + int r; + + r = sym_libbpf_get_error(ptr); + + switch (r) { + case -524: + /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until + * it is fixed: + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 + */ + return -EOPNOTSUPP; + default: + return r; + } +} + +#else + +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."); +} +#endif diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-util.h similarity index 52% rename from src/shared/bpf-dlopen.h rename to src/shared/bpf-util.h index 62ea9cf707b84..622e4c2e2831f 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-util.h @@ -8,10 +8,27 @@ #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ -#include "bpf-compat.h" /* IWYU pragma: export */ #include "dlfcn-util.h" #include "shared-forward.h" +/* Always redeclare these so DLSYM_PROTOTYPE's typeof() resolves regardless of libbpf version; + * suppress the warning when the libbpf headers already declare them. + * - bpf_map_create, bpf_object__next_map: available since libbpf 0.7 + * - bpf_token_create: available since libbpf 1.5 + * - bpf_create_map: removed in libbpf 1.0 + */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +struct bpf_map_create_opts; +struct bpf_token_create_opts; +extern int bpf_create_map(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); +extern int bpf_map_create(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); +extern struct bpf_map* bpf_object__next_map(const struct bpf_object *obj, const struct bpf_map *map); +extern int bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + +extern DLSYM_PROTOTYPE(bpf_create_map); extern DLSYM_PROTOTYPE(bpf_link__destroy); extern DLSYM_PROTOTYPE(bpf_link__fd); extern DLSYM_PROTOTYPE(bpf_link__open); @@ -21,6 +38,7 @@ extern DLSYM_PROTOTYPE(bpf_map__name); extern DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); extern DLSYM_PROTOTYPE(bpf_map__set_max_entries); extern DLSYM_PROTOTYPE(bpf_map__set_pin_path); +extern DLSYM_PROTOTYPE(bpf_map_create); extern DLSYM_PROTOTYPE(bpf_map_delete_elem); extern DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); extern DLSYM_PROTOTYPE(bpf_map_lookup_elem); @@ -33,6 +51,7 @@ extern DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); extern DLSYM_PROTOTYPE(bpf_object__detach_skeleton); extern DLSYM_PROTOTYPE(bpf_object__load_skeleton); extern DLSYM_PROTOTYPE(bpf_object__name); +extern DLSYM_PROTOTYPE(bpf_object__next_map); extern DLSYM_PROTOTYPE(bpf_object__open_skeleton); extern DLSYM_PROTOTYPE(bpf_object__pin_maps); extern DLSYM_PROTOTYPE(bpf_program__attach); @@ -40,12 +59,30 @@ extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup); extern DLSYM_PROTOTYPE(bpf_program__attach_lsm); extern DLSYM_PROTOTYPE(bpf_program__name); extern DLSYM_PROTOTYPE(bpf_program__set_autoload); +extern DLSYM_PROTOTYPE(bpf_token_create); extern DLSYM_PROTOTYPE(libbpf_set_print); extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd); extern DLSYM_PROTOTYPE(ring_buffer__free); extern DLSYM_PROTOTYPE(ring_buffer__new); extern DLSYM_PROTOTYPE(ring_buffer__poll); +/* Helper that uses the available variant behind the new API. */ +static inline int compat_bpf_map_create( + enum bpf_map_type map_type, + const char *map_name, + __u32 key_size, + __u32 value_size, + __u32 max_entries, + const struct bpf_map_create_opts *opts) { + + if (sym_bpf_map_create) + return sym_bpf_map_create(map_type, map_name, key_size, + value_size, max_entries, opts); + + return sym_bpf_create_map(map_type, key_size, value_size, max_entries, + 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); +} + /* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos * we understand. */ diff --git a/src/shared/meson.build b/src/shared/meson.build index 9b539f79839d9..ce96ce3025cb0 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -19,8 +19,8 @@ shared_sources = files( 'boot-entry.c', 'boot-timestamps.c', 'bootspec.c', - 'bpf-dlopen.c', 'bpf-program.c', + 'bpf-util.c', 'bridge-util.c', 'btrfs-util.c', 'bus-get-properties.c', diff --git a/src/test/meson.build b/src/test/meson.build index 4871c5517f376..c8279bc7514f0 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -554,7 +554,7 @@ executables += [ }, core_test_template + { 'sources' : files('test-bpf-token.c'), - 'dependencies' : common_test_dependencies + libbpf, + 'dependencies' : common_test_dependencies + libbpf_cflags, 'conditions' : ['BPF_FRAMEWORK'], 'type' : 'manual', }, diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c index e95e706c276cc..0d4e18671aa3e 100644 --- a/src/test/test-bpf-restrict-fsaccess.c +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -92,7 +92,7 @@ static int do_mprotect_exec(const char *path) { } #if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "restrict-fsaccess-skel.h" static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { diff --git a/src/test/test-bpf-token.c b/src/test/test-bpf-token.c index 7e83a048e52f9..f991f92601271 100644 --- a/src/test/test-bpf-token.c +++ b/src/test/test-bpf-token.c @@ -1,26 +1,34 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "bpf-util.h" #include "fd-util.h" #include "main-func.h" -#include "tests.h" /* NOLINT(misc-include-cleaner): this is needed conditionally */ +#include "tests.h" 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 HAVE_LIBBPF + int r; + + r = dlopen_bpf(LOG_ERR); + if (r < 0) + return r; + + _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_CLOEXEC|O_RDONLY); if (bpffs_fd < 0) return log_error_errno(errno, "Failed to open '/sys/fs/bpf': %m"); - _cleanup_close_ int token_fd = bpf_token_create(bpffs_fd, /* opts= */ NULL); + _cleanup_close_ int token_fd = sym_bpf_token_create(bpffs_fd, /* opts= */ NULL); + if (token_fd == -ENOSYS) + return log_tests_skipped("bpf_token_create() unavailable (libbpf too old or kernel lacks BPF_TOKEN_CREATE support)"); if (token_fd < 0) - return log_error_errno(errno, "Failed to create bpf token: %m"); + return log_error_errno(token_fd, "Failed to create bpf token: %m"); log_info("Successfully created token fd."); return 0; #else - return log_tests_skipped("libbpf is older than v1.5"); + return log_tests_skipped("BPF framework support is disabled"); #endif } diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 7421a77024f1b..0eca1356c0313 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -3,7 +3,7 @@ #include "acl-util.h" #include "apparmor-util.h" #include "blkid-util.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "compress.h" #include "crypto-util.h" #include "cryptsetup-util.h" From 94d745c42c389ac2de89ca5b5e300678b9c23f84 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 12:16:01 +0000 Subject: [PATCH 1944/2155] cryptsetup: dlopen libcryptsetup in tokens This avoids having to subpackage the tokens separately. If they link directly against libcryptsetup, package manager will automatically add a dependency on libcryptsetup to the package containing the tokens. With this change, the tokens can ship in the main systemd package without necessarily pulling in libcryptsetup. It also makes things more consistent. Once we also do the same for pam, any direct linking will be limited to just libc, which for example simplifies writing tests for ensuring we don't link unnecessarily as we don't have to add exceptions for the cryptsetup tokens. This actually drops the dependency on cryptsetup-libs for the fedora/centos/opensuse systemd-udev package so install it explicitly in the initrd now to keep the tests working. --- .../mkosi.conf.d/centos-fedora.conf | 1 + mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf | 1 + .../cryptsetup-token-systemd-fido2.c | 14 +++++++++++++- .../cryptsetup-token-systemd-pkcs11.c | 14 +++++++++++++- .../cryptsetup-token-systemd-tpm2.c | 14 +++++++++++++- .../cryptsetup-tokens/cryptsetup-token-util.h | 13 ++++++------- src/cryptsetup/cryptsetup-tokens/meson.build | 8 ++++---- src/shared/cryptsetup-util.c | 2 ++ src/shared/cryptsetup-util.h | 1 + 9 files changed, 54 insertions(+), 14 deletions(-) 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 e753749dc443f..ab648130601ea 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 + cryptsetup-libs 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 c30d970c85a2b..b863f7b2d14d9 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 + libcryptsetup12 policycoreutils tpm2.0-tools diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index a2804e033a1de..0265b2d5a0393 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -2,6 +2,7 @@ #include #include +#include #include "sd-json.h" @@ -37,8 +38,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -98,6 +103,9 @@ _public_ void cryptsetup_token_dump( assert(json); + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -162,6 +170,10 @@ _public_ int cryptsetup_token_validate( assert(json); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + 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 16cf910fe6d89..e99fcfea85088 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-json.h" @@ -36,8 +37,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -89,6 +94,9 @@ _public_ void cryptsetup_token_dump( _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL; _cleanup_free_ void *pkcs11_key = NULL; + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -115,6 +123,10 @@ _public_ int cryptsetup_token_validate( sd_json_variant *w; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + 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 58dc37c5bfb74..ad14942622fef 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "alloc-util.h" #include "cryptsetup-token.h" @@ -60,8 +61,12 @@ _public_ int cryptsetup_token_open_pin( assert(ret_password); assert(ret_password_len); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); assert(token == r); assert(json); @@ -186,6 +191,9 @@ _public_ void cryptsetup_token_dump( assert(json); + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + 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,6 +283,10 @@ _public_ int cryptsetup_token_validate( assert(json); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + 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-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index 68b09fc4586db..c6a7d25f398d0 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -2,22 +2,21 @@ #pragma once -#include - +#include "cryptsetup-util.h" #include "shared-forward.h" /* crypt_dump() internal indentation magic */ #define CRYPT_DUMP_LINE_SEP "\n\t " -#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) -#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) -#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) -#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) +#define crypt_log_debug(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) +#define crypt_log_error(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) +#define crypt_log_verbose(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) +#define crypt_log(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) #define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = ABS(e), _s = errno; \ errno = _e; \ - crypt_logf(cd, lvl, __VA_ARGS__); \ + sym_crypt_logf(cd, lvl, __VA_ARGS__); \ errno = _s; \ -_e; \ }) diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 0fd6309201bb7..772c29f50f526 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -45,8 +45,8 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_tpm2_sources, 'dependencies' : [ - libcryptsetup, - tpm2, + libcryptsetup_cflags, + tpm2_cflags, ], }, template + { @@ -57,7 +57,7 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_fido2_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libfido2_cflags, ], }, @@ -69,7 +69,7 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_pkcs11_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libp11kit_cflags, ], }, diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 7aa3d5d9c5231..a51783cb296ee 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -45,6 +45,7 @@ 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_logf) = NULL; DLSYM_PROTOTYPE(crypt_metadata_locking) = NULL; DLSYM_PROTOTYPE(crypt_persistent_flags_get) = NULL; DLSYM_PROTOTYPE(crypt_persistent_flags_set) = NULL; @@ -315,6 +316,7 @@ int dlopen_cryptsetup(int log_level) { DLSYM_ARG(crypt_keyslot_max), DLSYM_ARG(crypt_keyslot_status), DLSYM_ARG(crypt_load), + DLSYM_ARG(crypt_logf), DLSYM_ARG(crypt_metadata_locking), DLSYM_ARG(crypt_persistent_flags_get), DLSYM_ARG(crypt_persistent_flags_set), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 5c83aad8ee211..23d52d111ae0b 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -46,6 +46,7 @@ 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_logf); extern DLSYM_PROTOTYPE(crypt_metadata_locking); extern DLSYM_PROTOTYPE(crypt_persistent_flags_get); extern DLSYM_PROTOTYPE(crypt_persistent_flags_set); From 62053ee28955ed339d44e7725bb1b555c51e7143 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 14:54:04 +0000 Subject: [PATCH 1945/2155] tree-wide: dlopen libpam in pam plugins Same reasoning as for cryptsetup tokens. It means we can include the pam plugins in the main systemd package without the package manager introducing a dependency on libpam. It also makes things more consistent and makes writing the upcoming linking test script a lot simpler. At the same time, we get rid of the libpam_misc dependency as the one symbol we were using from it is trivial to reimplement ourselves. --- meson.build | 23 +------- src/home/meson.build | 3 +- src/home/pam_systemd_home.c | 73 ++++++++++++----------- src/login/pam_systemd.c | 100 ++++++++++++++++---------------- src/login/pam_systemd_loadkey.c | 4 +- src/shared/pam-util.c | 23 ++++++++ src/shared/pam-util.h | 16 ++++- 7 files changed, 132 insertions(+), 110 deletions(-) diff --git a/meson.build b/meson.build index c69b96e1c6d50..92129bee7d77b 100644 --- a/meson.build +++ b/meson.build @@ -1133,12 +1133,7 @@ if not libpam.found() # Debian older than bookworm and Ubuntu older than 22.10 do not provide the .pc file. libpam = cc.find_library('pam', required : feature) endif -libpam_misc = dependency('pam_misc', - required : feature.disabled() ? feature : false) -if not libpam_misc.found() - libpam_misc = cc.find_library('pam_misc', required : feature) -endif -conf.set10('HAVE_PAM', libpam.found() and libpam_misc.found()) +conf.set10('HAVE_PAM', libpam.found()) libpam_cflags = libpam.partial_dependency(includes: true, compile_args: true) libmicrohttpd = dependency('libmicrohttpd', @@ -1949,21 +1944,7 @@ pam_template = { libshared_static, ], 'dependencies' : [ - # Note: our PAM modules also call dlopen_libpam() and use - # symbols acquired through that, hence the explicit dep here is - # strictly speaking unnecessary. We put it in place anyway, - # since for the PAM modules we cannot avoid libpam anyway, - # after all they are loaded *by* libpam, and hence there's no - # loss in having explicit deps here, but there's a win: it - # makes the deps more visible. - # - # (In case you wonder why we do dlopen_libpam() from the PAM - # modules in the first place: that's mostly so that all our PAM - # code (regardless if our PAM modules or our PAM consuming - # programs) can use the same helpers, which hence go via - # dlopen_libpam(). - libpam_misc, - libpam, + libpam_cflags, threads, ], 'install' : true, diff --git a/src/home/meson.build b/src/home/meson.build index 600f00b4ac997..631eeb13aa879 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -116,8 +116,7 @@ modules += [ 'sources' : pam_systemd_home_sources, 'dependencies' : [ libintl, - libpam_misc, - libpam, + libpam_cflags, threads, ], 'version-script' : meson.current_source_dir() / 'pam_systemd_home.sym', diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 1de66b4c0325c..5fe6dcbec20fc 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include "sd-bus.h" @@ -45,7 +44,7 @@ static int parse_argv( k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k); @@ -57,12 +56,12 @@ static int parse_argv( int k; k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); else if (debug) *debug = k; } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -76,7 +75,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { * easy to declare the features of a display manager in code rather than configuration, and this is * really a feature of code */ - v = pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); + v = sym_pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); if (!v) { /* Also check the process env block, so that people can control this via an env var from the * outside of our process. */ @@ -87,7 +86,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { r = parse_boolean(v); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r); @@ -106,7 +105,7 @@ static int acquire_user_record( assert(pamh); if (!username) { - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -149,7 +148,7 @@ static int acquire_user_record( return pam_log_oom(pamh); /* Let's use the cache, so that we can share it between the session and the authentication hooks */ - r = pam_get_data(pamh, homed_field, (const void**) &json); + r = sym_pam_get_data(pamh, homed_field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { @@ -211,13 +210,13 @@ static int acquire_user_record( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SERVICE_ERR, "Acquired user record does not match user name."); - /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is + /* Update the 'username' pointer to point to our own record now. The sym_pam_set_item() call below is * going to invalidate the old version after all */ username = ur->user_name; /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary, * normalized name of the user record (i.e. not an alias or so). */ - r = pam_set_item(pamh, PAM_USER, ur->user_name); + r = sym_pam_set_item(pamh, PAM_USER, ur->user_name); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name); @@ -230,7 +229,7 @@ static int acquire_user_record( if (!json_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", homed_field); @@ -246,7 +245,7 @@ static int acquire_user_record( if (!generic_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", generic_field); @@ -256,7 +255,7 @@ static int acquire_user_record( /* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */ if (area) { - r = pam_misc_setenv(pamh, "XDG_AREA", area, /* readonly= */ 0); + r = pam_putenv_assign(pamh, "XDG_AREA", area); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area); @@ -269,7 +268,7 @@ static int acquire_user_record( user_unknown: /* Cache this, so that we don't check again */ - r = pam_set_data(pamh, homed_field, POINTER_MAX, NULL); + r = sym_pam_set_data(pamh, homed_field, POINTER_MAX, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s' to invalid, ignoring: @PAMERR@", @@ -289,7 +288,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!homed_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, NULL, NULL); + r = sym_pam_set_data(pamh, homed_field, NULL, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM user record data '%s': @PAMERR@", homed_field); @@ -298,7 +297,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!generic_field) return pam_log_oom(pamh); - k = pam_set_data(pamh, generic_field, NULL, NULL); + k = sym_pam_set_data(pamh, generic_field, NULL, NULL); if (k != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, k, "Failed to release PAM user record data '%s': @PAMERR@", generic_field); @@ -569,7 +568,7 @@ static int acquire_home( * prompt the user for the missing unlock credentials, and then chainload the real shell. */ - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -580,7 +579,7 @@ static int acquire_home( if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); @@ -618,7 +617,7 @@ static int acquire_home( /* If there's already a cached password, use it. But if not let's authenticate * without anything, maybe some other authentication mechanism systemd-homed * implements (such as PKCS#11) allows us to authenticate without anything else. */ - r = pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); + r = sym_pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get cached password: @PAMERR@"); @@ -681,7 +680,7 @@ static int acquire_home( (void) pam_prompt_graceful(pamh, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE)) - pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); + sym_pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); else pam_debug_syslog(pamh, debug, "Failed to prompt for password/prompt."); @@ -720,12 +719,12 @@ static int acquire_home( /* Later PAM modules may need the auth token, but only during pam_authenticate. */ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) { - r = pam_set_item(pamh, PAM_AUTHTOK, *secret->password); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, *secret->password); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); } - r = pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); + r = sym_pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@"); TAKE_FD(acquired_fd); @@ -744,13 +743,13 @@ static int acquire_home( * manager for us (since it would see an inaccessible home directory). Hence set an environment * variable that pam_systemd looks for). */ if (unrestricted) { - r = pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); + r = sym_pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@"); - pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); } else - pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); return PAM_SUCCESS; } @@ -767,13 +766,13 @@ static int release_home_fd(pam_handle_t *pamh, const char *username) { if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (r == PAM_NO_MODULE_DATA || (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) < 0)) return PAM_NO_MODULE_DATA; if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); - r = pam_set_data(pamh, fd_field, NULL, NULL); + r = sym_pam_set_data(pamh, fd_field, NULL, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM home reference fd: @PAMERR@"); @@ -887,12 +886,12 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return r; - r = pam_putenv(pamh, "SYSTEMD_HOME=1"); + r = sym_pam_putenv(pamh, "SYSTEMD_HOME=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@"); - r = pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); + r = sym_pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@"); @@ -911,6 +910,10 @@ _public_ PAM_EXTERN int pam_sm_close_session( bool debug = false; int r; + r = dlopen_libpam(LOG_DEBUG); + if (r < 0) + return PAM_SERVICE_ERR; + pam_log_setup(); if (parse_argv(pamh, @@ -921,7 +924,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( pam_debug_syslog(pamh, debug, "pam-systemd-homed: closing session..."); - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -954,7 +957,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SESSION_ERR, "Failed to release user home: %s", bus_error_message(&error, r)); - pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); + sym_pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); } return PAM_SUCCESS; @@ -1005,7 +1008,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -ESTALE: - pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); + sym_pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); break; case -ENOLCK: @@ -1062,7 +1065,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( case -ESTALE: /* If the system clock is wrong, let's log but continue */ - pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); + sym_pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); break; case -EROFS: @@ -1121,7 +1124,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( /* No, it's not cached, then let's ask for the password and its verification, and cache * it. */ - r = pam_get_authtok_noverify(pamh, &new_password, "New password: "); + r = sym_pam_get_authtok_noverify(pamh, &new_password, "New password: "); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get new password: @PAMERR@"); @@ -1130,7 +1133,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( return PAM_AUTHTOK_ERR; } - r = pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ + r = sym_pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get password again: @PAMERR@"); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 4e571e705655d..1b0adfae9ea82 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -88,7 +87,7 @@ static int parse_caps( c = capability_from_name(s); if (c < 0) { - pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); continue; } @@ -146,7 +145,7 @@ static int parse_argv( } else if ((p = startswith(argv[i], "area="))) { if (!isempty(p) && !filename_is_valid(p)) - pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); else if (area) *area = p; @@ -157,22 +156,22 @@ static int parse_argv( } else if ((p = startswith(argv[i], "debug="))) { r = parse_boolean(p); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); else if (debug) *debug = r; } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) { r = parse_caps(pamh, p, default_capability_bounding_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) { r = parse_caps(pamh, p, default_capability_ambient_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -184,7 +183,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { assert(pamh); const char *username = NULL; - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -198,7 +197,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; const char *json = NULL; - r = pam_get_data(pamh, field, (const void**) &json); + r = sym_pam_get_data(pamh, field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { @@ -240,7 +239,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to format user JSON: %m"); /* And cache it for everyone else */ - r = pam_set_data(pamh, field, formatted, pam_cleanup_free); + r = sym_pam_set_data(pamh, field, formatted, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", field); @@ -382,7 +381,7 @@ static int append_session_memory_max(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = parse_size(limit, 1024, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); return 0; } @@ -402,7 +401,7 @@ static int append_session_runtime_max_sec(pam_handle_t *pamh, sd_bus_message *m, usec_t val; r = parse_sec(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); return 0; } @@ -422,7 +421,7 @@ static int append_session_tasks_max(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = safe_atou64(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); return 0; } @@ -441,7 +440,7 @@ static int append_session_cpu_weight(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = cg_cpu_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); return 0; } @@ -460,7 +459,7 @@ static int append_session_io_weight(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = cg_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); return 0; } @@ -479,7 +478,7 @@ static const char* getenv_harder(pam_handle_t *pamh, const char *key, const char * PAM services don't have to be reworked to set systemd-specific properties, but these properties * can still be set from the unit file Environment= block. */ - v = pam_getenv(pamh, key); + v = sym_pam_getenv(pamh, key); if (!isempty(v)) return v; @@ -507,9 +506,9 @@ static bool getenv_harder_bool(pam_handle_t *pamh, const char *key, bool fallbac r = parse_boolean(v); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", - v, key, true_false(fallback)); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", + v, key, true_false(fallback)); return fallback; } @@ -529,9 +528,9 @@ static uint32_t getenv_harder_uint32(pam_handle_t *pamh, const char *key, uint32 uint32_t u; r = safe_atou32(v, &u); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", - v, key, fallback); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", + v, key, fallback); return fallback; } @@ -548,14 +547,14 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v * about errors. */ if (isempty(value)) { - /* Unset the variable if set. Note that pam_putenv() would log nastily behind our back if we + /* Unset the variable if set. Note that sym_pam_putenv() would log nastily behind our back if we * call it without the variable actually being set. Hence we check explicitly if it's set * before. */ - if (!pam_getenv(pamh, key)) + if (!sym_pam_getenv(pamh, key)) return PAM_SUCCESS; - r = pam_putenv(pamh, key); + r = sym_pam_putenv(pamh, key); if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to unset %s environment variable: @PAMERR@", key); @@ -563,7 +562,7 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, key, value, /* readonly= */ false); + r = pam_putenv_assign(pamh, key, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", key); @@ -588,7 +587,7 @@ static int propagate_credential_to_environment(pam_handle_t *pamh, bool debug, c return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, varname, value, 0); + r = pam_putenv_assign(pamh, varname, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", varname); @@ -610,7 +609,7 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ if (!path_is_absolute(path)) { - pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); + sym_pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); goto fail; } @@ -620,19 +619,19 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid } if (!S_ISDIR(st.st_mode)) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); goto fail; } if (st.st_uid != uid) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); goto fail; } return true; fail: - pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); + sym_pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); return false; } @@ -642,7 +641,7 @@ static int pam_putenv_and_log(pam_handle_t *pamh, const char *e, bool debug) { assert(pamh); assert(e); - r = pam_putenv(pamh, e); + r = sym_pam_putenv(pamh, e); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable %s: @PAMERR@", e); @@ -992,7 +991,7 @@ static void session_context_mangle( c->area = ur->default_area; if (!isempty(c->area) && !filename_is_valid(c->area)) { - pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); + sym_pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); c->area = NULL; } @@ -1048,7 +1047,7 @@ static void session_context_mangle( if (streq(c->class, "user")) c->class = "user-incomplete"; else - pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); + sym_pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); } c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host); @@ -1253,8 +1252,7 @@ static int register_session( return PAM_SUCCESS; } - pam_syslog(pamh, LOG_ERR, - "Failed to create session: %s", bus_error_message(&error, r)); + sym_pam_syslog(pamh, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r)); return PAM_SESSION_ERR; } @@ -1282,7 +1280,7 @@ static int register_session( if (fd < 0) return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup session fd: %m"); - r = pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); + r = sym_pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); TAKE_FD(fd); @@ -1580,7 +1578,7 @@ static int setup_environment( pam_syslog_errno(pamh, LOG_WARNING, r, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory: %m", j, area); /* Also tell the user directly at login, but a bit more vague */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); area = NULL; } else { /* Validate that the target is definitely owned by user */ @@ -1589,10 +1587,10 @@ static int setup_environment( return pam_syslog_errno(pamh, LOG_ERR, errno, "Unable to fstat() target area directory '%s': %m", ha); if (st.st_uid != ur->uid) { - pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); /* Also tell the user directly at login. */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); area = NULL; } else { /* All good, now make a copy of the area string, since we quite likely are @@ -1631,13 +1629,13 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!streq_ptr(session_type, "tty")) return PAM_SUCCESS; - const char *e = pam_getenv(pamh, "TERM"); + const char *e = sym_pam_getenv(pamh, "TERM"); if (!e) e = getenv("TERM"); if (streq_ptr(e, "dumb")) return PAM_SUCCESS; - /* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too + /* NB: we output directly to stdout, instead of going via sym_pam_info() or so, because that's too * high-level for us, as it suffixes the output with a newline, expecting a full blown text message * as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout * anyway, hence let's do so here too, but only after careful validation. */ @@ -1657,7 +1655,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe sd_id128_t osc_id; r = osc_context_open_session( ur->user_name, - pam_getenv(pamh, "XDG_SESSION_ID"), + sym_pam_getenv(pamh, "XDG_SESSION_ID"), &osc, &osc_id); if (r < 0) @@ -1672,7 +1670,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!osc_id_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence ID data: @PAMERR@"); @@ -1680,7 +1678,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe TAKE_PTR(osc_id_copy); if (tty_opath_fd >= 0) { - r = pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); + r = sym_pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence fd data: @PAMERR@"); @@ -1698,7 +1696,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { const void *p; int tty_opath_fd = -EBADF; - r = pam_get_data(pamh, "systemd.osc-context-fd", &p); + r = sym_pam_get_data(pamh, "systemd.osc-context-fd", &p); if (r == PAM_SUCCESS) tty_opath_fd = PTR_TO_FD(p); else if (r != PAM_NO_MODULE_DATA) @@ -1707,7 +1705,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { return PAM_SUCCESS; const sd_id128_t *osc_id = NULL; - r = pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); + r = sym_pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@"); if (!osc_id) @@ -1721,7 +1719,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { } /* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that - * range, just in case pam_syslog() or so logs to stderr */ + * range, just in case sym_pam_syslog() or so logs to stderr */ fd = fd_move_above_stdio(fd); /* Safety check, let's verify this is a valid TTY we just opened */ @@ -1849,6 +1847,10 @@ _public_ PAM_EXTERN int pam_sm_close_session( assert(pamh); + r = dlopen_libpam(LOG_DEBUG); + if (r < 0) + return PAM_SERVICE_ERR; + pam_log_setup(); if (parse_argv(pamh, @@ -1866,7 +1868,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( (void) close_osc_context(pamh, debug); - id = pam_getenv(pamh, "XDG_SESSION_ID"); + id = sym_pam_getenv(pamh, "XDG_SESSION_ID"); if (id) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; bool done = false; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 93ccddc25b6a7..9d44863b02bd1 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -41,7 +41,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( else if (streq(argv[i], "debug")) debug = true; else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } pam_debug_syslog(pamh, debug, "pam-systemd-loadkey: initializing..."); @@ -81,7 +81,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( } else if (passwords_len > 1) pam_debug_syslog(pamh, debug, "Multiple passwords found in the key. Using the last one."); - r = pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index 9728c3e00da07..37f2caf31e6f3 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -25,10 +25,15 @@ static void *libpam_dl = NULL; DLSYM_PROTOTYPE(pam_acct_mgmt) = NULL; DLSYM_PROTOTYPE(pam_close_session) = NULL; DLSYM_PROTOTYPE(pam_end) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_noverify) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_verify) = NULL; DLSYM_PROTOTYPE(pam_get_data) = NULL; DLSYM_PROTOTYPE(pam_get_item) = NULL; +DLSYM_PROTOTYPE(pam_get_user) = NULL; +DLSYM_PROTOTYPE(pam_getenv) = NULL; DLSYM_PROTOTYPE(pam_getenvlist) = NULL; DLSYM_PROTOTYPE(pam_open_session) = NULL; +DLSYM_PROTOTYPE(pam_prompt) = NULL; DLSYM_PROTOTYPE(pam_putenv) = NULL; DLSYM_PROTOTYPE(pam_set_data) = NULL; DLSYM_PROTOTYPE(pam_set_item) = NULL; @@ -361,6 +366,19 @@ int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, cons return PAM_SUCCESS; } +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value) { + _cleanup_(erase_and_freep) char *s = NULL; + + assert(pamh); + assert(name); + assert(value); + + if (asprintf(&s, "%s=%s", name, value) < 0) + return PAM_BUF_ERR; + + return sym_pam_putenv(pamh, s); +} + #endif int dlopen_libpam(int log_level) { @@ -378,10 +396,15 @@ int dlopen_libpam(int log_level) { DLSYM_ARG(pam_acct_mgmt), DLSYM_ARG(pam_close_session), DLSYM_ARG(pam_end), + DLSYM_ARG(pam_get_authtok_noverify), + DLSYM_ARG(pam_get_authtok_verify), DLSYM_ARG(pam_get_data), DLSYM_ARG(pam_get_item), + DLSYM_ARG(pam_get_user), + DLSYM_ARG(pam_getenv), DLSYM_ARG(pam_getenvlist), DLSYM_ARG(pam_open_session), + DLSYM_ARG(pam_prompt), DLSYM_ARG(pam_putenv), DLSYM_ARG(pam_set_data), DLSYM_ARG(pam_set_item), diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 96d522d8f9bfb..ca3ed0c7c88a3 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -6,7 +6,7 @@ #if HAVE_PAM #include #include -#include /* IWYU pragma: export */ +#include /* IWYU pragma: export */ #include #include "dlfcn-util.h" @@ -14,10 +14,15 @@ extern DLSYM_PROTOTYPE(pam_acct_mgmt); extern DLSYM_PROTOTYPE(pam_close_session); extern DLSYM_PROTOTYPE(pam_end); +extern DLSYM_PROTOTYPE(pam_get_authtok_noverify); +extern DLSYM_PROTOTYPE(pam_get_authtok_verify); extern DLSYM_PROTOTYPE(pam_get_data); extern DLSYM_PROTOTYPE(pam_get_item); +extern DLSYM_PROTOTYPE(pam_get_user); +extern DLSYM_PROTOTYPE(pam_getenv); extern DLSYM_PROTOTYPE(pam_getenvlist); extern DLSYM_PROTOTYPE(pam_open_session); +extern DLSYM_PROTOTYPE(pam_prompt); extern DLSYM_PROTOTYPE(pam_putenv); extern DLSYM_PROTOTYPE(pam_set_data); extern DLSYM_PROTOTYPE(pam_set_item); @@ -27,6 +32,11 @@ extern DLSYM_PROTOTYPE(pam_strerror); extern DLSYM_PROTOTYPE(pam_syslog); extern DLSYM_PROTOTYPE(pam_vsyslog); +/* sym_pam_prompt() replacement for the pam_info() macro from , which expands to a direct + * pam_prompt() call. */ +#define sym_pam_info(pamh, fmt, ...) \ + sym_pam_prompt((pamh), PAM_TEXT_INFO, NULL, (fmt), ## __VA_ARGS__) + void pam_log_setup(void); int errno_to_pam_error(int error) _const_; @@ -90,6 +100,10 @@ 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); +/* Equivalent of pam_misc_setenv(pamh, name, value, 0), without the libpam_misc dep — builds "name=value" + * and hands it to sym_pam_putenv(), then erases the buffer before freeing in case it carried a secret. */ +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value); + #endif int dlopen_libpam(int log_level); From 0af0161f554b822d2f72eb6cf343b520924bd72b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 18:24:11 +0000 Subject: [PATCH 1946/2155] test-bus-marshal: dlopen() glib and libdbus instead of linking directly The test only uses 9 symbols (5 from glib, 4 from libdbus) for its interop checks; dlopen them at runtime so the binary no longer carries a hard link-time dependency on either library. Headers are still pulled in through the *_cflags partial dependencies for the type declarations. While we're at it, drop the compat glue for glib 2.36 which is long obsolete at this point. --- meson.build | 4 ++ src/libsystemd/meson.build | 8 +-- src/libsystemd/sd-bus/test-bus-marshal.c | 62 +++++++++++++++++------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/meson.build b/meson.build index 92129bee7d77b..05739e517b274 100644 --- a/meson.build +++ b/meson.build @@ -1319,11 +1319,15 @@ libgobject = dependency('gobject-2.0', libgio = dependency('gio-2.0', required : get_option('glib')) conf.set10('HAVE_GLIB', libglib.found() and libgobject.found() and libgio.found()) +libglib_cflags = libglib.partial_dependency(includes: true, compile_args: true) +libgobject_cflags = libgobject.partial_dependency(includes: true, compile_args: true) +libgio_cflags = libgio.partial_dependency(includes: true, compile_args: true) libdbus = dependency('dbus-1', version : '>= 1.3.2', required : get_option('dbus')) conf.set10('HAVE_DBUS', libdbus.found()) +libdbus_cflags = libdbus.partial_dependency(includes: true, compile_args: true) dbusdatadir = libdbus.get_variable(pkgconfig: 'datadir', default_value: datadir) / 'dbus-1' diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 2fab54719474c..2c86a231064a8 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -221,10 +221,10 @@ libsystemd_tests += [ { 'sources' : files('sd-bus/test-bus-marshal.c'), 'dependencies' : [ - libdbus, - libgio, - libglib, - libgobject, + libdbus_cflags, + libgio_cflags, + libglib_cflags, + libgobject_cflags, libm, threads, ], diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index e26ca43344713..8229eef54ab9f 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -23,6 +23,7 @@ REENABLE_WARNING #include "bus-label.h" #include "bus-message.h" #include "bus-util.h" +#include "dlfcn-util.h" #include "escape.h" #include "fd-util.h" #include "log.h" @@ -31,6 +32,40 @@ REENABLE_WARNING #include "stat-util.h" #include "tests.h" +#if HAVE_GLIB +static void *glib_dl = NULL; +static DLSYM_PROTOTYPE(g_dbus_message_new_from_blob) = NULL; +static DLSYM_PROTOTYPE(g_dbus_message_print) = NULL; +static DLSYM_PROTOTYPE(g_free) = NULL; +static DLSYM_PROTOTYPE(g_object_unref) = NULL; + +static int dlopen_glib(void) { + return dlopen_many_sym_or_warn( + &glib_dl, "libgio-2.0.so.0", LOG_DEBUG, + DLSYM_ARG(g_dbus_message_new_from_blob), + DLSYM_ARG(g_dbus_message_print), + DLSYM_ARG(g_free), + DLSYM_ARG(g_object_unref)); +} +#endif + +#if HAVE_DBUS +static void *libdbus_dl = NULL; +static DLSYM_PROTOTYPE(dbus_error_init) = NULL; +static DLSYM_PROTOTYPE(dbus_error_free) = NULL; +static DLSYM_PROTOTYPE(dbus_message_demarshal) = NULL; +static DLSYM_PROTOTYPE(dbus_message_unref) = NULL; + +static int dlopen_libdbus(void) { + return dlopen_many_sym_or_warn( + &libdbus_dl, "libdbus-1.so.3", LOG_DEBUG, + DLSYM_ARG(dbus_error_init), + DLSYM_ARG(dbus_error_free), + DLSYM_ARG(dbus_message_demarshal), + DLSYM_ARG(dbus_message_unref)); +} +#endif + static void test_bus_path_encode_unique(void) { _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; @@ -322,39 +357,32 @@ int main(int argc, char *argv[]) { log_info("message size = %zu, contents =\n%s", sz, h); #if HAVE_GLIB - /* Work-around for asan bug. See c8d980a3e962aba2ea3a4cedf75fa94890a6d746. */ -#if !HAS_FEATURE_ADDRESS_SANITIZER - { + if (dlopen_glib() >= 0) { GDBusMessage *g; char *p; -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); - p = g_dbus_message_print(g, 0); + g = sym_g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = sym_g_dbus_message_print(g, 0); log_info("%s", p); - g_free(p); - g_object_unref(g); + sym_g_free(p); + sym_g_object_unref(g); } #endif -#endif #if HAVE_DBUS - { + if (dlopen_libdbus() >= 0) { DBusMessage *w; DBusError error; - dbus_error_init(&error); + sym_dbus_error_init(&error); - w = dbus_message_demarshal(buffer, sz, &error); + w = sym_dbus_message_demarshal(buffer, sz, &error); if (!w) log_error("%s", error.message); else - dbus_message_unref(w); + sym_dbus_message_unref(w); - dbus_error_free(&error); + sym_dbus_error_free(&error); } #endif From bcb7cf4a61f4ac5c731b32b30830bea55a4c456b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 08:57:24 +0000 Subject: [PATCH 1947/2155] lock-util: Simplify timeout for lock_generic_with_timeout() We can just put a timeout on the parent process completing rather than doing it inside the subprocess. --- src/basic/lock-util.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index bd267eabf99d4..24eee3b9e4c99 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -227,25 +226,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo if (r < 0) return log_error_errno(r, "Failed to flock block device in child process: %m"); if (r == 0) { - struct sigevent sev = { - .sigev_notify = SIGEV_SIGNAL, - .sigev_signo = SIGALRM, - }; - timer_t id; - - if (timer_create(CLOCK_MONOTONIC, &sev, &id) < 0) { - log_error_errno(errno, "Failed to allocate CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - - struct itimerspec its = {}; - timespec_store(&its.it_value, timeout); - - if (timer_settime(id, /* flags= */ 0, &its, NULL) < 0) { - log_error_errno(errno, "Failed to start CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - if (lock_generic(fd, type, operation) < 0) { log_error_errno(errno, "Unable to get an exclusive lock on the device: %m"); _exit(EXIT_FAILURE); @@ -255,7 +235,7 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo } siginfo_t status; - r = pidref_wait_for_terminate(&pidref, &status); + r = pidref_wait_for_terminate_full(&pidref, timeout, &status); if (r < 0) return r; @@ -270,11 +250,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo return 0; case CLD_KILLED: - if (status.si_status == SIGALRM) - return -ETIMEDOUT; - - _fallthrough_; - case CLD_DUMPED: return -EPROTO; From 7d2925fcde70ed221658e01ffb6f2797061d3520 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 17 May 2026 11:28:00 +0000 Subject: [PATCH 1948/2155] color-util: simplify hsv_to_rgb, fix rgb_to_hsv negative-hue wrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In hsv_to_rgb, restructure the conversion around the sector index k = (int)(h/60) and fractional offset f = h/60 - k. The auxiliary x value becomes c * (k & 1 ? 1.0 - f : f) and the six branches turn into a switch on k. This drops the two xfmod() calls that were doing the modulo work, in exchange for a single assert(h >= 0 && h < 360) — all in-tree callers satisfy this and never relied on the wrap. In rgb_to_hsv, the two xfmod() calls were no-ops (their arguments were always within the divisor's magnitude). The trailing xfmod(*ret_h, 360) appeared to be wrapping negative hues from the r-max branch back into [0, 360), but fmod is sign-preserving so it never did. Drop the no-ops and add an explicit +360 wrap so magenta (1, 0, 1) now yields h ≈ 300 instead of -60. Extend the tests to cover all six primary/secondary colors at sector boundaries, all six sector midpoints (to catch any future inversion of the ramp direction), the h-near-360 edge of the last sector, and the rgb_to_hsv negative-wrap path via magenta. Switch the new and existing integer-channel checks to ASSERT_EQ from tests.h; the double-typed h/s/v range checks stay on ASSERT_TRUE since the ASSERT_* comparison macros only support integer types. Co-developed-by: Claude Opus 4.7 --- src/shared/color-util.c | 40 ++++++----- src/test/test-color-util.c | 142 +++++++++++++++++++++++++++---------- 2 files changed, 128 insertions(+), 54 deletions(-) diff --git a/src/shared/color-util.c b/src/shared/color-util.c index 1912785954720..ed417a1a2010a 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -32,13 +32,15 @@ void rgb_to_hsv(double r, double g, double b, if (ret_h) { if (delta > 0) { if (r >= max_color) - *ret_h = 60 * fmod((g - b) / delta, 6); + *ret_h = 60 * (g - b) / delta; else if (g >= max_color) *ret_h = 60 * (((b - r) / delta) + 2); - else if (b >= max_color) + else /* b >= max_color */ *ret_h = 60 * (((r - g) / delta) + 4); - *ret_h = fmod(*ret_h, 360); + /* The r-max branch produces (-60, 60); fold the negative half up. */ + if (*ret_h < 0) + *ret_h += 360; } else *ret_h = NAN; } @@ -49,29 +51,33 @@ void hsv_to_rgb(double h, double s, double v, double c, x, m, r, g, b; + assert(h >= 0 && h <= 360); assert(s >= 0 && s <= 100); assert(v >= 0 && v <= 100); assert(ret_r); assert(ret_g); assert(ret_b); - h = fmod(h, 360); c = (s / 100.0) * (v / 100.0); - x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); m = (v / 100) - c; - if (h >= 0 && h < 60) - r = c, g = x, b = 0.0; - else if (h >= 60 && h < 120) - r = x, g = c, b = 0.0; - else if (h >= 120 && h < 180) - r = 0.0, g = c, b = x; - else if (h >= 180 && h < 240) - r = 0.0, g = x, b = c; - else if (h >= 240 && h < 300) - r = x, g = 0.0, b = c; - else - r = c, g = 0.0, b = x; + /* Split h into sector index k ∈ [0, 6] and fractional offset f ∈ [0, 1) within the sector. + * Within each sector exactly one of {r, g, b} equals c, one equals 0, and the third (x) + * ramps linearly between them — ascending in even sectors, descending in odd. h == 360 is + * the cyclic boundary equivalent to h == 0, and maps to sector 0. */ + double seg = h / 60.0; + int k = (int) seg % 6; + double f = seg - (int) seg; + x = c * (k & 1 ? 1.0 - f : f); + + switch (k) { + case 0: r = c; g = x; b = 0.0; break; + case 1: r = x; g = c; b = 0.0; break; + case 2: r = 0.0; g = c; b = x; break; + case 3: r = 0.0; g = x; b = c; break; + case 4: r = x; g = 0.0; b = c; break; + default: r = c; g = 0.0; b = x; break; + } *ret_r = (uint8_t) ((r + m) * 255); *ret_g = (uint8_t) ((g + m) * 255); diff --git a/src/test/test-color-util.c b/src/test/test-color-util.c index 3d00d8719d82a..a1369f4ef5231 100644 --- a/src/test/test-color-util.c +++ b/src/test/test-color-util.c @@ -6,58 +6,126 @@ TEST(hsv_to_rgb) { uint8_t r, g, b; + /* Black at any hue. */ hsv_to_rgb(0, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); hsv_to_rgb(60, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); + /* White: s=0 must ignore hue. */ hsv_to_rgb(0, 0, 100, &r, &g, &b); - assert(r == 255 && g == 255 && b == 255); - - hsv_to_rgb(0, 100, 100, &r, &g, &b); - assert(r == 255 && g == 0 && b == 0); - - hsv_to_rgb(120, 100, 100, &r, &g, &b); - assert(r == 0 && g == 255 && b == 0); - - hsv_to_rgb(240, 100, 100, &r, &g, &b); - assert(r == 0 && g == 0 && b == 255); - + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); + + hsv_to_rgb(180, 0, 100, &r, &g, &b); + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); + + /* Pure primary and secondary colors — one per sector boundary. */ + hsv_to_rgb(0, 100, 100, &r, &g, &b); /* red */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + hsv_to_rgb(60, 100, 100, &r, &g, &b); /* yellow */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(120, 100, 100, &r, &g, &b); /* green */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(180, 100, 100, &r, &g, &b); /* cyan */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 255); + hsv_to_rgb(240, 100, 100, &r, &g, &b); /* blue */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(300, 100, 100, &r, &g, &b); /* magenta */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + + /* Sector midpoints. Catches inverted ramp direction (k & 1 ? 1-f : f) in any single + * sector — a regression would shift exactly one channel by ±half-intensity. */ + hsv_to_rgb(30, 100, 100, &r, &g, &b); /* orange — sector 0 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 127); ASSERT_EQ(b, 0); + hsv_to_rgb(90, 100, 100, &r, &g, &b); /* chartreuse — sector 1 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(150, 100, 100, &r, &g, &b); /* spring green — sector 2 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 127); + hsv_to_rgb(210, 100, 100, &r, &g, &b); /* azure — sector 3 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 127); ASSERT_EQ(b, 255); + hsv_to_rgb(270, 100, 100, &r, &g, &b); /* violet — sector 4 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(330, 100, 100, &r, &g, &b); /* rose — sector 5 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 127); + + /* Just below 360 — ensures (int) seg stays in sector 5 and doesn't wrap to 6. */ + hsv_to_rgb(359.5, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 2); + + /* Exactly 360 — cyclic boundary, equivalent to 0 (red). Callers like color_for_pcr() in + * pcrlock.c can reach this via floating-point arithmetic (e.g. 360.0/23*23 == 360.0). */ + hsv_to_rgb(360, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + + /* Non-trivial s/v: regression check for the multiply-and-cast path. */ hsv_to_rgb(311, 52, 62, &r, &g, &b); - assert(r == 158 && g == 75 && b == 143); + ASSERT_EQ(r, 158); ASSERT_EQ(g, 75); ASSERT_EQ(b, 143); } TEST(rgb_to_hsv) { double h, s, v; + + /* Grayscale: delta == 0, h is NaN, s == 0. */ rgb_to_hsv(0, 0, 0, &h, &s, &v); - assert(s <= 0); - assert(v <= 0); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v <= 0); rgb_to_hsv(1, 1, 1, &h, &s, &v); - assert(s <= 0); - assert(v >= 100); - - rgb_to_hsv(1, 0, 0, &h, &s, &v); - assert(h >= 359 || h <= 1); - assert(s >= 100); - assert(v >= 100); - - rgb_to_hsv(0, 1, 0, &h, &s, &v); - assert(h >= 119 && h <= 121); - assert(s >= 100); - assert(v >= 100); - - rgb_to_hsv(0, 0, 1, &h, &s, &v); - assert(h >= 239 && h <= 241); - assert(s >= 100); - assert(v >= 100); - + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0.5, 0.5, 0.5, &h, &s, &v); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 49 && v <= 51); + + /* Pure primary colors. */ + rgb_to_hsv(1, 0, 0, &h, &s, &v); /* red */ + ASSERT_TRUE(h >= 0 && h <= 1); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 1, 0, &h, &s, &v); /* green */ + ASSERT_TRUE(h >= 119 && h <= 121); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 0, 1, &h, &s, &v); /* blue */ + ASSERT_TRUE(h >= 239 && h <= 241); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Pure secondary colors — each exercises a different "max" branch. Magenta exercises + * the negative-hue wrap from the r-max branch (raw value is -60). */ + rgb_to_hsv(1, 1, 0, &h, &s, &v); /* yellow — r-max branch, positive */ + ASSERT_TRUE(h >= 59 && h <= 61); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 1, 1, &h, &s, &v); /* cyan — g-max branch */ + ASSERT_TRUE(h >= 179 && h <= 181); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(1, 0, 1, &h, &s, &v); /* magenta — r-max branch, wrapped */ + ASSERT_TRUE(h >= 299 && h <= 301); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Mixed values. */ rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v); - assert(h >= 209 && h <= 211); - assert(s >= 28 && s <= 31); - assert(v >= 69 && v <= 71); + ASSERT_TRUE(h >= 209 && h <= 211); + ASSERT_TRUE(s >= 28 && s <= 31); + ASSERT_TRUE(v >= 69 && v <= 71); } DEFINE_TEST_MAIN(LOG_DEBUG); From b2bfe5b4748a804e7ea25a4925102412967a5e4e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 11:06:21 +0000 Subject: [PATCH 1949/2155] tree-wide: Use our own macros instead of fabs()/fmax()/fmin() To make this work, ABS() is made generic so it also works on floats and doubles. While at it, fold the __ABS_INTEGER indirection and the assert_cc(sizeof(long long) == sizeof(intmax_t)) away. The previous form switched between __builtin_llabs (clang) and __builtin_imaxabs (gcc), with the assert keeping the two paths behaviorally identical on every platform we build for. imaxabs was originally chosen because intmax_t is conceptually the widest signed integer type the platform exposes, but the _Generic ABS already casts to (long long) before the call, so the extra width imaxabs could in theory carry was being narrowed away immediately anyway. With both paths collapsed to __builtin_llabs((long long) (a)), the size relationship between long long and intmax_t is no longer relevant. Also add explicit unsigned long long / unsigned long / unsigned int cases that pass the argument through unchanged. The previous default branch cast unsigned values to (long long); for values above LLONG_MAX this reinterprets them as negative, and __builtin_llabs(LLONG_MIN) is UB. Unsigned values are already non-negative, so passing them through is both correct and avoids the narrowing. Smaller unsigned types (unsigned char, unsigned short) still go through the default branch but promote to int first and fit in long long losslessly. Co-developed-by: Claude Opus 4.7 --- src/fundamental/macro-fundamental.h | 14 ++++++++------ src/libsystemd/sd-bus/test-bus-marshal.c | 4 +--- src/libsystemd/sd-json/test-json.c | 20 ++++++++++---------- src/shared/color-util.c | 4 ++-- src/test/test-parse-util.c | 5 ++--- src/test/test-random-util.c | 2 +- src/timesync/timesyncd-manager.c | 14 +++++++------- 7 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 82e2cb4c3bca3..12f2aa81dcd83 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -180,12 +180,14 @@ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) -#ifdef __clang__ -# define ABS(a) __builtin_llabs(a) -#else -# define ABS(a) __builtin_imaxabs(a) -#endif -assert_cc(sizeof(long long) == sizeof(intmax_t)); +#define ABS(a) _Generic((a), \ + float: __builtin_fabsf((float) (a)), \ + double: __builtin_fabs((double) (a)), \ + long double: __builtin_fabsl((long double) (a)), \ + unsigned long long: (a), \ + unsigned long: (a), \ + unsigned int: (a), \ + default: __builtin_llabs((long long) (a))) #define IS_UNSIGNED_INTEGER_TYPE(type) \ (__builtin_types_compatible_p(typeof(type), unsigned char) || \ diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 8229eef54ab9f..5c2fca0f8357e 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - /* We make an exception here to our usual "include system headers first" rule because we need one of these * macros to disable a warning triggered by the glib headers. */ #include "macro-fundamental.h" @@ -479,7 +477,7 @@ int main(int argc, char *argv[]) { assert_se(r > 0); assert_se(streq(x, "foo")); assert_se(u64 == 815ULL); - assert_se(fabs(dbl - 47.0) < 0.1); + assert_se(ABS(dbl - 47.0) < 0.1); assert_se(streq(y, "/")); r = sd_bus_message_peek_type(m, NULL, NULL); diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index 3be4b09660b14..59724b00c39a5 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -60,8 +60,8 @@ static void test_tokenizer_one(const char *data, ...) { d = va_arg(ap, double); - assert_se(fabs(d - v.real) < 1e-10 || - fabs((d - v.real) / v.real) < 1e-10); + assert_se(ABS(d - v.real) < 1e-10 || + ABS((d - v.real) / v.real) < 1e-10); } else if (t == JSON_TOKEN_INTEGER) { int64_t i; @@ -242,7 +242,7 @@ static void test_2(sd_json_variant *v) { /* has thisisaverylongproperty */ p = sd_json_variant_by_key(v, "thisisaverylongproperty"); - assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && fabs(sd_json_variant_real(p) - 1.27) < 0.001); + assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && ABS(sd_json_variant_real(p) - 1.27) < 0.001); } static void test_zeroes(sd_json_variant *v) { @@ -690,14 +690,14 @@ 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(ABS(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(ABS(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 */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 4))); /* -inf is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 5)) || - fabs(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ + ABS(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 6)) && sd_json_variant_is_integer(sd_json_variant_by_index(v, 6)) && sd_json_variant_integer(sd_json_variant_by_index(v, 6)) == 0); @@ -710,11 +710,11 @@ static void test_float_match(sd_json_variant *v) { 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(ABS(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))); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 10)))); - assert_se(fabs(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); + assert_se(ABS(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); } TEST(float) { @@ -1080,8 +1080,8 @@ TEST(json_dispatch_double) { /* flags= */ 0, &data) >= 0); - assert_se(fabs(data.x1 - 0.5) < 0.01); - assert_se(fabs(data.x2 + 0.5) < 0.01); + assert_se(ABS(data.x1 - 0.5) < 0.01); + assert_se(ABS(data.x2 + 0.5) < 0.01); assert_se(isinf(data.x3)); assert_se(data.x3 > 0); assert_se(isinf(data.x4)); diff --git a/src/shared/color-util.c b/src/shared/color-util.c index ed417a1a2010a..f2add33b1d85b 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -11,8 +11,8 @@ void rgb_to_hsv(double r, double g, double b, assert(g >= 0 && g <= 1); assert(b >= 0 && b <= 1); - double max_color = fmax(r, fmax(g, b)); - double min_color = fmin(r, fmin(g, b)); + double max_color = MAX(r, MAX(g, b)); + double min_color = MIN(r, MIN(g, b)); double delta = max_color - min_color; if (ret_v) diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index 0c2cc500b13b0..bc74856fde629 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "capability-util.h" @@ -660,7 +659,7 @@ TEST(safe_atod) { ASSERT_ERROR(safe_atod("junk", &d), EINVAL); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); @@ -672,7 +671,7 @@ TEST(safe_atod) { return (void) log_tests_skipped_errno(errno, "locale de_DE.utf8 not found"); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index e5972718b46ff..d5043779b702a 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -65,7 +65,7 @@ static void test_random_u64_range_one(unsigned mod) { i, count[i], dev, (int) (count[i] / scale), "x"); - assert_se(fabs(dev) < 6); /* 6 sigma is excessive, but this check should be enough to + assert_se(ABS(dev) < 6); /* 6 sigma is excessive, but this check should be enough to * identify catastrophic failure while minimizing false * positives. */ } diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index e4f471e2aa5dc..b0f22516dfe33 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -244,7 +244,7 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { /* For small deltas, tell the kernel to gradually adjust the system clock to the NTP time, larger * deltas are just directly set. */ - if (fabs(offset) < NTP_MAX_ADJUST) { + if (ABS(offset) < NTP_MAX_ADJUST) { tmx = (struct timex) { .modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR, .status = STA_PLL, @@ -346,7 +346,7 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* always accept offset if we are farther off than the round-trip delay */ - if (fabs(offset) > delay) + if (ABS(offset) > delay) return false; /* we need a few samples before looking at them */ @@ -354,11 +354,11 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* do not accept anything worse than the maximum possible error of the best sample */ - if (fabs(offset) > m->samples[idx_min].delay) + if (ABS(offset) > m->samples[idx_min].delay) return true; /* compare the difference between the current offset to the previous offset and jitter */ - return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; + return ABS(offset - m->samples[idx_cur].offset) > 3 * jitter; } static void manager_adjust_poll(Manager *m, double offset, bool spike) { @@ -371,20 +371,20 @@ static void manager_adjust_poll(Manager *m, double offset, bool spike) { } /* set to minimal poll interval */ - if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { + if (!spike && ABS(offset) > NTP_ACCURACY_SEC) { m->poll_interval_usec = m->poll_interval_min_usec; return; } /* increase polling interval */ - if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { + if (ABS(offset) < NTP_ACCURACY_SEC * 0.25) { if (m->poll_interval_usec < m->poll_interval_max_usec) m->poll_interval_usec *= 2; return; } /* decrease polling interval */ - if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { + if (spike || ABS(offset) > NTP_ACCURACY_SEC * 0.75) { if (m->poll_interval_usec > m->poll_interval_min_usec) m->poll_interval_usec /= 2; return; From e6e65dc26157207a6b720fccc49632ac77236384 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 18:33:43 +0000 Subject: [PATCH 1950/2155] locale-util: dlopen() libintl instead of linking against it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dgettext() lives in libc on glibc and in libintl.so.8 on musl with gettext. Resolve it via dlsym() so neither configuration produces a hard link-time dependency on libintl: try libintl.so.8 first and fall back to RTLD_DEFAULT (which finds dgettext in libc on glibc). The _() macro now expands to a runtime check that returns the untranslated string if dlopen_libintl() has not run successfully, so callers don't have to gate every translatable message on a runtime check. pam_systemd_home — currently the only consumer of _() — calls dlopen_libintl() best-effort from each PAM entry point. The meson find_library('intl') dance is replaced with a has_header() check; the only thing we need at build time is the prototype. --- meson.build | 19 +++++-------------- src/basic/locale-util.c | 31 +++++++++++++++++++++++++++++++ src/basic/locale-util.h | 13 ++++++++++++- src/home/meson.build | 1 - src/home/pam_systemd_home.c | 12 ++++++++++-- src/test/test-dlopen-so.c | 2 ++ 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index 05739e517b274..b26ef7979c996 100644 --- a/meson.build +++ b/meson.build @@ -983,20 +983,11 @@ librt = cc.find_library('rt') libm = cc.find_library('m') libdl = cc.find_library('dl') -# On some distributions that use musl (e.g. Alpine), libintl.h may be provided by gettext rather than musl. -# In that case, we need to explicitly link with libintl.so. -if cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE') - libintl = [] -else - libintl = cc.find_library('intl') - if not cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE', - dependencies : libintl) - error('dgettext() not found') - endif +# Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never +# link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside +# libintl.so.8 which we dlopen() if present. +if not cc.has_header('libintl.h') + error('libintl.h not found (install gettext / gettext-dev)') endif # On some architectures, libatomic is required. But on some installations, diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 3b869d70f9747..6d4493c0bb450 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -7,7 +7,12 @@ #include #include +#ifndef __GLIBC__ +#include "sd-dlopen.h" +#endif + #include "dirent-util.h" +#include "dlfcn-util.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" @@ -22,6 +27,32 @@ #include "strv.h" #include "utf8.h" +#ifdef __GLIBC__ +DLSYM_PROTOTYPE(dgettext) = dgettext; +#else +DLSYM_PROTOTYPE(dgettext) = NULL; +#endif + +int dlopen_libintl(int log_level) { +#ifdef __GLIBC__ + return 1; +#else + static void *libintl_dl = NULL; + + SD_ELF_NOTE_DLOPEN( + "intl", + "Support for message translation via gettext", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libintl.so.8"); + + return dlopen_many_sym_or_warn( + &libintl_dl, + "libintl.so.8", + log_level, + DLSYM_ARG(dgettext)); +#endif +} + static char* normalize_locale(const char *name) { const char *e; diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index bf5cbcb439220..fb22953f16f0e 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -1,9 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include /* IWYU pragma: export */ #include /* IWYU pragma: export */ #include "basic-forward.h" +#include "dlfcn-util.h" + +/* format_arg(2) propagates the format-string nature of the second argument to the return value, so that + * printf(_("Hello %s"), name) still gets checked. It survives both DLSYM_PROTOTYPE's typeof() and the + * ternary in _() below — verified on gcc and clang. */ +extern DLSYM_PROTOTYPE(dgettext) __attribute__((format_arg(2))); + +int dlopen_libintl(int log_level); typedef enum LocaleVariable { /* We don't list LC_ALL here on purpose. People should be @@ -31,7 +40,9 @@ int get_locales(char ***ret); bool locale_is_valid(const char *name); int locale_is_installed(const char *name); -#define _(String) dgettext(GETTEXT_PACKAGE, String) +/* Falls back to the untranslated string if dlopen_libintl() hasn't run or has failed, so callers don't have + * to gate every translatable message on a runtime check. */ +#define _(String) (sym_dgettext ? sym_dgettext(GETTEXT_PACKAGE, (String)) : (String)) #define N_(String) String bool is_locale_utf8(void); diff --git a/src/home/meson.build b/src/home/meson.build index 631eeb13aa879..0b27001c5a04b 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -115,7 +115,6 @@ modules += [ 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, 'dependencies' : [ - libintl, libpam_cflags, threads, ], diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 5fe6dcbec20fc..6e24924e91cfc 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -792,6 +790,8 @@ _public_ PAM_EXTERN int pam_sm_authenticate( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -857,6 +857,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -914,6 +916,8 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, @@ -979,6 +983,8 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -1098,6 +1104,8 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 0eca1356c0313..de1d7671392e8 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -18,6 +18,7 @@ #include "libcrypt-util.h" #include "libfido2-util.h" #include "libmount-util.h" +#include "locale-util.h" #include "main-func.h" #include "microhttpd-util.h" #include "module-util.h" @@ -65,6 +66,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libblkid, HAVE_BLKID); ASSERT_DLOPEN(dlopen_libcrypt, HAVE_LIBCRYPT); ASSERT_DLOPEN(dlopen_libfido2, HAVE_LIBFIDO2); + ASSERT_OK(dlopen_libintl(LOG_DEBUG)); /* Required to be available at build time. */ ASSERT_DLOPEN(dlopen_libkmod, HAVE_KMOD); ASSERT_DLOPEN(dlopen_libmount, HAVE_LIBMOUNT); ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM); From 5a1644de343f91eed7ebc9e3b32dc6d66019a687 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 11:06:21 +0000 Subject: [PATCH 1951/2155] home: Use log2u64() over log2() And drop the libm dependency. --- src/home/homed-manager.c | 8 ++++---- src/home/meson.build | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index fc6fe8a4b1c44..0fcf7b9395e91 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -41,6 +40,7 @@ #include "homed-manager-bus.h" #include "homed-operation.h" #include "homed-varlink.h" +#include "logarithm.h" #include "mkdir.h" #include "notify-recv.h" #include "ordered-set.h" @@ -1937,9 +1937,9 @@ static int manager_rebalance_calculate(Manager *m) { } /* Scale next rebalancing interval based on the least amount of space of any of the home - * directories. We pick a time in the range 1min … 15min, scaled by log2(min_free), so that: - * 10M → ~0.7min, 100M → ~2.7min, 1G → ~4.6min, 10G → ~6.5min, 100G ~8.4 */ - m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2(min_free), 22)*15*USEC_PER_MINUTE)/26, + * directories. We pick a time in the range 1min … 15min, scaled by floor(log2(min_free)), + * so that: 10M → ~0.6min, 100M → ~2.3min, 1G → ~4.0min, 10G → ~6.3min, 100G → ~8.1min */ + m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2u64(min_free), 22u)*15*USEC_PER_MINUTE)/26, 1 * USEC_PER_MINUTE, 15 * USEC_PER_MINUTE); diff --git a/src/home/meson.build b/src/home/meson.build index 0b27001c5a04b..64c62f1dddf2a 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -68,7 +68,6 @@ executables += [ 'sources' : systemd_homed_sources, 'extract' : systemd_homed_extract_sources, 'dependencies' : [ - libm, libopenssl_cflags, threads, ], From a14adac42361d1ebaf6c7c2a8a680939f2d1cd64 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 09:54:53 +0000 Subject: [PATCH 1952/2155] meson: drop libdl, threads, and librt dependencies Our baseline glibc is 2.34, which merged libdl, libpthread (the dependency('threads') target), and librt into libc. Empty .so/.a stubs remain for backward compatibility with old binaries, but new builds resolve dl_*, pthread_*, mq_*, timer_*, etc. directly from libc. On musl the same libraries are likewise empty stubs. Drop the libdl, threads, and librt entries from every meson.build, and remove the now-stale 'Libs.private: -lrt -pthread' from libudev.pc.in since both flags resolve to empty link-time stubs on glibc 2.34+ and musl. Verified with readelf -d that libsystemd.so, libudev.so, and systemd no longer carry DT_NEEDED entries for libdl/libpthread/librt. --- meson.build | 24 ++++-------------------- src/basic/meson.build | 3 --- src/core/meson.build | 3 --- src/coredump/meson.build | 1 - src/creds/meson.build | 1 - src/cryptenroll/meson.build | 1 - src/home/meson.build | 12 +----------- src/import/meson.build | 1 - src/journal-remote/meson.build | 1 - src/journal/meson.build | 5 ----- src/libsystemd/meson.build | 21 +++------------------ src/libudev/libudev.pc.in | 1 - src/locale/meson.build | 1 - src/login/meson.build | 5 ----- src/machine/meson.build | 2 -- src/network/meson.build | 4 ---- src/nsresourced/meson.build | 2 -- src/portable/meson.build | 2 -- src/pstore/meson.build | 1 - src/repart/meson.build | 2 -- src/resolve/meson.build | 1 - src/shared/meson.build | 5 +---- src/socket-activate/meson.build | 1 - src/socket-proxy/meson.build | 1 - src/systemctl/meson.build | 1 - src/sysupdate/meson.build | 2 -- src/test/meson.build | 31 +++++-------------------------- src/timesync/meson.build | 5 +---- src/udev/meson.build | 2 -- src/userdb/meson.build | 3 --- 30 files changed, 15 insertions(+), 130 deletions(-) diff --git a/meson.build b/meson.build index b26ef7979c996..8d5ee4f5792d7 100644 --- a/meson.build +++ b/meson.build @@ -978,10 +978,7 @@ endif ##################################################################### -threads = dependency('threads') -librt = cc.find_library('rt') libm = cc.find_library('m') -libdl = cc.find_library('dl') # Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never # link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside @@ -1758,9 +1755,7 @@ libsystemd = shared_library( link_with : [libc_wrapper_static, libbasic_static], link_whole : [libsystemd_static], - dependencies : [librt, - threads, - userspace], + dependencies : [userspace], link_depends : libsystemd_sym, install : true, install_tag: 'libsystemd', @@ -1779,14 +1774,11 @@ if static_libsystemd != 'false' install_tag: 'libsystemd', install_dir : libdir, pic : static_libsystemd_pic, - dependencies : [libdl, - libgcrypt_cflags, + dependencies : [libgcrypt_cflags, liblz4_cflags, libm, - librt, libxz_cflags, libzstd_cflags, - threads, userspace], c_args : libsystemd_c_args + (static_libsystemd_pic ? [] : ['-fno-PIC'])) @@ -1804,8 +1796,7 @@ libudev = shared_library( '-Wl,--version-script=' + libudev_sym_path], link_with : [libsystemd_static], link_whole : libudev_basic, - dependencies : [threads, - userspace], + dependencies : [userspace], link_depends : libudev_sym, install : true, install_tag: 'libudev', @@ -1922,10 +1913,6 @@ nss_template = { libshared_static, libbasic_static, ], - 'dependencies' : [ - librt, - threads, - ], 'install' : true, 'install_tag' : 'nss', 'install_dir' : libdir, @@ -1938,10 +1925,7 @@ pam_template = { libsystemd_static, libshared_static, ], - 'dependencies' : [ - libpam_cflags, - threads, - ], + 'dependencies' : libpam_cflags, 'install' : true, 'install_tag' : 'pam', 'install_dir' : pamlibdir, diff --git a/src/basic/meson.build b/src/basic/meson.build index f847b175b61f0..9007cff178149 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -212,15 +212,12 @@ libbasic_static = static_library( include_directories : basic_includes, implicit_include_directories : false, dependencies : [libbzip2_cflags, - libdl, libgcrypt_cflags, liblz4_cflags, libm, - librt, libxz_cflags, libz_cflags, libzstd_cflags, - threads, userspace], c_args : ['-fvisibility=default'], build_by_default : false) diff --git a/src/core/meson.build b/src/core/meson.build index 2bd8170c6a2eb..8971800bcbafa 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -148,13 +148,10 @@ libcore_static = static_library( dependencies : [libaudit_cflags, libbpf_cflags, libcryptsetup_cflags, - libdl, libm, libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, userspace], build_by_default : false) diff --git a/src/coredump/meson.build b/src/coredump/meson.build index 355993d09ef1f..b0753d86fa882 100644 --- a/src/coredump/meson.build +++ b/src/coredump/meson.build @@ -30,7 +30,6 @@ common_dependencies = [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ diff --git a/src/creds/meson.build b/src/creds/meson.build index dc4a5a28ae316..c18fe2ec8901d 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -12,7 +12,6 @@ executables += [ 'dependencies' : [ libmount_cflags, libopenssl_cflags, - threads, ], }, ] diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 8213a0e672572..aa789c0a071ab 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -22,7 +22,6 @@ executables += [ 'sources' : systemd_cryptenroll_sources, 'dependencies' : [ libcryptsetup_cflags, - libdl, libfido2_cflags, libopenssl_cflags, libp11kit_cflags, diff --git a/src/home/meson.build b/src/home/meson.build index 64c62f1dddf2a..b724517ae9ce9 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -67,10 +67,7 @@ executables += [ 'dbus' : true, 'sources' : systemd_homed_sources, 'extract' : systemd_homed_extract_sources, - 'dependencies' : [ - libopenssl_cflags, - threads, - ], + 'dependencies' : libopenssl_cflags, }, libexec_template + { 'name' : 'systemd-homework', @@ -81,7 +78,6 @@ executables += [ libfdisk_cflags, libopenssl_cflags, libp11kit_cflags, - threads, ], }, executable_template + { @@ -91,10 +87,8 @@ executables += [ 'extract' : homectl_extract, 'objects' : ['systemd-homed'], 'dependencies' : [ - libdl, libopenssl_cflags, libp11kit_cflags, - threads, ], }, test_template + { @@ -113,10 +107,6 @@ modules += [ 'name' : 'pam_systemd_home', 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, - 'dependencies' : [ - libpam_cflags, - threads, - ], 'version-script' : meson.current_source_dir() / 'pam_systemd_home.sym', }, ] diff --git a/src/import/meson.build b/src/import/meson.build index c2879c5d843cf..f133f276b4be2 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -17,7 +17,6 @@ executables += [ 'import-common.c', 'qcow2-util.c', ), - 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-pull', diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index f6aa71349c24c..22ac8703b55d4 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -25,7 +25,6 @@ common_deps = [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ diff --git a/src/journal/meson.build b/src/journal/meson.build index 1f40e9b43b1a9..75cd7b7a30ae4 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -72,7 +72,6 @@ executables += [ libselinux_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, libexec_template + { @@ -86,7 +85,6 @@ executables += [ 'public' : true, 'sources' : files('cat.c'), 'objects' : ['systemd-journald'], - 'dependencies' : [threads], }, executable_template + { 'name' : 'journalctl', @@ -94,11 +92,9 @@ executables += [ 'sources' : journalctl_sources, 'link_with' : journalctl_link_with, 'dependencies' : [ - libdl, liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, journal_test_template + { @@ -123,7 +119,6 @@ executables += [ liblz4_cflags, libselinux_cflags, libxz_cflags, - threads, ], }, journal_test_template + { diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 2c86a231064a8..b7e11c9bb22b0 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -150,9 +150,7 @@ libsystemd_static = static_library( c_args : libsystemd_c_args, link_with : [libc_wrapper_static, libbasic_static], - dependencies : [threads, - libm, - librt, + dependencies : [libm, userspace], build_by_default : false) @@ -203,20 +201,17 @@ simple_tests += files( libsystemd_tests += [ { 'sources' : files('sd-bus/test-bus-address.c'), - 'dependencies' : threads }, { 'sources' : files('sd-bus/test-bus-benchmark.c'), - 'dependencies' : threads, 'type' : 'manual', }, { 'sources' : files('sd-bus/test-bus-chat.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-cleanup.c'), - 'dependencies' : [threads, libseccomp_cflags], + 'dependencies' : libseccomp_cflags, }, { 'sources' : files('sd-bus/test-bus-marshal.c'), @@ -226,28 +221,22 @@ libsystemd_tests += [ libglib_cflags, libgobject_cflags, libm, - threads, ], }, { 'sources' : files('sd-bus/test-bus-objects.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-peersockaddr.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-queue-ref-cycle.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-server.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-signature.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-track.c'), @@ -255,12 +244,11 @@ libsystemd_tests += [ }, { 'sources' : files('sd-bus/test-bus-watch-bind.c'), - 'dependencies' : threads, 'timeout' : 120, }, { 'sources' : files('sd-device/test-sd-device.c'), - 'dependencies' : [threads, libmount_cflags], + 'dependencies' : libmount_cflags, }, { 'sources' : files('sd-event/test-event.c'), @@ -288,16 +276,13 @@ libsystemd_tests += [ }, { 'sources' : files('sd-resolve/test-resolve.c'), - 'dependencies' : threads, 'timeout' : 120, }, { 'sources' : files('sd-varlink/test-varlink.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-varlink/test-varlink-idl.c'), - 'dependencies' : threads, }, ] diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in index 6541bcb1ab6b8..72d46ffc50530 100644 --- a/src/libudev/libudev.pc.in +++ b/src/libudev/libudev.pc.in @@ -16,5 +16,4 @@ Name: libudev Description: Library to access udev device information Version: {{PROJECT_VERSION}} Libs: -L${libdir} -ludev -Libs.private: -lrt -pthread Cflags: -I${includedir} diff --git a/src/locale/meson.build b/src/locale/meson.build index 1b97628abbc42..2f99bb8d8072a 100644 --- a/src/locale/meson.build +++ b/src/locale/meson.build @@ -18,7 +18,6 @@ localectl_sources = files('localectl.c') # specify where the headers are. if conf.get('HAVE_XKBCOMMON') == 1 libxkbcommon_deps = [ - libdl, libxkbcommon_cflags, ] else diff --git a/src/login/meson.build b/src/login/meson.build index 390960d5f6c10..44325ccd7c02f 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -48,9 +48,6 @@ executables += [ 'dbus' : true, 'sources' : systemd_logind_sources, 'extract' : systemd_logind_extract_sources, - 'dependencies' : [ - threads, - ], }, executable_template + { 'name' : 'loginctl', @@ -60,7 +57,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, executable_template + { @@ -79,7 +75,6 @@ executables += [ test_template + { 'sources' : files('test-login-tables.c'), 'objects' : ['systemd-logind'], - 'dependencies' : threads, }, test_template + { 'sources' : files('test-session-properties.c'), diff --git a/src/machine/meson.build b/src/machine/meson.build index 13756cb8a1ba2..fc16e9f5c5f32 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -36,13 +36,11 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, test_template + { 'sources' : files('test-machine-tables.c'), 'objects' : ['systemd-machined'], - 'dependencies': threads, }, ] diff --git a/src/network/meson.build b/src/network/meson.build index 0319102492590..23f6f3a4fe587 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -188,7 +188,6 @@ network_fuzz_template = fuzz_template + { libsystemd_network, ], 'objects' : ['systemd-networkd'], - 'dependencies' : threads, 'include_directories' : network_includes, } @@ -204,7 +203,6 @@ executables += [ libsystemd_network, networkd_link_with, ], - 'dependencies' : threads, 'bpf_programs': [ 'sysctl-monitor', ] @@ -240,11 +238,9 @@ executables += [ }, network_test_template + { 'sources' : files('test-network-tables.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-network.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-networkd-address.c'), diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build index 881fd911e418a..1654e1766b1eb 100644 --- a/src/nsresourced/meson.build +++ b/src/nsresourced/meson.build @@ -24,13 +24,11 @@ executables += [ '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'], }, diff --git a/src/portable/meson.build b/src/portable/meson.build index 3029ad4177784..0bd5d1c3ce262 100644 --- a/src/portable/meson.build +++ b/src/portable/meson.build @@ -32,7 +32,6 @@ executables += [ libcryptsetup_cflags, libmount_cflags, libselinux_cflags, - threads, ], }, executable_template + { @@ -40,7 +39,6 @@ executables += [ 'public' : true, 'sources' : files('portablectl.c'), 'link_with' : portabled_link_with, - 'dependencies' : threads, }, ] diff --git a/src/pstore/meson.build b/src/pstore/meson.build index d6bb925789778..a94f1b1e8841c 100644 --- a/src/pstore/meson.build +++ b/src/pstore/meson.build @@ -12,7 +12,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, ] diff --git a/src/repart/meson.build b/src/repart/meson.build index 9b89f56f7a0f2..bb5b63be054b2 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -17,7 +17,6 @@ executables += [ libfdisk_cflags, libmount_cflags, libopenssl_cflags, - threads, ], }, executable_template + { @@ -35,7 +34,6 @@ executables += [ libfdisk_cflags, libmount_cflags, libopenssl_cflags, - threads, ], }, ] diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 5802889746e97..3650313355576 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -72,7 +72,6 @@ resolve_common_template = { libidn2_cflags, libopenssl_cflags, libm, - threads, ], } diff --git a/src/shared/meson.build b/src/shared/meson.build index ce96ce3025cb0..55cf8364d3748 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -383,8 +383,7 @@ shared_sources += [dns_type_from_name_inc, dns_type_to_name_inc] libshared_name = 'systemd-shared-@0@'.format(shared_lib_tag) -libshared_deps = [threads, - libacl_cflags, +libshared_deps = [libacl_cflags, libapparmor_cflags, libarchive_cflags, libaudit_cflags, @@ -393,7 +392,6 @@ libshared_deps = [threads, libcrypt_cflags, libcryptsetup_cflags, libcurl_cflags, - libdl, libdw_cflags, libelf_cflags, libfdisk_cflags, @@ -410,7 +408,6 @@ libshared_deps = [threads, libpcre2_cflags, libpwquality_cflags, libqrencode_cflags, - librt, libseccomp_cflags, libselinux_cflags, libxenctrl_cflags, diff --git a/src/socket-activate/meson.build b/src/socket-activate/meson.build index a4d18b58a8dab..628dbe79ffccc 100644 --- a/src/socket-activate/meson.build +++ b/src/socket-activate/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-activate', 'public' : true, 'sources' : files('socket-activate.c'), - 'dependencies' : threads, }, ] diff --git a/src/socket-proxy/meson.build b/src/socket-proxy/meson.build index 52d63a8440c88..1106b445e9cd4 100644 --- a/src/socket-proxy/meson.build +++ b/src/socket-proxy/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-proxyd', 'public' : true, 'sources' : files('socket-proxyd.c'), - 'dependencies' : threads, }, ] diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 2ce11c8f48d0a..882704c9d7d87 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -58,7 +58,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], 'install_tag' : 'systemctl', }, diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 0cfe22f5283dd..da554ddcc3d99 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -30,7 +30,6 @@ executables += [ 'dependencies' : [ libfdisk_cflags, libopenssl_cflags, - threads, ], }, libexec_template + { @@ -38,7 +37,6 @@ executables += [ 'dbus' : true, 'conditions' : ['ENABLE_SYSUPDATED'], 'sources' : files('sysupdated.c'), - 'dependencies' : threads, }, executable_template + { 'name' : 'updatectl', diff --git a/src/test/meson.build b/src/test/meson.build index c8279bc7514f0..b581de293b307 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -223,10 +223,8 @@ simple_tests += files( common_test_dependencies = [ libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, ] executables += [ @@ -303,14 +301,13 @@ executables += [ ], }, test_template + { - # only static linking apart from libdl, to make sure that the - # module is linked to all libraries that it uses. + # only static linking, to make sure that the module is linked + # to all libraries that it uses. 'sources' : files('test-dlopen.c'), 'link_with' : [ libc_wrapper_static, libbasic_static, ], - 'dependencies' : libdl, 'install' : false, 'type' : 'manual', }, @@ -359,10 +356,7 @@ executables += [ test_template + { 'sources' : files('test-libmount.c'), 'conditions' : ['HAVE_LIBMOUNT'], - 'dependencies' : [ - libmount_cflags, - threads, - ], + 'dependencies' : libmount_cflags, }, test_template + { 'sources' : files('test-loopback.c'), @@ -378,7 +372,6 @@ executables += [ }, test_template + { 'sources' : files('test-pressure.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-mount-util.c'), @@ -398,17 +391,13 @@ executables += [ test_template + { 'sources' : files('test-nss-hosts.c'), 'extract' : files('nss-test-util.c'), - 'dependencies' : [ - libdl, - libseccomp_cflags, - ], + 'dependencies' : libseccomp_cflags, 'conditions' : ['ENABLE_NSS'], 'timeout' : 120, }, test_template + { 'sources' : files('test-nss-users.c'), 'objects' : ['test-nss-hosts'], - 'dependencies' : libdl, 'conditions' : ['ENABLE_NSS'], }, test_template + { @@ -426,7 +415,6 @@ executables += [ }, test_template + { 'sources' : files('test-process-util.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-progress-bar.c'), @@ -441,7 +429,6 @@ executables += [ }, test_template + { 'sources' : files('test-qrcode-util.c'), - 'dependencies' : libdl, }, test_template + { 'sources' : files('test-random-util.c'), @@ -471,7 +458,6 @@ executables += [ }, test_template + { 'sources' : files('test-set-disable-mempool.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-sizeof.c'), @@ -603,7 +589,6 @@ executables += [ }, core_test_template + { 'sources' : files('test-loop-util.c'), - 'dependencies' : [threads], 'parallel' : false, }, core_test_template + { @@ -611,10 +596,7 @@ executables += [ }, core_test_template + { 'sources' : files('test-namespace.c'), - 'dependencies' : [ - threads, - libmount_cflags, - ], + 'dependencies' : libmount_cflags, }, core_test_template + { 'sources' : files('test-ns.c'), @@ -632,7 +614,6 @@ executables += [ }, core_test_template + { 'sources' : files('test-socket-bind.c'), - 'dependencies' : libdl, 'conditions' : ['BPF_FRAMEWORK'], }, core_test_template + { @@ -666,7 +647,6 @@ executables += [ libbasic_static, libsystemd, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-udev-device-thread.c'), @@ -675,7 +655,6 @@ executables += [ libbasic_static, libudev, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-libudev.c'), diff --git a/src/timesync/meson.build b/src/timesync/meson.build index b30724577202a..c42fcd3e70963 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -35,10 +35,7 @@ executables += [ 'sources' : timesyncd_sources, 'extract' : timesyncd_extract_sources, 'link_with' : timesyncd_link_with, - 'dependencies' : [ - libm, - threads, - ], + 'dependencies' : libm, }, libexec_template + { 'name' : 'systemd-time-wait-sync', diff --git a/src/udev/meson.build b/src/udev/meson.build index 700a8cc8d5ed3..7e2435d0fab68 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -122,7 +122,6 @@ udev_dependencies = [ libblkid_cflags, libkmod_cflags, libmount_cflags, - threads, ] udev_plugin_template = executable_template + { @@ -138,7 +137,6 @@ udev_common_template = { 'dependencies' : [ libacl_cflags, libblkid_cflags, - threads, ], } udev_test_template = test_template + udev_common_template diff --git a/src/userdb/meson.build b/src/userdb/meson.build index a933a4907b32f..c25c40bc0ab0a 100644 --- a/src/userdb/meson.build +++ b/src/userdb/meson.build @@ -5,7 +5,6 @@ executables += [ 'name' : 'systemd-userwork', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userwork.c'), - 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-userdbd', @@ -14,13 +13,11 @@ executables += [ 'userdbd-manager.c', 'userdbd.c', ), - 'dependencies' : threads, }, executable_template + { 'name' : 'userdbctl', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userdbctl.c'), - 'dependencies' : threads, }, ] From b77c7b7cfc243691c44d5104e8440cd3aeb86ace Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 19:20:02 +0000 Subject: [PATCH 1953/2155] libc: Use dlsym() from a constructor instead of weak symbols Weak symbols still introduce a version requirement on a newer libc. Resolve each libc symbol via dlsym(RTLD_DEFAULT) from a per-shim constructor and cache the result in a file-scope static instead. This avoids the version requirement, keeps the call path free of atomics (constructors run single-threaded before main() and before any signal handler can fire), and keeps dlsym() out of contexts where it is not async-signal-safe. --- src/libc/libc-shim.h | 64 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/libc/libc-shim.h b/src/libc/libc-shim.h index c5cf9c8acdab5..022ee5ad157bd 100644 --- a/src/libc/libc-shim.h +++ b/src/libc/libc-shim.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include #include #include @@ -35,21 +36,38 @@ #define _SHIM_NAME_8(t, n, ...) n, _SHIM_NAME_7(__VA_ARGS__) #define _SHIM_NAME(...) _SHIM_CAT(_SHIM_NAME_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) -/* Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the - * corresponding direct syscall otherwise. The libc symbol is redeclared as a weak reference so the - * binary still loads on libc versions that don't provide it. Each parameter is passed as type, - * name pairs (flat). +/* The shim resolves the libc symbol via dlsym(RTLD_DEFAULT) at DSO-load time using a constructor + * and caches the result in a file-scope static. Constructors run single-threaded before main() and + * before any signal handler can fire, so the cached pointer needs no atomics: subsequent reads from + * any thread observe the value stored during init. Resolving eagerly also keeps dlsym() out of + * contexts where it is not async-signal-safe (signal handlers, between fork() and exec()). * - * The weak reference is bound to the libc symbol via an __asm__("label") rename so that the bare - * libc identifier never appears as a C token. This matters because override/musl headers - * sometimes #define the libc name to redirect it to the _shim variant — without the rename the - * caller would have to #undef each name before invoking the macro. # and ## operators don't - * macro-expand their operands, so the parameter "func" stays a literal token everywhere. */ + * The asm barrier after dlsym() is load-bearing: without it, when LTO determines the cache store + * is dead (because no caller of func##_shim survives DCE) the compiler is free to tail-call + * dlsym() (jmp dlsym@plt). Under glibc, dlsym reads __builtin_return_address(0) to find its + * caller's link map; with a tail call that read lands inside ld.so's call_init(), the resulting + * link map has no l_scope, and _dl_lookup_symbol_x SIGSEGVs. Filed upstream in glibc by + * https://sourceware.org/bugzilla/show_bug.cgi?id=34156. The barrier keeps us working on + * unpatched libc. + * + * Each reference to `func` in the macro body is positioned as an operand of `#` or `##` so the + * override headers (e.g. "#define openat2 openat2_shim") don't rewrite the token before pasting or + * stringification. For the same reason the resolution logic isn't extracted into a helper macro — + * passing `func` to a nested macro would expand it as a regular argument and re-trigger the + * override. + * + * Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the + * corresponding direct syscall otherwise. Each parameter is passed as type, name pairs (flat). */ #define DEFINE_SYSCALL_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ return syscall(__NR_##func, _SHIM_NAME(__VA_ARGS__)); \ } @@ -57,20 +75,30 @@ * by returning the positive errno value directly (posix_spawn-family convention). If the libc symbol * is missing at runtime, ENOSYS is returned. */ #define DEFINE_LIBC_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ return ENOSYS; \ } /* Like DEFINE_LIBC_SHIM but for libc helpers that report errors via errno + -1 return value. If the * libc symbol is missing at runtime, errno is set to ENOSYS and -1 is returned. */ #define DEFINE_LIBC_ERRNO_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ errno = ENOSYS; \ return -1; \ } From 97f81508c51cd11e01fc9b32cb3d7825f4a56001 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 19:20:02 +0000 Subject: [PATCH 1954/2155] libc: Make sure C23 versions of strtol(), sscanf() are not used When _GNU_SOURCE is defined, glibc will always use c23 versions of strtol(), sscanf() and friends if available (introduced after glibc 2.34). Which means that any binaries built with headers from newer glibc won't load on glibc < 2.38. To work around this, redefine the appropriate constants to zero make sure the c99 versions are used instead. --- meson.build | 13 ++++++------- src/include/glibc/stdio.h | 18 ++++++++++++++++++ src/include/glibc/stdlib.h | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 src/include/glibc/stdio.h create mode 100644 src/include/glibc/stdlib.h diff --git a/meson.build b/meson.build index 8d5ee4f5792d7..56b841ed359b5 100644 --- a/meson.build +++ b/meson.build @@ -1691,14 +1691,13 @@ system_includes = [ ), ] -if get_option('libc') == 'musl' - system_include_args = [ - '-isystem', meson.project_build_root() / 'src/include/musl', - '-isystem', meson.project_source_root() / 'src/include/musl', - ] + system_include_args +libc_include_dir = 'src/include' / get_option('libc') +system_include_args = [ + '-isystem', meson.project_build_root() / libc_include_dir, + '-isystem', meson.project_source_root() / libc_include_dir, +] + system_include_args - system_includes += include_directories('src/include/musl', is_system : true) -endif +system_includes += include_directories(libc_include_dir, is_system : true) basic_includes = [ include_directories( diff --git a/src/include/glibc/stdio.h b/src/include/glibc/stdio.h new file mode 100644 index 0000000000000..7e45cb3ba28de --- /dev/null +++ b/src/include/glibc/stdio.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdio.h to route sscanf/fscanf to the old __isoc99_* siblings (GLIBC_2.7) rather + * than the newer __isoc23_* ones (GLIBC_2.38). The only behavioural difference is "0b" prefix + * support in %i conversions, which we don't use. We include features.h first so the macro is set + * to its normal value, then override it before stdio.h's body evaluates __GLIBC_USE(C23_STRTOL). + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */ diff --git a/src/include/glibc/stdlib.h b/src/include/glibc/stdlib.h new file mode 100644 index 0000000000000..a3ad2a3b5ab9a --- /dev/null +++ b/src/include/glibc/stdlib.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdlib.h to leave strtol/strtoul/strtoll/strtoull as their original GLIBC_2.2.5 + * symbols rather than redirect to __isoc23_* (GLIBC_2.38). The only behavioural difference is + * "0b" prefix support in base 0/2 parsing, which we don't use. + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */ From 606698ec748bd6105fdff9cb0d70da8a9214649e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 18 May 2026 23:17:28 +0200 Subject: [PATCH 1955/2155] update NEWS --- NEWS | 116 +++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index 1eb4746fe8d03..6b5e5fb061aba 100644 --- a/NEWS +++ b/NEWS @@ -53,6 +53,12 @@ CHANGES WITH 261 in spe: changed to restrict socket address families to AF_INET, AF_INET6 and AF_UNIX. + * The experimental "systemd-sysupdated" D-Bus API is going to be + removed again in the next release. The plan is that in its place + clients should directly talk to systemd-sysupdate (i.e. the backend + of "systemd-sysupdated") via Varlink IPC. The "updatectl" tool will + be reworked along these lines. + Changes in the system and service manager: * PID1 now supports the kernel's Live Update Orchestration (LUO) / @@ -73,6 +79,30 @@ CHANGES WITH 261 in spe: support, this lets user units persist state (e.g.: memfds) across not only user session restarts, but also kexec reboots. + * The hardware database now contains a new database hwdb.d/40-imds.hwdb + that recognizes various established public clouds by their SMBIOS + information, and provides information how to reach local IMDS + functionality on the node. Currently, Amazon EC2, Microsoft Azure, + Google Compute Engine, Hetzner, Oracle Cloud, Scaleway are + recognized. + + * An IMDS subsystem has been added. Specifically, there's now + systemd-imdsd which provides a local Varliknk IPC API that makes IMDS + services accessible locally. It provides both a relatively low-level + interface for querying arbitrary fields, and a higher level interface + for querying certain well-known keys in a generic way (which maps to + various cloud specific keys via the hwdb). The service can be pulled + into the boot transaction automatically if a supported cloud is + recognized via the systemd-imds-generator functionality. This permits + implementation of truly generic images, that can interact with IMDS + if available, but operate without if not.l + + * 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. + * The manager exposes a new ReloadCount property on its D-Bus and Varlink interfaces (org.freedesktop.systemd1.Manager and io.systemd.Manager respectively). The counter increments after @@ -97,7 +127,7 @@ CHANGES WITH 261 in spe: BPF LSM program to restrict execution to only binaries that are stored on a signed and verified dm-verity protected filesystem. - * The io.systemd.Unit.StartTransient Varlink method has been extended + * The io.systemd.Unit.StartTransient() Varlink method has been extended to accept SetCredentials, SetCredentialsEncrypted, Environment and WorkingDirectory fields, on par with what is already possible via the legacy D-Bus interface. @@ -123,16 +153,15 @@ CHANGES WITH 261 in spe: * A new tmpfiles.d/root.conf has been added that sets permissions on the root directory (/) to 0555. - * systemd-tmpfiles gained a new --inline option to accept - tmpfiles.d directives on the command line. + * systemd-tmpfiles gained a new --inline switch which permits passing + tmpfiles.d/ directives directly on the command line rather than via a + configuration file or STDIN. This is similar to the switch of the + same name to systemd-sysusers. * New directive types 'k/K' have been added to systemd-tmpfiles for setting file capabilities. - * systemd-firstboot can now set the static hostname from a system - credential (firstboot.hostname). - - Changes in systemd-sysext and systemd-confext: + Changes in systemd-sysext/systemd-confext: * New initrd services systemd-sysext-sysroot.service and systemd-confext-sysroot.service are provided. These services are @@ -178,8 +207,13 @@ CHANGES WITH 261 in spe: settings are now supported to allow overriding the default caches sizes for the respective protocols. - * Additional local resource records may now be defined via drop-in - configuration files, complementing the existing global definitions. + * systemd-resolved will now read additional DNS resource record + definitions to resolve locally from JSON drop-in files in + {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. This + is a generalization of /etc/hosts in a way, but is supposed to be + more flexible (i.e. other RR types than just A/AAAA + PTR can be + configured, even if right now not too many are hooked up yet) and + follow the usual drop-in pattern that avoids ownership conflicts. * Insecure DNSSEC answers using unsupported signature or digest algorithms are now correctly accepted as insecure, rather than @@ -202,11 +236,18 @@ CHANGES WITH 261 in spe: Changes in systemd-boot, systemd-stub, bootctl, ukify and BLS: - * A new "boot secret" mechanism has been added: systemd-boot can - provision a per-system secret in an EFI variable that is locked - down so that the OS cannot read it back. This allows the boot - loader to attest its identity to the booted system without giving - the system the means to impersonate it on systems without a TPM2. + * systemd-stub will now maintain a "boot secret" and pass it to the OS + in the /.extra/boot-secret file in the initrd. This boot secret is + derived from a persistent EFI variable that is not accessible by the + OS (i.e. only accessible in the UEFI environment). The EFI variable + is automatically initialized to a randomly generated value if not set + yet. It is intended to be used for certain fallback codepaths in case + a local TPM is not available, but an UEFI environment is. If a TPM is + available, it's highly recommended to use it as a better source of + per-system key material, but in absence of a TPM it often might be an + acceptable fallback for local, persistent key material. Applications + should never use the key as-is, but derive their own key from it, + through hashing. * systemd-stub now auto-detects the active EFI serial console device and appends an appropriate "console=" parameter to the @@ -244,8 +285,10 @@ CHANGES WITH 261 in spe: * A new BlockDeviceReplace= setting allows partitions to atomically replace the contents of an existing block device. - * A new --grain-size= command line option overrides the alignment - granularity used when placing partitions. + * systemd-repart now supports a new --grain-size= switch to explicitly + select the desired "grain" size (i.e. alignment granularity) when + placing partitions. It defaults to 4K (as before), but can now be set + to any other power of 2 larger than the sector size. * A new --el-torito= command line option causes a minimal El Torito boot catalog to be written for EFI boot on hybrid ISO @@ -296,8 +339,16 @@ CHANGES WITH 261 in spe: etc.); a PTY is now provided for the native console mode, and headless console operation is supported. - * systemd-vmspawn gained a new --efi-nvram-template= option that - selects the EFI variable store template. + * systemd-vmspawn gained a new switch --efi-nvram-state= for + controlling whether and where to persist the EFI variable NVRAM + between VM invocations. It's modelled after --tpm-state= in + behaviour. + + * systemd-vmspawn's TPM logic will now ensure to install an + endorsement certificate. + + * systemd-vmspawn's --console= switch gained a new value "headless" to + spawn a VM in truly headless mode, i.e without a console or display. * systemd-vmspawn gained a new --firmware-features= option that enables or disables individual firmware features (with a @@ -306,11 +357,10 @@ CHANGES WITH 261 in spe: * systemd-vmspawn now supports direct kernel boot without UEFI firmware. - * systemd-vmspawn gained support for new disk types 'nvme', - 'virtio-scsi' and 'scsi-cd' (for ISO/CD-ROM images). - - * systemd-vmspawn now exposes a QMP-to-Varlink bridge that makes - the running QEMU instance reachable to other tools at runtime. + * systemd-vmspawn gained support for a new --image-disk-type= switch + for selecting the block storage type (virtio-blk, virtio-scsi, nvme) + for block devices exposed to the VM. The --extra-drive= switch + optionally can configure this too now. * The io.systemd.MachineInstance Varlink interface gained AddStorage(), RemoveStorage() and ReplaceStorage() methods for @@ -466,6 +516,26 @@ CHANGES WITH 261 in spe: * The unused dependency on libgpg-error has been dropped. + * systemd-firstboot will now honour a new "firstboot.hostname" system + credential for persistently setting the system hostname on first + boot. This is different from the pre-existing "system.hostname" which + sets the hostname on boot the credential is passed on only, and which + is not made persistent. + + * systemd-hostnamed now provides a D-Bus API to acquire arbitrary + fields from /etc/machine-info. + + * systemd-hostnamed is now available in early boot too (i.e. before + basic.target). Note that D-Bus only becomes available later, and it + hence can only be contacted via Varlink that early. + + * JSON user database records may now optionally carry a birth date + field. homectl gained a new switch --birth-date= to set it. + + * systemd-vconsole-setup will now gracefully handle if the + setfont/loadkeys tools are not installed, and skip operation cleanly + in that case. + CHANGES WITH 260: Feature Removals and Incompatible Changes: From 9a8e2f17ef17d4c5528d4e614c7d0a911285faff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 18 May 2026 23:43:23 +0200 Subject: [PATCH 1956/2155] update NEWS --- NEWS | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 6b5e5fb061aba..3bb7156e123e5 100644 --- a/NEWS +++ b/NEWS @@ -87,7 +87,7 @@ CHANGES WITH 261 in spe: recognized. * An IMDS subsystem has been added. Specifically, there's now - systemd-imdsd which provides a local Varliknk IPC API that makes IMDS + systemd-imdsd which provides a local Varlink IPC API that makes IMDS services accessible locally. It provides both a relatively low-level interface for querying arbitrary fields, and a higher level interface for querying certain well-known keys in a generic way (which maps to @@ -95,7 +95,10 @@ CHANGES WITH 261 in spe: into the boot transaction automatically if a supported cloud is recognized via the systemd-imds-generator functionality. This permits implementation of truly generic images, that can interact with IMDS - if available, but operate without if not.l + if available, but operate without if not. A tool systemd-imds acts as + a client to systemd-imdsd and imports various IMDS provided fields + into local system credentials, which can then be consumed by later + services. The acquired IMDS is measured before being imported. * Networking to cloud IMDS services may be locked down for recognized clouds. This is recommended for secure installations, but typically @@ -109,10 +112,14 @@ CHANGES WITH 261 in spe: each successfully completed daemon-reload, and it is reset on daemon-reexec. - * A new ConditionSecurity=measured-os condition has been added that - checks whether the system was booted with measured-boot semantics - (i.e. via systemd-stub or an equivalent verified-boot mechanism - that measured the OS to the TPM). + * A new ConditionSecurity=measured-os unit condition has been added + that checks whether the system was booted with measured-boot + semantics (i.e. via systemd-stub or an equivalent verified-boot + mechanism that measured the OS to the TPM). This is very similar to + the pre-existing ConditionSecurity=measured-uki however is a more + generic as it can also cover environments where the firmware/UKI does + not have a TPM but the OS has (which is for example the case if the + TPM is implemented purely in software). * A new unit setting CPUSetPartition= has been added that allows configuring the cpuset cgroup partition type (e.g. "root", @@ -463,10 +470,6 @@ CHANGES WITH 261 in spe: command as a Varlink server, and a new '--upgrade' option (along with '--exec') to consume the protocol upgrade API. - * A new JsonStream transport-layer module has been added for - consumers building higher-level JSON-over-stream protocols on - top of sd-json. - * sd-path now exposes an XDG 'projects' user directory. * sd-device gained a number of helpers, including @@ -536,6 +539,25 @@ CHANGES WITH 261 in spe: setfont/loadkeys tools are not installed, and skip operation cleanly in that case. + * sd_json_parse() (and related calls) now supports a pair of new flags + SD_JSON_PARSE_MUST_BE_OBJECT and SD_JSON_PARSE_MUST_BE_ARRAY. If + specified this flags cause the parser to failure if the top-level + parsed JSON variant is not an object/array. + + * A new service systemd-tpm2-swtpm.service has been added that can run + the IBM "swtpm" as a software TPM, for use as (optional) automatic + fallback for systems that lack a physical TPM but where TPM + functionality should be made available nonetheless. (This + functionality must be enabled via systemd.tpm2_software_fallback= on + the kernel command line.) Of course a software TPM running as part of + a system's userspace does not provide a security posture in any way + equivalent to that of a discrete hardware TPM, however in various + usecase it might still be preferable over having no TPM functionality + at all. The software TPM uses a key derived from the new "boot + secret" functionality for encryption, and stores its state in the + disk's TPM. This provides at least some protection, and reasonable + persistancy from initrd on. + CHANGES WITH 260: Feature Removals and Incompatible Changes: From aa76d81122a1db488c7116cf44d949f07b8fb198 Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Mon, 18 May 2026 13:30:51 -0400 Subject: [PATCH 1957/2155] resolved: add missing polkit checks on FlushCaches and ResetServerFeatures D-Bus methods The FlushCaches and ResetServerFeatures D-Bus methods perform destructive operations (flushing all DNS caches and resetting server feature negotiation including DNS-over-TLS state) without any authorization check. The corresponding Varlink methods already enforce polkit via verify_polkit(), but the D-Bus handlers were not updated. Add bus_verify_polkit_async() calls to both methods, matching the pattern used by ResetStatistics. Add the corresponding policy actions to the polkit policy file. --- src/resolve/org.freedesktop.resolve1.policy | 22 +++++++++++++++++++ src/resolve/resolved-bus.c | 24 +++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/resolve/org.freedesktop.resolve1.policy b/src/resolve/org.freedesktop.resolve1.policy index 097e78e73ca7e..6fa243856c7a4 100644 --- a/src/resolve/org.freedesktop.resolve1.policy +++ b/src/resolve/org.freedesktop.resolve1.policy @@ -205,4 +205,26 @@ unix-user:systemd-resolve + + Flush DNS caches + Authentication is required to flush DNS caches. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + + + Reset server features + Authentication is required to reset server features. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index e20c975de8b38..e04caa7898c0a 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1842,9 +1842,21 @@ static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_e static int bus_method_flush_caches(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.flush-caches", + /* 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, "cache flush"); manager_flush_caches(m, LOG_INFO); @@ -1854,9 +1866,21 @@ static int bus_method_flush_caches(sd_bus_message *message, void *userdata, sd_b static int bus_method_reset_server_features(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-server-features", + /* 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, "server feature reset"); (void) dns_stream_disconnect_all(m); From 0614602dfa1a4af016f323ca0a8e40efd12c5b11 Mon Sep 17 00:00:00 2001 From: Dmitry Konishchev Date: Tue, 19 May 2026 09:25:20 +0300 Subject: [PATCH 1958/2155] Fix an invalid section in example for NFTSet `NFTSet` is supposed to be in `Service` instead of `Unit` section. The current example leads to `Unknown key 'NFTSet' in section [Unit], ignoring` in systemd logs. --- man/systemd.resource-control.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index 58e923b618491..4e908f150ecd1 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1127,7 +1127,7 @@ BindNetworkInterface=vrf-mgmt systemctl daemon-reload can be used to refill the sets. Example: - [Unit] + [Service] NFTSet=cgroup:inet:filter:my_service user:inet:filter:serviceuser Corresponding NFT rules: From 577f2f975d09f10bade176cbdbb49ed414630489 Mon Sep 17 00:00:00 2001 From: Emanuele Rocca Date: Wed, 6 May 2026 15:43:58 +0000 Subject: [PATCH 1959/2155] include: add coredump_code to struct pidfd_info Linux v7.1 adds coredump_code to struct pidfd_info and defines a few new constants. Reflect the changes in include/override/sys/pidfd.h too. Stop including the libc version of sys/pidfd.h to be able to override the definition of pidfd_info. Signed-off-by: Emanuele Rocca --- src/include/override/sys/pidfd.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index 0e9b2f39989d7..28fb75698ed38 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -1,15 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - -/* since glibc-2.36. Only descend into the next on glibc — musl ships no such header, - * and musl-gcc's -idirafter /usr/include would otherwise pull in glibc's copy (which depends on - * __THROW and other glibc-isms) from the fallback path. */ -#if defined(__GLIBC__) && __has_include_next() -#include_next /* IWYU pragma: export */ -#endif - #include #include #include @@ -49,8 +40,14 @@ int pidfd_send_signal_shim(int fd, int sig, siginfo_t *info, unsigned flags); #define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */ #define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ +#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ +#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ +#define PIDFD_INFO_COREDUMP_CODE (1UL << 7) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ +#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */ +#define PIDFD_INFO_SIZE_VER3 88 /* sizeof fourth published struct */ /* * Values for @coredump_mask in pidfd_info. @@ -105,8 +102,13 @@ struct pidfd_info { __u32 fsuid; __u32 fsgid; __s32 exit_code; /* since kernel v6.15 (7477d7dce48a996ae4e4f0b5f7bd82de7ec9131b) */ - __u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */ - __u32 __spare1; + struct { /* coredump info */ + __u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */ + __u32 coredump_signal; /* since kernel v6.19 (036375522be8425874e9e0f907c7127e315c7a52) */ + __u32 coredump_code; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ + __u32 coredump_pad; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ + }; + __u64 supported_mask; /* Mask flags that this kernel supports */ }; #define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) From c8c1bcf1941047d1fe43d9827ad4826b4620297a Mon Sep 17 00:00:00 2001 From: Emanuele Rocca Date: Mon, 11 May 2026 13:13:20 +0000 Subject: [PATCH 1960/2155] signal-util: add signal_code_to_string Add signal_code_to_string() in signal-util.c and cover the si_code values defined in libc's siginfo-consts.h. Fall back to the numeric value when no symbolic name is known. Co-developed-by: Codex (GPT-5) Signed-off-by: Emanuele Rocca --- src/basic/signal-util.c | 148 ++++++++++++++++++++++++++++++++++ src/basic/signal-util.h | 1 + src/include/override/signal.h | 47 +++++++++++ src/test/test-signal-util.c | 11 +++ 4 files changed, 207 insertions(+) diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index ae7bfa6bd338e..aceafd35e2e14 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -223,6 +223,154 @@ int signal_from_string(const char *s) { return -EINVAL; } +static const char *const sigill_code_table[] = { + [ILL_ILLOPC] = "ILL_ILLOPC", + [ILL_ILLOPN] = "ILL_ILLOPN", + [ILL_ILLADR] = "ILL_ILLADR", + [ILL_ILLTRP] = "ILL_ILLTRP", + [ILL_PRVOPC] = "ILL_PRVOPC", + [ILL_PRVREG] = "ILL_PRVREG", + [ILL_COPROC] = "ILL_COPROC", + [ILL_BADSTK] = "ILL_BADSTK", + [ILL_BADIADDR] = "ILL_BADIADDR", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigill_code, int); + +static const char *const sigfpe_code_table[] = { + [FPE_INTDIV] = "FPE_INTDIV", + [FPE_INTOVF] = "FPE_INTOVF", + [FPE_FLTDIV] = "FPE_FLTDIV", + [FPE_FLTOVF] = "FPE_FLTOVF", + [FPE_FLTUND] = "FPE_FLTUND", + [FPE_FLTRES] = "FPE_FLTRES", + [FPE_FLTINV] = "FPE_FLTINV", + [FPE_FLTSUB] = "FPE_FLTSUB", + [FPE_FLTUNK] = "FPE_FLTUNK", + [FPE_CONDTRAP] = "FPE_CONDTRAP", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigfpe_code, int); + +static const char *const sigsegv_code_table[] = { + [SEGV_MAPERR] = "SEGV_MAPERR", + [SEGV_ACCERR] = "SEGV_ACCERR", + [SEGV_BNDERR] = "SEGV_BNDERR", + [SEGV_PKUERR] = "SEGV_PKUERR", + [SEGV_ACCADI] = "SEGV_ACCADI", + [SEGV_ADIDERR] = "SEGV_ADIDERR", + [SEGV_ADIPERR] = "SEGV_ADIPERR", + [SEGV_MTEAERR] = "SEGV_MTEAERR", + [SEGV_MTESERR] = "SEGV_MTESERR", + [SEGV_CPERR] = "SEGV_CPERR", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsegv_code, int); + +static const char *const sigbus_code_table[] = { + [BUS_ADRALN] = "BUS_ADRALN", + [BUS_ADRERR] = "BUS_ADRERR", + [BUS_OBJERR] = "BUS_OBJERR", + [BUS_MCEERR_AR] = "BUS_MCEERR_AR", + [BUS_MCEERR_AO] = "BUS_MCEERR_AO", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigbus_code, int); + +static const char *const sigtrap_code_table[] = { + [TRAP_BRKPT] = "TRAP_BRKPT", + [TRAP_TRACE] = "TRAP_TRACE", + [TRAP_BRANCH] = "TRAP_BRANCH", + [TRAP_HWBKPT] = "TRAP_HWBKPT", + [TRAP_UNK] = "TRAP_UNK", + [TRAP_PERF] = "TRAP_PERF", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigtrap_code, int); + +static const char *const sigsys_code_table[] = { + [SYS_SECCOMP] = "SYS_SECCOMP", + [SYS_USER_DISPATCH] = "SYS_USER_DISPATCH", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsys_code, int); + +/* sigchld_code_table is already defined in src/basic/process-util.c with + * decoded, lower-case values (CLD_EXITED -> "exited"). For consistency with + * all other values decoded here, provide an uppercase variant. */ +static const char *const sigchld_uppercase_code_table[] = { + [CLD_EXITED] = "CLD_EXITED", + [CLD_KILLED] = "CLD_KILLED", + [CLD_DUMPED] = "CLD_DUMPED", + [CLD_TRAPPED] = "CLD_TRAPPED", + [CLD_STOPPED] = "CLD_STOPPED", + [CLD_CONTINUED] = "CLD_CONTINUED", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigchld_uppercase_code, int); + +static const char *const sigpoll_code_table[] = { + [POLL_IN] = "POLL_IN", + [POLL_OUT] = "POLL_OUT", + [POLL_MSG] = "POLL_MSG", + [POLL_ERR] = "POLL_ERR", + [POLL_PRI] = "POLL_PRI", + [POLL_HUP] = "POLL_HUP", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigpoll_code, int); + +const char* signal_code_to_string(int signo, int code) { + switch (code) { + /* SI_KERNEL is 0x80 (128). Defining a table just for SI_KERNEL and + * SI_USER would be a waste of .rodata space: special case them instead. */ + case SI_KERNEL: + return "SI_KERNEL"; + case SI_USER: + return "SI_USER"; + /* The following values are all negatives. SI_ASYNCNL is -60. Similarly + * to the special case for SI_KERNEL above, avoid using a table for negative + * values as well. */ + case SI_ASYNCNL: + return "SI_ASYNCNL"; + case SI_DETHREAD: + return "SI_DETHREAD"; + case SI_TKILL: + return "SI_TKILL"; + case SI_SIGIO: + return "SI_SIGIO"; + case SI_ASYNCIO: + return "SI_ASYNCIO"; + case SI_MESGQ: + return "SI_MESGQ"; + case SI_TIMER: + return "SI_TIMER"; + case SI_QUEUE: + return "SI_QUEUE"; + } + + switch (signo) { + case SIGILL: + return sigill_code_to_string(code); + case SIGFPE: + return sigfpe_code_to_string(code); + case SIGSEGV: + return sigsegv_code_to_string(code); + case SIGBUS: + return sigbus_code_to_string(code); + case SIGTRAP: + return sigtrap_code_to_string(code); + case SIGCHLD: + return sigchld_uppercase_code_to_string(code); + case SIGPOLL: + return sigpoll_code_to_string(code); + case SIGSYS: + return sigsys_code_to_string(code); + default: + return NULL; + } +} + void nop_signal_handler(int sig) { /* nothing here */ } diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index c022da6295051..0c6aa6898d373 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -32,6 +32,7 @@ int sigprocmask_many_internal(int how, sigset_t *ret_old_mask, ...); #define sigprocmask_many(...) sigprocmask_many_internal(__VA_ARGS__, -1) DECLARE_STRING_TABLE_LOOKUP(signal, int); +const char* signal_code_to_string(int signo, int code) _const_; void nop_signal_handler(int sig); diff --git a/src/include/override/signal.h b/src/include/override/signal.h index 6e596adaccef2..64ddacc4c9dc0 100644 --- a/src/include/override/signal.h +++ b/src/include/override/signal.h @@ -3,5 +3,52 @@ #include_next /* IWYU pragma: export */ +#ifndef ILL_BADIADDR +#define ILL_BADIADDR 9 +#endif + +#ifndef FPE_FLTUNK +#define FPE_FLTUNK 14 +#endif + +#ifndef FPE_CONDTRAP +#define FPE_CONDTRAP 15 +#endif + +#ifndef SEGV_ACCADI +#define SEGV_ACCADI 5 +#endif + +#ifndef SEGV_ADIDERR +#define SEGV_ADIDERR 6 +#endif + +#ifndef SEGV_ADIPERR +#define SEGV_ADIPERR 7 +#endif + +/* Defined since glibc-2.39. */ +#ifndef SEGV_CPERR +#define SEGV_CPERR 10 +#endif + +#ifndef SI_DETHREAD +#define SI_DETHREAD -7 +#endif + +/* Defined since glibc-2.43. */ +#ifndef TRAP_PERF +#define TRAP_PERF 6 +#endif + +/* Defined since glibc-2.33. */ +#ifndef SYS_SECCOMP +#define SYS_SECCOMP 1 +#endif + +#ifndef SYS_USER_DISPATCH +#define SYS_USER_DISPATCH 2 +#endif + int rt_tgsigqueueinfo_shim(pid_t tgid, pid_t tid, int sig, siginfo_t *info); #define rt_tgsigqueueinfo rt_tgsigqueueinfo_shim diff --git a/src/test/test-signal-util.c b/src/test/test-signal-util.c index 822556022d9f5..541ff8c7fcabe 100644 --- a/src/test/test-signal-util.c +++ b/src/test/test-signal-util.c @@ -103,6 +103,17 @@ TEST(signal_from_string) { test_signal_from_string_number("-2", -ERANGE); } +TEST(signal_code_to_string) { + assert_se(streq_ptr(signal_code_to_string(SIGSEGV, SEGV_MAPERR), "SEGV_MAPERR")); + assert_se(streq_ptr(signal_code_to_string(SIGILL, ILL_ILLOPC), "ILL_ILLOPC")); + assert_se(streq_ptr(signal_code_to_string(SIGCHLD, CLD_CONTINUED), "CLD_CONTINUED")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_TKILL), "SI_TKILL")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_USER), "SI_USER")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_KERNEL), "SI_KERNEL")); + assert_se(streq_ptr(signal_code_to_string(SIGSYS, SYS_SECCOMP), "SYS_SECCOMP")); + assert_se(signal_code_to_string(SIGABRT, 99) == NULL); +} + TEST(block_signals) { assert_se(signal_is_blocked(SIGUSR1) == 0); assert_se(signal_is_blocked(SIGALRM) == 0); From ed143d8623086ebc835679c7f29ae3ff7a69cde3 Mon Sep 17 00:00:00 2001 From: Emanuele Rocca Date: Wed, 6 May 2026 15:46:21 +0000 Subject: [PATCH 1961/2155] coredump: add COREDUMP_CODE field for signal reason Introduce COREDUMP_CODE as a new captured field alongside the existing COREDUMP_SIGNAL. While COREDUMP_SIGNAL identifies the signal number that terminated the process, COREDUMP_CODE provides the reason the signal was sent. For example, a process terminated by SIGSEGV due to invalid permissions would produce COREDUMP_SIGNAL=11 and COREDUMP_CODE=2 (SEGV_ACCERR). The kernel exposes coredump_code via pidfd starting with v7.1: https://git.kernel.org/torvalds/c/701f7f4fbabbf4989ba6fbf033b160dd943221d5 System administrators can find both the signal and code in coredumpctl info: $ coredumpctl info | grep Signal: Signal: 11 (SEGV) si_code: SEGV_MAPERR Signed-off-by: Emanuele Rocca --- man/systemd-coredump.xml | 14 ++++++ src/coredump/coredump-context.c | 47 +++++++++++++++++++++ src/coredump/coredump-context.h | 3 ++ src/coredump/coredumpctl.c | 29 +++++++++++-- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 6 +++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml index d499600dc15d9..c27981bd89b5f 100644 --- a/man/systemd-coredump.xml +++ b/man/systemd-coredump.xml @@ -201,6 +201,7 @@ COREDUMP_UID=1000 COREDUMP_GID=1000 COREDUMP_SIGNAL_NAME=SIGSEGV COREDUMP_SIGNAL=11 +COREDUMP_CODE=2 COREDUMP_TIMESTAMP=1614342930000000 COREDUMP_COMM=Web Content COREDUMP_EXE=/usr/lib64/firefox/firefox @@ -327,6 +328,19 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst + + COREDUMP_CODE= + + The reason why the signal was sent as a numerical value. The code is defined as + field si_code in the siginfo_t structure and + documented in sigaction2. + + + + + + COREDUMP_CWD= COREDUMP_ROOT= diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 6cacae4eff1ae..24d273499a3c6 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -14,6 +14,7 @@ #include "memstream-util.h" #include "namespace-util.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "signal-util.h" #include "special.h" @@ -38,6 +39,7 @@ static const char * const metadata_field_table[_META_MAX] = { [META_UNIT] = "COREDUMP_UNIT=", [META_PROC_AUXV] = "COREDUMP_PROC_AUXV=", [META_THREAD_NAME] = "COREDUMP_THREAD_NAME=", + [META_CODE] = "COREDUMP_CODE=", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField); @@ -176,6 +178,34 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) return 1; } +/* The kernel passes si_signo through core_pattern (%s). Starting with v7.1, + * si_code is reported as well via pidfd. */ +static int coredump_context_read_pidfd_info(CoredumpContext *context) { + struct pidfd_info info = { + .mask = PIDFD_INFO_COREDUMP, + }; + int r; + + assert(context); + assert(pidref_is_set(&context->pidref)); + + if (!context->got_pidfd || context->pidref.fd < 0) + return 0; + + r = pidfd_get_info(context->pidref.fd, &info); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return log_debug_errno(r, "PIDFD_INFO_COREDUMP not supported, ignoring: %m"); + if (r < 0) + return log_debug_errno(r, "Failed to get pidfd coredump info, ignoring: %m"); + + if (FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP_CODE)) { + context->code = (int) info.coredump_code; + context->got_code = true; + } + + return 0; +} + int coredump_context_build_iovw(CoredumpContext *context) { char *t; int r; @@ -213,6 +243,10 @@ int coredump_context_build_iovw(CoredumpContext *context) { return log_error_errno(r, "Failed to add COREDUMP_SIGNAL= field: %m"); (void) iovw_put_string_field(&context->iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo)); + + /* Emit si_code if we learned it from pidfd_info */ + if (context->got_code) + (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_CODE=", "%i", context->code); } r = iovw_put_string_fieldf(&context->iovw, "COREDUMP_TIMESTAMP=", USEC_FMT, context->timestamp); @@ -367,6 +401,8 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) { if (r < 0) log_warning_errno(r, "Failed to get auxv, ignoring: %m"); + (void) coredump_context_read_pidfd_info(context); + r = pidref_verify(&context->pidref); if (r < 0) return log_error_errno(r, "PIDFD validation failed: %m"); @@ -499,6 +535,17 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool case META_THREAD_NAME: return free_and_strdup_warn(&context->thread_name, s); + case META_CODE: + /* We must accept both positive and negative values. The former + * are reserved for kernel-generated signals, the latter for signals requested + * from userspace. See /usr/include/bits/siginfo-consts.h. */ + r = safe_atoi(s, &context->code); + if (r < 0) + log_warning_errno(r, "Failed to parse code \"%s\", ignoring: %m", s); + else + context->got_code = true; + return 0; + 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 7fedfde2adbf7..a2ce16b8531cb 100644 --- a/src/coredump/coredump-context.h +++ b/src/coredump/coredump-context.h @@ -42,6 +42,7 @@ typedef enum MetadataField { META_UNIT, META_PROC_AUXV, META_THREAD_NAME, + META_CODE, /* code of signal causing dump (eg: 2 for SEGV_ACCERR). Since v7.1. */ _META_MAX, _META_INVALID = -EINVAL, } MetadataField; @@ -51,6 +52,7 @@ struct CoredumpContext { uid_t uid; /* META_ARGV_UID */ gid_t gid; /* META_ARGV_GID */ int signo; /* META_ARGV_SIGNAL */ + int code; /* META_CODE */ usec_t timestamp; /* META_ARGV_TIMESTAMP */ uint64_t rlimit; /* META_ARGV_RLIMIT */ char *hostname; /* META_ARGV_HOSTNAME */ @@ -63,6 +65,7 @@ struct CoredumpContext { size_t auxv_size; /* META_PROC_AUXV */ char *thread_name; /* META_THREAD_NAME */ bool got_pidfd; /* META_ARGV_PIDFD */ + bool got_code; /* META_CODE */ bool same_pidns; bool forwarded; int input_fd; diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 3b31471ab4dc7..9b26e23d62930 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -585,6 +585,7 @@ typedef enum CoredumpField { COREDUMP_FIELD_UID, COREDUMP_FIELD_GID, COREDUMP_FIELD_SGNL, + COREDUMP_FIELD_CODE, COREDUMP_FIELD_EXE, COREDUMP_FIELD_COMM, COREDUMP_FIELD_CMDLINE, @@ -615,6 +616,7 @@ static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = { [COREDUMP_FIELD_UID] = "COREDUMP_UID", [COREDUMP_FIELD_GID] = "COREDUMP_GID", [COREDUMP_FIELD_SGNL] = "COREDUMP_SIGNAL", + [COREDUMP_FIELD_CODE] = "COREDUMP_CODE", [COREDUMP_FIELD_EXE] = "COREDUMP_EXE", [COREDUMP_FIELD_COMM] = "COREDUMP_COMM", [COREDUMP_FIELD_CMDLINE] = "COREDUMP_CMDLINE", @@ -770,9 +772,23 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { int sig; const char *name = f.normal_coredump ? "Signal" : "Reason"; - if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0) - fprintf(file, " %s: %s (%s)\n", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig)); - else + if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0) { + fprintf(file, " %s: %s (%s)", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig)); + + if (f.fields[COREDUMP_FIELD_CODE]) { + int n; + const char *s; + + if (safe_atoi(f.fields[COREDUMP_FIELD_CODE], &n) >= 0) + s = signal_code_to_string(sig, n); + else + s = NULL; + + fprintf(file, " si_code: %s", s ?: f.fields[COREDUMP_FIELD_CODE]); + } + + fputc('\n', file); + } else fprintf(file, " %s: %s\n", name, f.fields[COREDUMP_FIELD_SGNL]); } @@ -893,7 +909,8 @@ static int print_info_json(FILE *file, sd_journal *j) { pid_t pid_as_int = 0, tid_as_int = 0; uid_t uid_as_int = UID_INVALID, owner_uid_as_int = UID_INVALID; gid_t gid_as_int = GID_INVALID; - int sig_as_int = 0; + int sig_as_int = 0, code_as_int = 0; + bool code_is_valid = false; usec_t ts = USEC_INFINITY; int r; @@ -916,6 +933,8 @@ static int print_info_json(FILE *file, sd_journal *j) { (void) parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &owner_uid_as_int); if (f.normal_coredump && f.fields[COREDUMP_FIELD_SGNL]) (void) safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig_as_int); + if (f.normal_coredump && f.fields[COREDUMP_FIELD_CODE]) + code_is_valid = safe_atoi(f.fields[COREDUMP_FIELD_CODE], &code_as_int) >= 0; if (f.fields[COREDUMP_FIELD_TIMESTAMP]) (void) safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &ts); @@ -932,6 +951,8 @@ static int print_info_json(FILE *file, sd_journal *j) { SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0, "Signal", SD_JSON_BUILD_INTEGER(sig_as_int)), SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0 && !!signal_to_string(sig_as_int), "SignalName", SD_JSON_BUILD_STRING(signal_to_string(sig_as_int))), SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int <= 0 && !!f.fields[COREDUMP_FIELD_SGNL], "Signal", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && code_is_valid, "SignalCode", SD_JSON_BUILD_INTEGER(code_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && !code_is_valid && !!f.fields[COREDUMP_FIELD_CODE], "SignalCode", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CODE])), SD_JSON_BUILD_PAIR_CONDITION(!f.normal_coredump && !!f.fields[COREDUMP_FIELD_SGNL], "Reason", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), SD_JSON_BUILD_PAIR_CONDITION(ts != USEC_INFINITY, "Timestamp", SD_JSON_BUILD_UNSIGNED(ts)), SD_JSON_BUILD_PAIR_CONDITION(ts == USEC_INFINITY && !!f.fields[COREDUMP_FIELD_TIMESTAMP], "Timestamp", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TIMESTAMP])), diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index 30252132ff85f..675d37184b571 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -166,6 +166,12 @@ coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null # Check the field is queryable in the journal coredumpctl -F COREDUMP_TID +# If COREDUMP_CODE= is present, check that the expected code is SI_USER (0). +if coredumpctl -F COREDUMP_CODE | grep "^0$" >/dev/null; then + coredumpctl info "$CORE_TEST_BIN" | grep --fixed-strings "Signal: 5 (TRAP) si_code: SI_USER" >/dev/null + coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'any(.[]; .SignalCode == 0)' +fi + 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 7416bdf8f1c71284805409a0b78a4d4abf1616df Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:00:07 -0700 Subject: [PATCH 1962/2155] shared: extend Job varlink type with Unit and ActivationDetails fields Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-job.c | 49 +++++++++++++++++++++++++++++ src/core/varlink-job.h | 6 ++++ src/core/varlink-unit.c | 13 +------- src/shared/varlink-io.systemd.Job.c | 25 +++++++++------ 5 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 src/core/varlink-job.c create mode 100644 src/core/varlink-job.h diff --git a/src/core/meson.build b/src/core/meson.build index 98cf02aef8879..a0ff0baf8dcd4 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -70,6 +70,7 @@ libcore_sources = files( 'varlink-common.c', 'varlink-dynamic-user.c', 'varlink-execute.c', + 'varlink-job.c', 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', diff --git a/src/core/varlink-job.c b/src/core/varlink-job.c new file mode 100644 index 0000000000000..46b9a4cbd928f --- /dev/null +++ b/src/core/varlink-job.c @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "job.h" +#include "json-util.h" +#include "strv.h" +#include "unit.h" +#include "varlink-job.h" + +static int activation_details_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **pairs = NULL; + Job *j = ASSERT_PTR(userdata); + int r; + + assert(ret); + + r = activation_details_append_pair(j->activation_details, &pairs); + if (r < 0) + return log_debug_errno(r, "Failed to get activation details: %m"); + if (r == 0) { + *ret = NULL; + return 0; + } + + STRV_FOREACH_PAIR(key, value, pairs) { + r = sd_json_variant_set_field_string(&v, *key, *value); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Job *j = ASSERT_PTR(userdata); + + /* "Unit" is omitted in StartTransient streaming notifications where the caller already knows the unit. */ + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", j->unit ? j->unit->id : NULL), + JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(j->state)), + JSON_BUILD_PAIR_ENUM_NON_EMPTY("Result", job_result_to_string(j->result)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, j)); +} diff --git a/src/core/varlink-job.h b/src/core/varlink-job.h new file mode 100644 index 0000000000000..cd661daca62ac --- /dev/null +++ b/src/core/varlink-job.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int job_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 31f7898a8cf04..4bf8136fa9164 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -27,6 +27,7 @@ #include "varlink-cgroup.h" #include "varlink-common.h" #include "varlink-execute.h" +#include "varlink-job.h" #include "varlink-kill.h" #include "varlink-mount.h" #include "varlink-path.h" @@ -617,18 +618,6 @@ void varlink_unit_send_change_signal(Unit *u) { SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u)); } -static int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { - Job *j = ASSERT_PTR(userdata); - - /* Note that "Result" is suppressed until the job reaches JOB_FINISHED. */ - return sd_json_buildo( - ASSERT_PTR(ret), - SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), - JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), - JSON_BUILD_PAIR_ENUM("State", job_state_to_string(j->state)), - JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("Result", job_result_to_string(j->result))); -} - void varlink_job_send_change_signal(Job *j) { assert(j); diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index 8dfc403c7be81..17b06a7de5865 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -39,17 +39,24 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(frozen), SD_VARLINK_DEFINE_ENUM_VALUE(concurrency)); -/* Field names match the D-Bus Job properties (Id, JobType, State) */ +/* Field names match the D-Bus Job properties (Id, Unit, JobType, State) */ +#define VARLINK_DEFINE_JOB_FIELDS(direction) \ + SD_VARLINK_FIELD_COMMENT("The numeric job ID"), \ + SD_VARLINK_DEFINE_##direction(Id, SD_VARLINK_INT, 0), \ + SD_VARLINK_FIELD_COMMENT("The unit name this job operates on"), \ + SD_VARLINK_DEFINE_##direction(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("The job type"), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(JobType, JobType, 0), \ + SD_VARLINK_FIELD_COMMENT("Current job state. 'finished' indicates the job has completed; in that case Result is also set."), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Job result. Only set once the job has reached the 'finished' state."), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Activation details describing what triggered this job, as key-value pairs"), \ + SD_VARLINK_DEFINE_##direction(ActivationDetails, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_MAP) + SD_VARLINK_DEFINE_STRUCT_TYPE( Job, - SD_VARLINK_FIELD_COMMENT("The numeric job ID"), - SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0), - SD_VARLINK_FIELD_COMMENT("The job type"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0), - SD_VARLINK_FIELD_COMMENT("Current job state. 'finished' indicates the job has completed; in that case Result is also set."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Job result. Only set once the job has reached the 'finished' state."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE)); + VARLINK_DEFINE_JOB_FIELDS(FIELD)); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Job, From 3941dff8b4d342aff9ac7ed69a94c18e21657ca4 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:01:27 -0700 Subject: [PATCH 1963/2155] core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods Co-developed-by: Claude Opus 4.6 --- src/core/varlink-job.c | 203 ++++++++++++++++++++++++++++ src/core/varlink-job.h | 6 + src/core/varlink.c | 6 + src/shared/varlink-io.systemd.Job.c | 33 ++++- 4 files changed, 246 insertions(+), 2 deletions(-) diff --git a/src/core/varlink-job.c b/src/core/varlink-job.c index 46b9a4cbd928f..a0b45fa24ceb1 100644 --- a/src/core/varlink-job.c +++ b/src/core/varlink-job.c @@ -2,8 +2,12 @@ #include "sd-varlink.h" +#include "bus-polkit.h" #include "job.h" #include "json-util.h" +#include "locale-util.h" +#include "manager.h" +#include "selinux-access.h" #include "strv.h" #include "unit.h" #include "varlink-job.h" @@ -47,3 +51,202 @@ int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { JSON_BUILD_PAIR_ENUM_NON_EMPTY("Result", job_result_to_string(j->result)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, j)); } + +static int varlink_error_no_such_job(sd_varlink *link, const char *name) { + return sd_varlink_errorbo( + ASSERT_PTR(link), + VARLINK_ERROR_JOB_NO_SUCH_JOB, + JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); +} + +static int list_job_one(sd_varlink *link, Job *job) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(job); + + r = job_build_json(&v, /* name= */ NULL, job); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +static int list_job_one_with_selinux_access_check(sd_varlink *link, Job *job) { + int r; + + assert(link); + assert(job); + assert(job->unit); + + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + /* If mac_selinux_unit_access_check_varlink() returned an error, + * it means that SELinux enforce is on. It also does all the logging(). */ + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + return list_job_one(link, job); +} + +typedef struct JobLookupParameters { + uint32_t id; + const char *unit; +} JobLookupParameters; + +static int lookup_job_by_parameters( + sd_varlink *link, + Manager *manager, + JobLookupParameters *p, + Job **ret) { + + /* The function can return ret=NULL if no lookup parameters provided */ + Job *job = NULL; + + assert(link); + assert(manager); + assert(p); + assert(ret); + + if (p->id > 0) { + job = manager_get_job(manager, p->id); + if (!job) + return varlink_error_no_such_job(link, "id"); + } + + if (p->unit) { + Unit *u = manager_get_unit(manager, p->unit); + if (!u || !u->job) + return varlink_error_no_such_job(link, "unit"); + if (job && u->job != job) { + log_debug("Job lookup by parameters id=%u unit='%s' resulted in different jobs.", p->id, p->unit); + return varlink_error_no_such_job(link, /* name= */ NULL); + } + + job = u->job; + } + + *ret = job; + return !!job; +} + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, offsetof(JobLookupParameters, id), 0 }, + { "unit", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(JobLookupParameters, unit), 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + JobLookupParameters p = {}; + Job *job; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = lookup_job_by_parameters(link, manager, &p, &job); + if (r < 0) + return r; + if (r > 0) + return list_job_one_with_selinux_access_check(link, job); + + /* List all jobs */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_JOB_NO_SUCH_JOB); + if (r < 0) + return r; + + HASHMAP_FOREACH(job, manager->jobs) { + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + continue; + + r = list_job_one(link, job); + if (r < 0) + return r; + } + + return 0; +} + +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, 0, SD_JSON_MANDATORY }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + uint32_t id = 0; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &id); + if (r != 0) + return r; + + Job *j = manager_get_job(manager, id); + if (!j) + return varlink_error_no_such_job(link, "id"); + + r = mac_selinux_unit_access_check_varlink(j->unit, link, "stop"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", j->unit ? j->unit->id : NULL, + "verb", "cancel", + "polkit.message", N_("Authentication is required to cancel job for unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + job_finish_and_invalidate(j, JOB_CANCELED, /* recursive= */ true, /* already= */ false); + + return sd_varlink_reply(link, NULL); +} + +int vl_method_clear_all_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, "reload"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "verb", "clear-jobs", + "polkit.message", N_("Authentication is required to clear all pending jobs."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + manager_clear_jobs(manager); + + return sd_varlink_reply(link, NULL); +} diff --git a/src/core/varlink-job.h b/src/core/varlink-job.h index cd661daca62ac..6393d4318af7b 100644 --- a/src/core/varlink-job.h +++ b/src/core/varlink-job.h @@ -3,4 +3,10 @@ #include "core-forward.h" +#define VARLINK_ERROR_JOB_NO_SUCH_JOB "io.systemd.Job.NoSuchJob" + int job_build_json(sd_json_variant **ret, const char *name, void *userdata); + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_clear_all_jobs(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 09817b6dce2c4..c09c6ad486f25 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -14,11 +14,13 @@ #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.Unit.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-io.systemd.service.h" +#include "varlink-job.h" #include "varlink-manager.h" #include "varlink-metrics.h" #include "varlink-serialize.h" @@ -398,6 +400,7 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_add_interface_many( s, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Manager, &vl_interface_io_systemd_Unit, &vl_interface_io_systemd_service); @@ -406,6 +409,9 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_bind_method_many( s, + "io.systemd.Job.List", vl_method_list_jobs, + "io.systemd.Job.Cancel", vl_method_cancel_job, + "io.systemd.Job.ClearAll", vl_method_clear_all_jobs, "io.systemd.Manager.Describe", vl_method_describe_manager, "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index 17b06a7de5865..b31e476511bb0 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -58,10 +58,37 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( Job, VARLINK_DEFINE_JOB_FIELDS(FIELD)); +static SD_VARLINK_DEFINE_ERROR( + NoSuchJob, + SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, filter by job ID"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, filter by unit name"), + SD_VARLINK_DEFINE_INPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_JOB_FIELDS(OUTPUT)); + +static SD_VARLINK_DEFINE_METHOD( + Cancel, + SD_VARLINK_FIELD_COMMENT("The job ID to cancel"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, 0)); + +static SD_VARLINK_DEFINE_METHOD( + ClearAll); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Job, "io.systemd.Job", - SD_VARLINK_INTERFACE_COMMENT("Job-related types for the systemd service manager."), + SD_VARLINK_INTERFACE_COMMENT("Job management interface for the systemd service manager."), + SD_VARLINK_SYMBOL_COMMENT("List queued jobs"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Cancel a specific job"), + &vl_method_Cancel, + SD_VARLINK_SYMBOL_COMMENT("Cancel all pending jobs"), + &vl_method_ClearAll, SD_VARLINK_SYMBOL_COMMENT("Job type"), &vl_type_JobType, SD_VARLINK_SYMBOL_COMMENT("Job state"), @@ -69,4 +96,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Job result"), &vl_type_JobResult, SD_VARLINK_SYMBOL_COMMENT("A job object"), - &vl_type_Job); + &vl_type_Job, + SD_VARLINK_SYMBOL_COMMENT("The specified job does not exist"), + &vl_error_NoSuchJob); From f287d85a0ac7827f4863e3b2221f1254e3f7a55a Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:01:43 -0700 Subject: [PATCH 1964/2155] test: split TEST-74-AUX-UTILS.varlinkctl.sh into per-interface subtests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the monolithic varlinkctl test script into separate files per varlink interface for better organization and easier maintenance: - varlinkctl.sh: core varlinkctl tool tests (CLI, transports, socket discovery, upgrade/serve) and io.systemd.Manager - varlinkctl-network.sh: io.systemd.Network - varlinkctl-unit.sh: io.systemd.Unit (system + user manager) - varlinkctl-metrics.sh: io.systemd.Metrics No functional changes — the test content is moved as-is. Co-developed-by: Claude Opus 4.6 --- .../TEST-74-AUX-UTILS.varlinkctl-metrics.sh | 16 ++++ .../TEST-74-AUX-UTILS.varlinkctl-network.sh | 9 ++ .../TEST-74-AUX-UTILS.varlinkctl-unit.sh | 83 ++++++++++++++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 95 ------------------- 4 files changed, 108 insertions(+), 95 deletions(-) create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh new file mode 100755 index 0000000000000..49d3373a7cbbd --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Metrics +varlinkctl info /run/systemd/report/io.systemd.Manager + +varlinkctl list-methods /run/systemd/report/io.systemd.Manager +varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl introspect /run/systemd/report/io.systemd.Manager +varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List '{}' +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh new file mode 100755 index 0000000000000..cae3d7a66e123 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Network +varlinkctl info /run/systemd/netif/io.systemd.Network +varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network +varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh new file mode 100755 index 0000000000000..2311e14a14588 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Unit +varlinkctl info /run/systemd/io.systemd.Manager +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') +set +o pipefail +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" +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 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 // empty' | tail -n 1) +test -n "$automount_id" +# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that +# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. +automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | 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 // empty' | tail -n 1) +test -n "$mount_id" +mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' +# test for PathContext/Runtime +path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$path_id" +path_params=$(jq -cn --arg name "$path_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' +# test for ServiceContext/Runtime +service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$service_id" +service_params=$(jq -cn --arg name "$service_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' +# test for ScopeContext/Runtime +scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$scope_id" +scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' +# test for SocketContext/Runtime +socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$socket_id" +socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' +# test for SwapContext/Runtime (swap units may not be present on all systems) +swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +if test -n "$swap_id"; then + swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' +fi +# test for TimerContext/Runtime +timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$timer_id" +timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' + +# test io.systemd.Unit in user manager +testuser_uid=$(id -u testuser) +systemd-run --wait --pipe --user --machine testuser@ \ + varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index bbc219decbfad..0836883f24cd1 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -205,97 +205,6 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' # This will disconnect and fail, as the manager reexec and drops connections varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: -# test io.systemd.Network -varlinkctl info /run/systemd/netif/io.systemd.Network -varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network -varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' - -# test io.systemd.Unit -varlinkctl info /run/systemd/io.systemd.Manager -varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit -varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') -set +o pipefail -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" -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 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 // empty' | tail -n 1) -test -n "$automount_id" -# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that -# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. -automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | 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 // empty' | tail -n 1) -test -n "$mount_id" -mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' -# test for PathContext/Runtime -path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$path_id" -path_params=$(jq -cn --arg name "$path_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' -# test for ServiceContext/Runtime -service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$service_id" -service_params=$(jq -cn --arg name "$service_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' -# test for ScopeContext/Runtime -scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$scope_id" -scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' -# test for SocketContext/Runtime -socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$socket_id" -socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' -# test for SwapContext/Runtime (swap units may not be present on all systems) -swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -if test -n "$swap_id"; then - swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') - varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' - varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' -fi -# test for TimerContext/Runtime -timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$timer_id" -timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' - -# test io.systemd.Metrics -varlinkctl info /run/systemd/report/io.systemd.Manager - -varlinkctl list-methods /run/systemd/report/io.systemd.Manager -varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl introspect /run/systemd/report/io.systemd.Manager -varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {} -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe {} - # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) systemd-run --wait --pipe --user --machine testuser@ \ @@ -305,10 +214,6 @@ systemd-run --wait --pipe --user --machine testuser@ \ systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Manager.Describe '{}' -# test io.systemd.Unit in user manager -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 From e86b49473ffe3eba66b02011e2fb825248784a41 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 18 May 2026 00:45:23 -0700 Subject: [PATCH 1965/2155] test: wait systemd to finish reexec in TEST-74-AUX-UTILS.varlinkctl.sh --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 0836883f24cd1..ec71e2641e0d5 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -204,6 +204,14 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Describe '{}' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' # This will disconnect and fail, as the manager reexec and drops connections varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: +# Wait for the manager to finish re-exec before proceeding — the user manager +# tests below use systemd-run which requires a functional PID 1. +for _ in {1..10}; do + if systemctl is-system-running 2>/dev/null | grep -qE 'running|degraded'; then + break + fi + sleep 1 +done # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) From bf867054e96daf0f8bab035c6501b1cad06e05fd Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:02:15 -0700 Subject: [PATCH 1966/2155] test: add integration tests for io.systemd.Job varlink methods Co-developed-by: Claude Opus 4.6 --- .../units/TEST-74-AUX-UTILS.varlinkctl-job.sh | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh new file mode 100755 index 0000000000000..032904b4e559c --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Job +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Job + +# List with no jobs pending — should return empty with --more +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}' --graceful=io.systemd.Job.NoSuchJob + +# Without --more and no filter, must fail (streaming required) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}') + +# Error cases: non-existent job ID, non-existent unit +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 999999}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": "non-existent.service"}') + +# Invalid inputs +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 0}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": ""}') + +at_exit() { + systemctl stop varlink-test-job.service 2>/dev/null || true + rm -f /run/systemd/system/varlink-test-job.service + systemctl daemon-reload +} +trap at_exit EXIT + +# Create a job by starting a slow service, then test List/Cancel +cat >/run/systemd/system/varlink-test-job.service < Date: Tue, 19 May 2026 13:04:18 +0200 Subject: [PATCH 1967/2155] update TODO --- NEWS | 464 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 271 insertions(+), 193 deletions(-) diff --git a/NEWS b/NEWS index 3bb7156e123e5..c8478c0ba63ae 100644 --- a/NEWS +++ b/NEWS @@ -23,9 +23,8 @@ CHANGES WITH 261 in spe: 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. + an argument) has been repurposed as a standalone switch 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 @@ -38,13 +37,13 @@ CHANGES WITH 261 in spe: 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, - 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. + it measures to the TPM also 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 for CC have now been + added; however, this changes the expected register values. This + may need to be reflected in the attestation environments which use + hardware CC registers (in place of TPM quotes). * systemd-nspawn gained a new --restrict-address-families= option (and corresponding RestrictAddressFamilies= setting in .nspawn files) to @@ -54,11 +53,26 @@ CHANGES WITH 261 in spe: AF_UNIX. * The experimental "systemd-sysupdated" D-Bus API is going to be - removed again in the next release. The plan is that in its place + removed in the next release. The plan is that in its place clients should directly talk to systemd-sysupdate (i.e. the backend of "systemd-sysupdated") via Varlink IPC. The "updatectl" tool will be reworked along these lines. + * A new service unit "systemd-pcrosseparator.service" will now measure + a new separator measurement during early userspace into PCRs 0-7, 9, + 12-14, in order to isolate firmware/pre-boot measurements from host + measurements. This is a safety concept to protect firmware + measurements on systems where the regular firmware separator + measurement is missing. It's also useful in environments where a + software TPM is used, i.e. where TPM functionality is only available + starting with the OS, but not before. Note that this new measurement + has an effect on all indicated PCRs, hence might affect relevant TPM + policies. + + * Support for udev's old database version 0 has been removed. This + effectively means live upgrades from versions older than v247 are not + supported anymore. + Changes in the system and service manager: * PID1 now supports the kernel's Live Update Orchestration (LUO) / @@ -67,7 +81,7 @@ CHANGES WITH 261 in spe: stashed (named) file descriptors after kexec, if the kernel supports the FD type (at the time of writing only memfds are supported). Units can also create their own LUO Sessions by talking to the kernel - directly, and store them in their FD Stores, and those will be also + directly, and store them in their FD Stores, and those will also be preserved and passed down to the unit after kexec. Units must set 'FileDescriptorStorePreserve=yes' in order to enable this feature. @@ -79,65 +93,22 @@ CHANGES WITH 261 in spe: support, this lets user units persist state (e.g.: memfds) across not only user session restarts, but also kexec reboots. - * The hardware database now contains a new database hwdb.d/40-imds.hwdb - that recognizes various established public clouds by their SMBIOS - information, and provides information how to reach local IMDS - functionality on the node. Currently, Amazon EC2, Microsoft Azure, - Google Compute Engine, Hetzner, Oracle Cloud, Scaleway are - recognized. - - * An IMDS subsystem has been added. Specifically, there's now - systemd-imdsd which provides a local Varlink IPC API that makes IMDS - services accessible locally. It provides both a relatively low-level - interface for querying arbitrary fields, and a higher level interface - for querying certain well-known keys in a generic way (which maps to - various cloud specific keys via the hwdb). The service can be pulled - into the boot transaction automatically if a supported cloud is - recognized via the systemd-imds-generator functionality. This permits - implementation of truly generic images, that can interact with IMDS - if available, but operate without if not. A tool systemd-imds acts as - a client to systemd-imdsd and imports various IMDS provided fields - into local system credentials, which can then be consumed by later - services. The acquired IMDS is measured before being imported. - - * 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. - * The manager exposes a new ReloadCount property on its D-Bus and Varlink interfaces (org.freedesktop.systemd1.Manager and io.systemd.Manager respectively). The counter increments after each successfully completed daemon-reload, and it is reset on daemon-reexec. - * A new ConditionSecurity=measured-os unit condition has been added - that checks whether the system was booted with measured-boot - semantics (i.e. via systemd-stub or an equivalent verified-boot - mechanism that measured the OS to the TPM). This is very similar to - the pre-existing ConditionSecurity=measured-uki however is a more - generic as it can also cover environments where the firmware/UKI does - not have a TPM but the OS has (which is for example the case if the - TPM is implemented purely in software). - * A new unit setting CPUSetPartition= has been added that allows configuring the cpuset cgroup partition type (e.g. "root", "isolated", "member") for a service. - * Two new optional sd_notify() messages have been introduced that - allow services to be notified of I/O and CPU pressure events from - PSI (Pressure Stall Information). The system manager forwards - pressure events for the corresponding cgroup. - * A new RestrictFileSystemAccess= setting has been added that uses a BPF LSM program to restrict execution to only binaries that are stored on a signed and verified dm-verity protected filesystem. - * The io.systemd.Unit.StartTransient() Varlink method has been extended - to accept SetCredentials, SetCredentialsEncrypted, Environment and - WorkingDirectory fields, on par with what is already possible via - the legacy D-Bus interface. + * The io.systemd.Unit.StartTransient() Varlink method has been added + for invoking service units transiently. * A new set of Varlink methods has been added to the io.systemd.Manager interface to request system shutdown: @@ -154,11 +125,85 @@ CHANGES WITH 261 in spe: * A new io.systemd.Job Varlink interface exposes information about pending and running manager jobs. - Changes in systemd-tmpfiles, systemd-sysusers and similar early-boot - tools: + * The service manager knows two new global knobs + EventLoopRateLimitIntervalSec=/EventLoopRateLimitBurst= to configure + PID1's event loop ratelimit logic. This permits fine-tuning the + safety logic in PID 1 that slows down operation in case PID 1 starts + to busy loop. + + * The service manager gained new per-unit settings + CPUPressureWatch=/CPUPressureThresholdSec=/IOPressureWatch=/IOPressureThresholdSec= + which enable services to get generic notifications on CPU or IO + pressure events. + + * A new global service manager knob MinimumUptimeSec= has been added + that defines a minimum uptime for the system. It defaults to 15s. If + the system is shut down more quickly than the specified time a delay + is inserted in the last part of shutdown, in order to avoid tight + boot loops. + + IMDS (Cloud "Instance Metadata Service") Subsystem: + + * The hardware database now contains a new database hwdb.d/40-imds.hwdb + that recognizes various established public clouds by their SMBIOS + information, and provides information on how to reach local IMDS + functionality on the node. Currently, Amazon EC2, Microsoft Azure, + Google Compute Engine, Hetzner, Oracle Cloud, Scaleway, Tencent + Cloud, and Alibaba ECS are recognized. + + * An IMDS subsystem has been added. Specifically, there's now + systemd-imdsd which provides a local Varlink IPC API that makes IMDS + services accessible to local programs. It provides both a relatively + low-level interface for querying arbitrary fields, and a higher level + interface for querying certain well-known keys in a generic way + (which maps to various cloud-specific keys via the hwdb). The service + can be pulled into the boot transaction automatically if a supported + cloud is recognized via the systemd-imds-generator + functionality. This permits implementation of truly generic images + that can interact with IMDS if available, but operate without if + not. A tool systemd-imds acts as a client to systemd-imdsd and + imports various IMDS provided fields into local system credentials, + which can then be consumed by later services. The acquired IMDS data + is measured before being imported. + + * 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. - * A new tmpfiles.d/root.conf has been added that sets permissions - on the root directory (/) to 0555. + TPM Subsystem: + + * A new ConditionSecurity=measured-os unit condition has been added + that checks whether the system was booted with measured-boot + semantics (i.e. via systemd-stub or an equivalent verified-boot + mechanism that measured the OS to the TPM). This is very similar to + the pre-existing ConditionSecurity=measured-uki, but is more + generic, as it can also cover environments where the firmware/UKI does + not have a TPM but the OS has (which is for example the case if the + TPM is implemented purely in software). + + * A new service systemd-tpm2-swtpm.service has been added that can run + the IBM "swtpm" as a software TPM, for use as (optional) automatic + fallback for systems that lack a physical TPM but where TPM + functionality should be made available nonetheless. (This + functionality must be enabled via systemd.tpm2_software_fallback= on + the kernel command line.) Of course a software TPM running as part of + a system's userspace does not provide a security posture in any way + equivalent to that of a discrete hardware TPM, but in various + use cases it might still be preferable to having no TPM functionality + at all. The software TPM uses a key derived from the new "boot + secret" functionality for encryption, and stores its state in the + disk's ESP. This provides at least some protection, and reasonable + persistency from initrd on. + + Changes in systemd-tmpfiles and systemd-sysusers: + + * A new tmpfiles.d/root.conf has been added that sets permissions on + the root directory (/) to 0555. This is particularly useful in + environments where the root file system is created fresh and empty + with only /usr/ mounted in – but it is also useful as a general + safety net. * systemd-tmpfiles gained a new --inline switch which permits passing tmpfiles.d/ directives directly on the command line rather than via a @@ -178,8 +223,8 @@ CHANGES WITH 261 in spe: cannot be used to modify the resources which are used in the early boot. - * A kernel command line kill switch is now honored that disables - systemd-sysext and systemd-confext merging entirely. + * A kernel command line kill switch that disables systemd-sysext and + systemd-confext merging entirely is now honoured. Changes in systemd-networkd and networkctl: @@ -210,18 +255,18 @@ CHANGES WITH 261 in spe: Changes in systemd-resolved: - * New 'DNSCacheSize=', 'MulticastDNSCacheSize=' and 'LLMNRCacheSize=' - settings are now supported to allow overriding the default caches - sizes for the respective protocols. - * systemd-resolved will now read additional DNS resource record definitions to resolve locally from JSON drop-in files in {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. This - is a generalization of /etc/hosts in a way, but is supposed to be + is a generalization of /etc/hosts, but is intended to be more flexible (i.e. other RR types than just A/AAAA + PTR can be configured, even if right now not too many are hooked up yet) and follow the usual drop-in pattern that avoids ownership conflicts. + * New 'DNSCacheSize=', 'MulticastDNSCacheSize=' and 'LLMNRCacheSize=' + settings are now supported to allow overriding the default + per-interface cache sizes for the respective protocols. + * Insecure DNSSEC answers using unsupported signature or digest algorithms are now correctly accepted as insecure, rather than being rejected outright. @@ -231,17 +276,30 @@ CHANGES WITH 261 in spe: stale entries available. * /etc/hosts entries are now re-read on reload (SIGHUP / D-Bus - Reload / Varlink Reload). + Reload() / Varlink Reload()). Changes in systemd-udevd, hwdb and udev rules: * The DMI ID device (/sys/class/dmi/id) is now tagged so that early-boot consumers can reliably order against it. - * A new hwdb database describes basic IMDS endpoints for known - cloud providers (see also systemd-imdsd above). - - Changes in systemd-boot, systemd-stub, bootctl, ukify and BLS: + * udev's "blkid" builtin will now set a new udev property + ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP=1 on boot block devices where a + GPT partition table is detected for a sector size different from the + native sector size of the device. (This typically happens if a Hybrid + ISO9660/GPT disk image is booted as CDROM, where the native sector + size is 2048 but the GPT header uses 512 sector size). If this + happens then a systemd-loop@.service instance is automatically pulled + in via an udev rule that generates a loopback block device from the + discovered block device, exposing the device with the corrected + sector size. Or in other words: booting a fully valid GPT disk image + on a block device with a non-matching sector size will now just work, + and automatically result in a matching loopback device popping + up. The new property is also set if the boot block device carries a + GPT header (i.e. is partitioned) but the block device has partition + table processing turned off. + + Changes in systemd-boot, systemd-stub, bootctl, ukify: * systemd-stub will now maintain a "boot secret" and pass it to the OS in the /.extra/boot-secret file in the initrd. This boot secret is @@ -249,37 +307,58 @@ CHANGES WITH 261 in spe: OS (i.e. only accessible in the UEFI environment). The EFI variable is automatically initialized to a randomly generated value if not set yet. It is intended to be used for certain fallback codepaths in case - a local TPM is not available, but an UEFI environment is. If a TPM is + a local TPM is not available, but a UEFI environment is. If a TPM is available, it's highly recommended to use it as a better source of - per-system key material, but in absence of a TPM it often might be an + per-system key material, but in the absence of a TPM it often might be an acceptable fallback for local, persistent key material. Applications should never use the key as-is, but derive their own key from it, through hashing. - * systemd-stub now auto-detects the active EFI serial console - device and appends an appropriate "console=" parameter to the - kernel command line, simplifying serial-console UKI deployments. - - * A new "extra" type-1 Boot Loader Specification stanza is parsed - and used to deliver additional initrds to a UKI without modifying - its contents. The generic "addon" handling has been generalized - so that all UKI sidecar artifacts (initrds, command-line - overlays, devicetree blobs, etc.) follow the same lookup rules. + * systemd-stub now auto-detects the active EFI serial console device + and appends an appropriate "console=" parameter to the kernel command + line, simplifying serial-console UKI deployments: the serial console + output configuration of UEFI is now automatically propagated to + Linux. + + * systemd-stub will now query the firmware's keyboard mapping and pass + it to the OS via the LoaderKeyboardLayout EFI variable. This variable + is then used by systemd-vconsole-setup as a fallback keyboard mapping + if no mapping is explicitly configured otherwise. On modern laptops this + means there's a good chance that the keyboard mapping of the built-in + keyboard will be automatically detected and set up without requiring + user intervention. + + * A new "extra" type-1 Boot Loader Specification stanza is parsed and + used to deliver additional resources to a UKI without modifying its + contents. This may be used to pass confext DDIs, sysext DDIs or + encrypted credentials to a UKI kernel. The generic "addon" handling + has been generalized so that all UKI sidecar artifacts (initrds, + command-line overlays, devicetree blobs, etc.) follow the same lookup + rules. * systemd-boot will never auto-boot a non-default UKI profile, preventing accidental boots into alternative profiles after a single timeout expiry. - * El Torito CDROM boot catalog partition UUIDs are now discovered - and exposed via the same mechanism as GPT/MBR partitions, + * systemd-stub: El Torito CDROM boot catalog partition UUIDs are now + discovered and exposed via the same mechanism as GPT/MBR partitions, enabling unified ISO image dissection. - * bootctl gained a new 'link' verb (with a matching Varlink API) - that installs a UKI on the ESP by symlinking it from - /usr/lib/modules/ instead of copying. A new - '--print-efi-architecture' option prints the EFI architecture - identifier of the running system, which is useful from packaging - scripts. + * systemd-stub will now incorporate any initrd already configured via + the LINUX_INITRD_MEDIA_GUID UEFI device into the set of initrds it + passes to the kernel (previously it would fail if one was already + set). This means systemd-stub now operates in a purely incremental + mode regarding initrds passed in from earlier boot steps. + + * bootctl gained a new '--print-efi-architecture' option that prints + the EFI architecture identifier of the running system, which is + useful from scripts. + + * bootctl gained a new 'link' verb (with a matching Varlink API) that + installs a Type #1 boot loader entry based on a UKI in combination + with confext DDIs, sysext DDIs or system credentials. + + * bootctl's 'unlink' verb is now also accessible via a Varlink API. Changes in systemd-repart: @@ -289,8 +368,11 @@ CHANGES WITH 261 in spe: * A new VolumeName= setting allows specifying the LUKS2 volume name independently of the on-disk partition label. - * A new BlockDeviceReplace= setting allows partitions to - atomically replace the contents of an existing block device. + * A new BlockDeviceReplace= setting allows partitions to atomically + migrate the contents of an existing block device to a different + partition. This may be used for OS installers that migrate the + running OS as a whole from an in-memory block device onto a disk, + requiring no reboot as part of the installation cycle. * systemd-repart now supports a new --grain-size= switch to explicitly select the desired "grain" size (i.e. alignment granularity) when @@ -304,15 +386,11 @@ CHANGES WITH 261 in spe: * --shrink now uses mkfs.btrfs's native minimal-filesystem support when available. - * A new persistent activation flag for LUKS2 partitions causes the - allow-discards option to be persisted in the LUKS2 header. + * A new per-partition Discard= setting may be used to control + the persistent "allow-discards" flag of LUKS encrypted partitions. Changes in systemd-sysupdate: - * Partial-and-pending UpdateSet states are now correctly recognized - in additional code paths, and partial versions may be returned - as the next candidate as well as targeted by vacuuming. - * systemd-sysupdate now emits READY=1 via sd_notify() after the install step completes, allowing for tighter integration with orchestration tooling. @@ -335,39 +413,41 @@ CHANGES WITH 261 in spe: --forward-journal-NAME= options to forward journal entries from the payload to specified journal sockets. - * systemd-vmspawn gained a new --bind-volume= option that binds host - paths into the VM. - - * systemd-vmspawn gained a new --cxl= option that configures CXL - memory devices and adds support for memory hotplug. + * systemd-vmspawn gained a new --bind-volume= option that binds volumes + provided by the storage provider Varlink logic (see below) into a VM. * systemd-vmspawn gained a new --console-transport= option that controls how the VM console is presented (PTY, native, headless, etc.); a PTY is now provided for the native console mode, and headless console operation is supported. + * systemd-vmspawn's --console= switch gained a new value "headless" to + spawn a VM in truly headless mode, i.e. without a console or display. + * systemd-vmspawn gained a new switch --efi-nvram-state= for controlling whether and where to persist the EFI variable NVRAM between VM invocations. It's modelled after --tpm-state= in - behaviour. - - * systemd-vmspawn's TPM logic will now ensure to install an - endorsement certificate. + behaviour. There's also a new --efi-nvram-template= knob for + selecting a template file to initialize the EFI NVRAM state from on + first boot. - * systemd-vmspawn's --console= switch gained a new value "headless" to - spawn a VM in truly headless mode, i.e without a console or display. + * systemd-vmspawn's TPM logic will now ensure an endorsement + certificate is installed. * systemd-vmspawn gained a new --firmware-features= option that enables or disables individual firmware features (with a "~feature" prefix for negation). + * systemd-vmspawn now searches XDG_DATA_DIRS for QEMU firmware + descriptors. + * systemd-vmspawn now supports direct kernel boot without UEFI firmware. * systemd-vmspawn gained support for a new --image-disk-type= switch - for selecting the block storage type (virtio-blk, virtio-scsi, nvme) - for block devices exposed to the VM. The --extra-drive= switch - optionally can configure this too now. + for selecting the block storage type (virtio-blk, virtio-scsi, nvme, + scsi-cd) for block devices exposed to the VM. The --extra-drive= + switch can now optionally configure this too. * The io.systemd.MachineInstance Varlink interface gained AddStorage(), RemoveStorage() and ReplaceStorage() methods for @@ -379,9 +459,6 @@ CHANGES WITH 261 in spe: * systemd-vmspawn now uses the QEMU built-in vdagent (clipboard, resolution sync) instead of spicevmc. - * systemd-vmspawn now searches XDG_DATA_DIRS for QEMU firmware - descriptors. - * systemd-vmspawn gained a new --print-profiles command that falls back to a non-JSON representation when the output is not JSON. @@ -419,23 +496,30 @@ CHANGES WITH 261 in spe: * The crashing thread's TID and name are now captured and recorded alongside the existing PID/comm metadata. - Changes in systemd-logind: - - * A new io.systemd.Shutdown Varlink interface has been introduced - to request system shutdown. The peer connection identifier of - the requester is logged. - Changes in systemd-creds, systemd-cryptsetup and systemd-cryptenroll: * systemd-creds only locks against the public-key TPM2 PCR when booting on UEFI firmware that supports TPMs, avoiding spurious - errors on systems without TPM. + errors on systems without a TPM. * libcryptsetup is now loaded via dlopen() in the cryptsetup binaries, eliminating the hard runtime dependency for systems that do not actually use it. + Dynamic Linking: + + * libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk + and libcryptsetup are now consistently loaded via dlopen() + throughout the codebase, further reducing the set of mandatory + dependencies from all binaries. + + * The unused dependency on libgpg-error has been dropped. + + → This means all direct shared library linking against external + libraries has now been replaced by dlopen()-based linking, with the + sole exception of libc. + Changes in libsystemd: * A new public 'sd-dlopen' header-only API has been added that @@ -445,52 +529,48 @@ CHANGES WITH 261 in spe: This header is licensed under MIT-0 to facilitate embedding it directly in other projects. - * A new 'sd_json_parse_fd' API is now available to facilitate parsing - FDs out of Varlink connections. + * sd_json_parse() (and related calls) now supports a pair of new flags + SD_JSON_PARSE_MUST_BE_OBJECT and SD_JSON_PARSE_MUST_BE_ARRAY. If + specified, these flags cause the parser to fail if the top-level + parsed JSON variant is not an object/array. - * sd-varlink gained a protocol upgrade mechanism, exposed via the - new sd_varlink_call_and_upgrade() and - sd_varlink_reply_and_upgrade() API. Internally the upgrade fd - handling and MSG_PEEK semantics for upgradable sockets have - been reworked, and the upgrade API always returns two file - descriptors. + * sd-json gained a new helper sd_json_parse_fd() that parses JSON data + from a file referenced by a file descriptor. It works similar to + sd_json_parse_file(), which operates on a FILE*. Moreover, a new + flag SD_JSON_PARSE_SEEK0 has been added which explicitly resets the + file offset to 0 when parsing via sd_json_parse_file() or + sd_json_parse_fd(). - * The 'ret' argument of sd_varlink_idl_parse() is now optional. + * sd-varlink gained a new call sd_varlink_set_sentinel() that + simplifies generating responses to method calls that have "more" set. - * sd-varlink's per-UID connection limit has been scaled down to - 128. + * sd-varlink gained a new call sd_varlink_call_and_upgrade() that + permits calling a method call with the Varlink "upgrade" feature + enabled, i.e. that allows switching from Varlink to a different + protocol. varlinkctl acquired a new --upgrade switch to expose this + functionality. A new call sd_varlink_reply_and_upgrade() supports + "upgrade" mode on the server side. - * Enumeration types have been introduced throughout the - well-known Varlink interfaces: ManagedOOMMode in - io.systemd.oom; class and whom in io.systemd.Machine; - configuration, scheduling and mount settings in - io.systemd.Unit; configuration settings in io.systemd.Manager. + * The 'ret' argument of sd_varlink_idl_parse() is now optional. - * varlinkctl gained a new 'serve' verb that wraps an arbitrary - command as a Varlink server, and a new '--upgrade' option - (along with '--exec') to consume the protocol upgrade API. + * sd-varlink's per-UID connection limit has been reduced to 128. - * sd-path now exposes an XDG 'projects' user directory. + * sd-event gained native support for CPU and IO pressure events, in + addition to the pre-existing support for memory pressure events. This + is useful for slowing down or pausing worker threads or so if CPU or + IO is under pressure. - * sd-device gained a number of helpers, including - sd_device_get_sysattr_safe_string(), sd_device_get_sysattr_u8(), - and sd_device_get_sysattr_u16(). + * sd-path now exposes the XDG 'projects' user directory. Other changes: - * A new systemd-imdsd service has been introduced that makes cloud - Instance Metadata Service (IMDS) data accessible locally. It is - accompanied by a 'systemd-imds' client tool, a generator that hooks - IMDS retrieval into cloud guests, a hwdb database describing basic - IMDS endpoints for known clouds (including AWS, Azure, Google - Cloud, Oracle Cloud, Tencent Cloud and Alibaba ECS), and TPM - measurements of the retrieved data so that IMDS-provided values can - be used as attestation inputs. Networking to cloud IMDS services - may also be locked down for recognized clouds; the new meson option - "-Dimds-network=" can change the default mode to "locked" at build - time. This is recommended for secure installations, but typically - conflicts with traditional IMDS clients such as cloud-init, which - require direct IMDS access. + * A new io.systemd.Shutdown Varlink interface has been introduced + to request system shutdown. The peer connection identifier of + the requester is logged. + + * varlinkctl gained a new 'serve' verb that wraps an arbitrary + command as a Varlink server, and a new '--upgrade' option + (along with '--exec') to consume the protocol upgrade API. * The systemd-report framework introduced in v260 has been substantially extended. Basic system metrics @@ -499,64 +579,59 @@ CHANGES WITH 261 in spe: report-basic.socket activation unit. Per-cgroup metrics (CPU time, etc.) and per-service metrics are exposed through dedicated Varlink services. systemd-report gained the ability to upload collected - reports via a "varlink socket directory" of HTTP destinations, and + reports via a Varlink socket directory or HTTP destinations, and to inject custom HTTP headers when doing so. * 'systemctl kexec' gained a new --kernel-cmdline= argument that overrides the kernel command line for kexec invocations. - * 'systemctl kexec' now prefers invoking the 'kexec_file_load' system - call directly, and uses the 'kexec' binary only as a fallback if - that is not available, so that on most systems the dependency on + * 'systemctl kexec' now prefers invoking the 'kexec_file_load()' system + call directly, and uses the 'kexec' binary only as a fallback if that + is not available, so that on most systems the dependency on 'kexec-tools' is no longer necessary. * fstab-generator now supports swap on network block devices. - * libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk - and libcryptsetup are now consistently loaded via dlopen() - throughout the code base, further reducing the set of mandatory - dependencies from all binaries. - - * The unused dependency on libgpg-error has been dropped. - * systemd-firstboot will now honour a new "firstboot.hostname" system credential for persistently setting the system hostname on first - boot. This is different from the pre-existing "system.hostname" which - sets the hostname on boot the credential is passed on only, and which - is not made persistent. + boot. This is different from the pre-existing "system.hostname", + which sets the hostname only for the boot the credential is passed + on, and which is not made persistent. * systemd-hostnamed now provides a D-Bus API to acquire arbitrary fields from /etc/machine-info. * systemd-hostnamed is now available in early boot too (i.e. before basic.target). Note that D-Bus only becomes available later, and it - hence can only be contacted via Varlink that early. + can hence only be contacted via Varlink that early. * JSON user database records may now optionally carry a birth date field. homectl gained a new switch --birth-date= to set it. - * systemd-vconsole-setup will now gracefully handle if the + * systemd-vconsole-setup will now gracefully handle the case where the setfont/loadkeys tools are not installed, and skip operation cleanly in that case. - * sd_json_parse() (and related calls) now supports a pair of new flags - SD_JSON_PARSE_MUST_BE_OBJECT and SD_JSON_PARSE_MUST_BE_ARRAY. If - specified this flags cause the parser to failure if the top-level - parsed JSON variant is not an object/array. + * The _netdev pseudo mount option is now also supported for swap + devices, i.e. enabling correct boot time ordering to allow swapping + on network block devices. - * A new service systemd-tpm2-swtpm.service has been added that can run - the IBM "swtpm" as a software TPM, for use as (optional) automatic - fallback for systems that lack a physical TPM but where TPM - functionality should be made available nonetheless. (This - functionality must be enabled via systemd.tpm2_software_fallback= on - the kernel command line.) Of course a software TPM running as part of - a system's userspace does not provide a security posture in any way - equivalent to that of a discrete hardware TPM, however in various - usecase it might still be preferable over having no TPM functionality - at all. The software TPM uses a key derived from the new "boot - secret" functionality for encryption, and stores its state in the - disk's TPM. This provides at least some protection, and reasonable - persistancy from initrd on. + * systemd-run gained a new --output= switch for controlling log output + formatting when using "-v" mode. + + * A new component systemd-sysinstall has been added that implements a + simple, modern textual installer for an OS. It's a wrapper around + Varlink calls to systemd-repart (to set up a partition table and + stream in the OS partitions), bootctl link (to install kernel and + boot menu items for the OS), bootctl install (to install the + systemd-boot boot loader), systemd-creds (to configure the minimal + amount of system settings, such as keyboard mappings, locale for the + newly installed system), followed by a request to reboot. It operates + either interactively or command-line driven. + + * systemd-oomd gained support for OOM rulesets. These allow fine-tuning + OOM policy handling, and may be defined in /etc/systemd/oomd/rules.d/ + and then enabled on a service unit via the new OOMRule= option. CHANGES WITH 260: @@ -1387,6 +1462,9 @@ CHANGES WITH 259: wrappers and other APIs it provides have been reimplemented directly in systemd, which reduced the codebase and the dependency tree. + → In summary: all direct shared library linking is gone now from + systemd, with the one exception of libc. + systemd-machined/systemd-importd: * systemd-machined gained support for RegisterMachineEx() + From a332f3b6caf1f1fa1d90f5d5ea88f24d3017792a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 24 Apr 2026 19:16:36 +0100 Subject: [PATCH 1968/2155] test: add test coverage for homed+fscrypt Co-developed-by: Claude Opus 4.7 --- test/units/TEST-46-HOMED.sh | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 5afa42d73968e..0a6f02cc81b0a 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -984,4 +984,86 @@ testcase_match() { homectl remove matchtest } +testcase_fscrypt() { + if ! command -v mkfs.ext4 >/dev/null; then + echo "e2fsprogs not installed, skipping fscrypt test." + return 0 + fi + + local IMAGE MNT + IMAGE="$(mktemp /tmp/fscrypt.XXXXXX.img)" + MNT="$(mktemp -d /tmp/fscrypt-mnt.XXXXXX)" + # shellcheck disable=SC2064 + trap "homectl deactivate fscrypttest 2>/dev/null || true; homectl remove fscrypttest 2>/dev/null || true; umount '$MNT' 2>/dev/null || true; rm -rf '$MNT' '$IMAGE'" RETURN ERR + + truncate -s 64M "$IMAGE" + if ! mkfs.ext4 -q -O encrypt "$IMAGE"; then + echo "mkfs.ext4 -O encrypt unsupported, skipping fscrypt test." + return 0 + fi + + if ! mount -o loop "$IMAGE" "$MNT"; then + echo "Cannot loop-mount fscrypt-capable ext4, skipping fscrypt test." + return 0 + fi + + if ! NEWPASSWORD=fsfsfs1234 homectl create fscrypttest \ + --storage=fscrypt \ + --image-path="$MNT/fscrypttest" \ + --rate-limit-interval=1s --rate-limit-burst=1000; then + echo "homed fscrypt backend not usable on this kernel, skipping." + return 0 + fi + + inspect fscrypttest + + (! PASSWORD=wrongpass timeout 10s homectl authenticate fscrypttest /home/fscrypttest/file1' + [[ "$(fscrypt_run0 fsfsfs1234 'cat /home/fscrypttest/file1')" == "hello fscrypt" ]] + fscrypt_run0 fsfsfs1234 'mkdir /home/fscrypttest/subdir' + fscrypt_run0 fsfsfs1234 'dd if=/dev/urandom of=/home/fscrypttest/subdir/blob bs=4096 count=8 status=none' + fscrypt_run0 fsfsfs1234 'cp /home/fscrypttest/subdir/blob /home/fscrypttest/subdir/blob.copy && cmp /home/fscrypttest/subdir/blob /home/fscrypttest/subdir/blob.copy' + fscrypt_run0 fsfsfs1234 'echo appended >> /home/fscrypttest/file1 && grep -F appended /home/fscrypttest/file1 >/dev/null' + fscrypt_run0 fsfsfs1234 'rm /home/fscrypttest/subdir/blob.copy && test ! -e /home/fscrypttest/subdir/blob.copy' + + systemctl stop user@"$(id -u fscrypttest)".service 2>/dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + # After deactivation the fscrypt-encrypted directory is locked, so cleartext file names should not be visible + [[ ! -e "$MNT/fscrypttest/file1" ]] + + PASSWORD=fsfsfs1234 homectl activate fscrypttest + [[ "$(fscrypt_run0 fsfsfs1234 'head -n1 /home/fscrypttest/file1')" == "hello fscrypt" ]] + systemctl stop user@"$(id -u fscrypttest)".service 2>/dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + homectl update fscrypttest --real-name="Fscrypt Test" --offline + inspect fscrypttest | grep "Real Name: Fscrypt Test" >/dev/null + + PASSWORD=fsfsfs1234 NEWPASSWORD=newfsfs5678 homectl passwd fscrypttest + PASSWORD=newfsfs5678 homectl authenticate fscrypttest + (! PASSWORD=fsfsfs1234 timeout 10s homectl authenticate fscrypttest /dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + homectl remove fscrypttest +} + run_testcases From b5d536206993d0e3057c4cd1b9f46af7547f9e31 Mon Sep 17 00:00:00 2001 From: Vincent Mihalkovic Date: Mon, 11 May 2026 13:52:49 +0200 Subject: [PATCH 1969/2155] fstab-generator: fix spurious quota warning for xfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystems like xfs, btrfs, gfs2 and ocfs2 handle quotas internally and do not need external quotacheck/quotaon services. When usrquota or grpquota mount options are used in fstab for these filesystems, generator_hook_up_quotacheck() falls through to the !fstype_needs_quota() branch and emits a misleading warning that quotas are "not supported" when they actually work fine — the kernel handles them internally. Add fstype_has_internal_quota() to return early with a debug message, and adopt a tri-state return convention so the caller skips quotaon when quotacheck was not needed. The buggy code path was introduced in #24824 and #24880. Co-developed-by: Claude Opus 4.6 --- src/basic/mountpoint-util.c | 11 +++++++++++ src/basic/mountpoint-util.h | 1 + src/fstab-generator/fstab-generator.c | 2 +- src/shared/generator.c | 13 ++++++++++--- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 958e34bc5326f..7c043bb54a4b9 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -385,6 +385,17 @@ bool fstype_needs_quota(const char *fstype) { "f2fs"); } +bool fstype_has_internal_quota(const char *fstype) { + /* These filesystems have built-in quota support and do not need + * external quotacheck/quotaon services - see the "nothing needed" + * entries in fstype_needs_quota() above. */ + return STR_IN_SET(fstype, + "xfs", + "gfs2", + "ocfs2", + "btrfs"); +} + bool fstype_is_api_vfs(const char *fstype) { assert(fstype); diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 1381f3d8aba0e..8cc966751c9c6 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -61,6 +61,7 @@ static inline int path_is_mount_point(const char *path) { bool fstype_is_network(const char *fstype); bool fstype_needs_quota(const char *fstype); +bool fstype_has_internal_quota(const char *fstype); bool fstype_is_api_vfs(const char *fstype); bool fstype_is_blockdev_backed(const char *fstype); bool fstype_is_ro(const char *fsype); diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 572f30a1cf7f4..2c23d28501027 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -704,7 +704,7 @@ static int add_mount( if (r < 0) { if (r != -EOPNOTSUPP) return r; - } else { + } else if (r > 0) { r = generator_hook_up_quotaon(dest, where, target_unit); if (r < 0) return r; diff --git a/src/shared/generator.c b/src/shared/generator.c index fd527b3e6d65a..5e2f8f6e0e64f 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -817,12 +817,18 @@ int generator_hook_up_quotacheck( if (isempty(fstype) || streq(fstype, "auto")) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what); + if (fstype_has_internal_quota(fstype)) { + log_debug("%s handles quotas internally, skipping quotacheck/quotaon setup for %s", fstype, what); + return 0; + } if (!fstype_needs_quota(fstype)) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype); /* quotacheck unit for system root */ - if (path_equal(where, "/")) - return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + if (path_equal(where, "/")) { + r = generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + return r < 0 ? r : 1; + } r = unit_name_path_escape(where, &instance); if (r < 0) @@ -838,7 +844,8 @@ int generator_hook_up_quotacheck( if (r < 0) return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); - return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); + r = generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); + return r < 0 ? r : 1; } int generator_hook_up_quotaon( From 4a82fc67c62f42fc0cc5656887386c653896c6e0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 24 Apr 2026 20:43:30 +0100 Subject: [PATCH 1970/2155] homed/fscrypt: add new xattr format hardening key sealing The current key sealing format has some less-than-ideal weaknesses: - PBKDF2 with only 65k iterations, where recommendations are ~200k - AES with null IV, relying on salt for uniqueness - lack of AES MAC/AEAD However improbable, it is at least theorically possible that with a lot of resources an offline bruteforce could be attempted. Add a v2 sealing format, keeping unsealing compatibility with the current format: v2::::: and use 600k iterations for the PBKDF2 sha512 --- docs/HOME_DIRECTORY.md | 14 +- src/home/homework-fscrypt.c | 340 ++++++++++++++++++++++++++++++------ test/units/TEST-46-HOMED.sh | 8 + 3 files changed, 305 insertions(+), 57 deletions(-) diff --git a/docs/HOME_DIRECTORY.md b/docs/HOME_DIRECTORY.md index 57d87f5eeac08..0ef39e9737254 100644 --- a/docs/HOME_DIRECTORY.md +++ b/docs/HOME_DIRECTORY.md @@ -54,11 +54,15 @@ mechanism, except that the home directory is encrypted using `fscrypt`. Key management is implemented via extended attributes on the directory itself: for each password an extended attribute `trusted.fscrypt_slot0`, `trusted.fscrypt_slot1`, `trusted.fscrypt_slot2`, … is maintained. -Its value contains a colon-separated pair of Base64 encoded data fields. -The first field contains a salt value, the second field the encrypted volume key. -The latter is encrypted using AES256 in counter mode, using a key derived from the password via PBKDF2-HMAC-SHA512, -together with the salt value. -The construction is similar to what LUKS does for`dm-crypt` encrypted volumes. +New slots are written in the `v2` format: a colon-separated string of the form +`$v2:::::`, where `` is a decimal integer +and the salt, IV, ciphertext and tag fields are Base64 encoded. +The volume key is wrapped with AES-256-GCM (authenticated encryption with a random IV) +under a key derived from the password via PBKDF2-HMAC-SHA512 with the stored iteration count. +For backward compatibility, legacy slots written by older versions of systemd-homed +(a colon-separated `:` pair encrypted with AES-256-CTR and a 0xFFFF-iteration +PBKDF2-HMAC-SHA512 KDF) continue to be readable, and they are upgraded to the v2 format the next +time the password is changed. Note that extended attributes are not encrypted by `fscrypt` and hence are suitable for carrying the key slots. Moreover, by using extended attributes, the slots are directly attached to the directory and an independent sidecar key database is not required. diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 6f8ae4b8c9c1c..432f5d8678a88 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "crypto-util.h" #include "errno-util.h" +#include "extract-word.h" #include "fd-util.h" #include "format-util.h" #include "hexdecoct.h" @@ -18,6 +19,7 @@ #include "homework-password-cache.h" #include "homework-quota.h" #include "homework.h" +#include "iovec-util.h" #include "keyring-util.h" #include "log.h" #include "memory-util.h" @@ -186,12 +188,33 @@ static void calculate_key_descriptor( memcpy(ret_key_descriptor, hashed2, FS_KEY_DESCRIPTOR_SIZE); } -static int fscrypt_slot_try_one( +/* fscrypt slot wrapping + * + * Two on-disk formats are supported. New slots are always written in v2, which improves offline security. + * + * v1 (legacy, read-only): + * : + * KDF: PBKDF2-HMAC-SHA512, 0xFFFF iterations + * Cipher: AES-256-CTR, all-zero IV (relies on per-slot random salt for key uniqueness) + * Integrity: 64-bit truncated double-SHA512 key descriptor comparison only + * + * v2: + * $v2::::: + * KDF: PBKDF2-HMAC-SHA512, FSCRYPT_SLOT_PBKDF2_ITERATIONS iterations (cost stored per slot) + * Cipher: AES-256-GCM with explicit random 96-bit IV and 128-bit authentication tag + */ + +#define FSCRYPT_SLOT_PBKDF2_ITERATIONS UINT32_C(600000) +#define FSCRYPT_SLOT_SALT_SIZE 64u +#define FSCRYPT_SLOT_GCM_IV_SIZE 12u +#define FSCRYPT_SLOT_GCM_TAG_SIZE 16u + +static int fscrypt_slot_try_v1( const char *password, const void *salt, size_t salt_size, const void *encrypted, size_t encrypted_size, const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], - void **ret_decrypted, size_t *ret_decrypted_size) { + struct iovec *ret_decrypted) { _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; _cleanup_(erase_and_freep) void *decrypted = NULL; @@ -273,25 +296,228 @@ static int fscrypt_slot_try_one( if (r < 0) return r; + if (ret_decrypted) { + ret_decrypted->iov_base = TAKE_PTR(decrypted); + ret_decrypted->iov_len = decrypted_size; + } + + return 0; +} + +static int fscrypt_slot_try_v2( + const char *password, + uint32_t iterations, + const struct iovec *salt, + const struct iovec *iv, + const struct iovec *encrypted, + const struct iovec *tag, + const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], + struct iovec *ret_decrypted) { + + _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; + _cleanup_(erase_and_freep) void *decrypted = NULL; + uint8_t key_descriptor[FS_KEY_DESCRIPTOR_SIZE]; + int decrypted_size_out1 = 0, decrypted_size_out2 = 0; + uint8_t derived[512 / 8] = {}; + size_t decrypted_size; + const EVP_CIPHER *cc; + int r; + + assert(password); + assert(iterations > 0); + assert(iovec_is_set(salt)); + assert(iovec_is_set(iv)); + assert(iovec_is_set(encrypted)); + assert(iovec_is_set(tag)); + assert(match_key_descriptor); + + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + CLEANUP_ERASE(derived); + + if (sym_PKCS5_PBKDF2_HMAC( + password, strlen(password), + salt->iov_base, salt->iov_len, + (int) iterations, sym_EVP_sha512(), + sizeof(derived), derived) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed."); + + context = sym_EVP_CIPHER_CTX_new(); + if (!context) + return log_oom(); + + assert_se(cc = sym_EVP_aes_256_gcm()); + + /* We only use the first 256 bit of the derived key */ + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); + + if (sym_EVP_DecryptInit_ex(context, cc, /* impl= */ NULL, /* key= */ NULL, /* iv= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); + + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, (int) iv->iov_len, /* ptr= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM IV length."); + + if (sym_EVP_DecryptInit_ex(context, /* type= */ NULL, /* impl= */ NULL, derived, iv->iov_base) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set decryption key/IV."); + + if (__builtin_add_overflow(encrypted->iov_len, (size_t) sym_EVP_CIPHER_get_block_size(cc), &decrypted_size)) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Decrypted buffer size would overflow."); + + decrypted = malloc(decrypted_size); + if (!decrypted) + return log_oom(); + + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted->iov_base, encrypted->iov_len) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key."); + + assert((size_t) decrypted_size_out1 <= decrypted_size); + + /* Set the expected GCM tag before finalisation, as an authentication failure here means the wrong + * password (or a tampered slot). */ + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, (int) tag->iov_len, tag->iov_base) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM tag."); + + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) + return -ENOANO; /* GCM authentication failed: wrong password or tampered slot */ + + assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 <= decrypted_size); + decrypted_size = (size_t) decrypted_size_out1 + (size_t) decrypted_size_out2; + + calculate_key_descriptor(decrypted, decrypted_size, key_descriptor); + + if (memcmp(key_descriptor, match_key_descriptor, FS_KEY_DESCRIPTOR_SIZE) != 0) + /* Authenticated decryption succeeded but the resulting volume key does not match the policy + * descriptor. Treat as a non-match (e.g. leftover slot from a different fscrypt setup). */ + return -ENOANO; + + r = fscrypt_upload_volume_key(key_descriptor, decrypted, decrypted_size, KEY_SPEC_THREAD_KEYRING); + if (r < 0) + return r; + + if (ret_decrypted) { + ret_decrypted->iov_base = TAKE_PTR(decrypted); + ret_decrypted->iov_len = decrypted_size; + } + + return 0; +} + +static int fscrypt_slot_try_one( + const char *password, + const char *xattr_value, size_t xattr_size, + const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], + struct iovec *ret_decrypted) { + + _cleanup_free_ void *salt = NULL, *iv = NULL, *encrypted = NULL, *tag = NULL; + size_t salt_size, iv_size, encrypted_size, tag_size; + const char *p, *e; + const void *body; + int r; + + assert(password); + assert(xattr_value); + assert(xattr_size > 0); + assert(match_key_descriptor); + + body = memory_startswith(xattr_value, xattr_size, "$v2:"); + if (!body) { + /* Legacy v1 format: ":" */ + log_debug("fscrypt slot uses legacy v1 format, will upgrade to v2 on next password change."); + + e = memchr(xattr_value, ':', xattr_size); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed legacy fscrypt slot (no separator)."); + + r = unbase64mem_full(xattr_value, e - xattr_value, /* secure= */ false, &salt, &salt_size); + if (r < 0) + return log_error_errno(r, "Failed to decode legacy salt: %m"); + + r = unbase64mem_full(e + 1, xattr_size - (e - xattr_value) - 1, /* secure= */ false, &encrypted, &encrypted_size); + if (r < 0) + return log_error_errno(r, "Failed to decode legacy ciphertext: %m"); + + return fscrypt_slot_try_v1(password, + salt, salt_size, + encrypted, encrypted_size, + match_key_descriptor, + ret_decrypted); + } + + /* v2 format: "$v2:::::". Reject if it has NULs. */ + _cleanup_free_ char *body_str = NULL; + r = make_cstring(body, xattr_size - STRLEN("$v2:"), MAKE_CSTRING_REFUSE_TRAILING_NUL, &body_str); + if (r < 0) + return log_error_errno(r, "Malformed v2 fscrypt slot: %m"); + + _cleanup_free_ char *iter_str = NULL, *salt_b64 = NULL, *iv_b64 = NULL, + *encrypted_b64 = NULL, *tag_b64 = NULL; + uint32_t iterations; + + p = body_str; + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_RETAIN_ESCAPE, + &iter_str, &salt_b64, &iv_b64, &encrypted_b64, &tag_b64); + if (r < 0) + return log_error_errno(r, "Failed to parse v2 fscrypt slot: %m"); + if (r < 5) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed v2 fscrypt slot."); + + if (safe_atou32(iter_str, &iterations) < 0 || iterations == 0 || iterations > INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid iteration count in v2 fscrypt slot."); + + r = unbase64mem(salt_b64, &salt, &salt_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 salt: %m"); + if (salt_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 salt size."); + + r = unbase64mem(iv_b64, &iv, &iv_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 IV: %m"); + if (iv_size != FSCRYPT_SLOT_GCM_IV_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 IV size."); + + r = unbase64mem(encrypted_b64, &encrypted, &encrypted_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 ciphertext: %m"); + if (encrypted_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty v2 ciphertext."); + + r = unbase64mem(tag_b64, &tag, &tag_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 tag: %m"); + if (tag_size != FSCRYPT_SLOT_GCM_TAG_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 tag size."); + + _cleanup_(iovec_done_erase) struct iovec decrypted = {}; + r = fscrypt_slot_try_v2(password, + iterations, + &IOVEC_MAKE(salt, salt_size), + &IOVEC_MAKE(iv, iv_size), + &IOVEC_MAKE(encrypted, encrypted_size), + &IOVEC_MAKE(tag, tag_size), + match_key_descriptor, + &decrypted); + if (r < 0) + return r; + if (ret_decrypted) - *ret_decrypted = TAKE_PTR(decrypted); - if (ret_decrypted_size) - *ret_decrypted_size = decrypted_size; + *ret_decrypted = TAKE_STRUCT(decrypted); return 0; } static int fscrypt_slot_try_many( char **passwords, - const void *salt, size_t salt_size, - const void *encrypted, size_t encrypted_size, + const char *xattr_value, size_t xattr_size, const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], - void **ret_decrypted, size_t *ret_decrypted_size) { + struct iovec *ret_decrypted) { int r; STRV_FOREACH(i, passwords) { - r = fscrypt_slot_try_one(*i, salt, salt_size, encrypted, encrypted_size, match_key_descriptor, ret_decrypted, ret_decrypted_size); + r = fscrypt_slot_try_one(*i, xattr_value, xattr_size, match_key_descriptor, ret_decrypted); if (r != -ENOANO) return r; } @@ -303,8 +529,7 @@ static int fscrypt_setup( const PasswordCache *cache, char **password, HomeSetup *setup, - void **ret_volume_key, - size_t *ret_volume_key_size) { + struct iovec *ret_volume_key) { _cleanup_free_ char *xattr_buf = NULL; int r; @@ -317,10 +542,9 @@ static int fscrypt_setup( return log_error_errno(r, "Failed to retrieve xattr list: %m"); NULSTR_FOREACH(xa, xattr_buf) { - _cleanup_free_ void *salt = NULL, *encrypted = NULL; _cleanup_free_ char *value = NULL; - size_t salt_size, encrypted_size, vsize; - const char *nr, *e; + size_t vsize; + const char *nr; /* Check if this xattr has the format 'trusted.fscrypt_slot' where '' is a 32-bit unsigned integer */ nr = startswith(xa, "trusted.fscrypt_slot"); @@ -334,28 +558,17 @@ static int fscrypt_setup( continue; if (r < 0) return log_error_errno(r, "Failed to read %s xattr: %m", xa); - - e = memchr(value, ':', vsize); - if (!e) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator.", xa); - - r = unbase64mem_full(value, e - value, /* secure= */ false, &salt, &salt_size); - if (r < 0) - return log_error_errno(r, "Failed to decode salt of %s: %m", xa); - - r = unbase64mem_full(e + 1, vsize - (e - value) - 1, /* secure= */ false, &encrypted, &encrypted_size); - if (r < 0) - return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa); + if (vsize == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s is empty.", xa); r = -ENOANO; char **list; FOREACH_ARGUMENT(list, cache->pkcs11_passwords, cache->fido2_passwords, password) { r = fscrypt_slot_try_many( list, - salt, salt_size, - encrypted, encrypted_size, + value, vsize, setup->fscrypt_key_descriptor, - ret_volume_key, ret_volume_key_size); + ret_volume_key); if (r >= 0) return 0; if (r != -ENOANO) @@ -371,9 +584,8 @@ int home_setup_fscrypt( HomeSetup *setup, const PasswordCache *cache) { - _cleanup_(erase_and_freep) void *volume_key = NULL; + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; struct fscrypt_policy policy = {}; - size_t volume_key_size = 0; const char *ip; int r; @@ -404,8 +616,7 @@ int home_setup_fscrypt( cache, h->password, setup, - &volume_key, - &volume_key_size); + &volume_key); if (r < 0) return r; @@ -429,8 +640,8 @@ int home_setup_fscrypt( r = fscrypt_upload_volume_key( setup->fscrypt_key_descriptor, - volume_key, - volume_key_size, + volume_key.iov_base, + volume_key.iov_len, KEY_SPEC_USER_KEYRING); if (r < 0) _exit(EXIT_FAILURE); @@ -476,11 +687,14 @@ static int fscrypt_slot_set( const char *password, uint32_t nr) { - _cleanup_free_ char *salt_base64 = NULL, *encrypted_base64 = NULL, *joined = NULL; + _cleanup_free_ char *salt_base64 = NULL, *iv_base64 = NULL, + *encrypted_base64 = NULL, *tag_base64 = NULL, + *joined = NULL; char label[STRLEN("trusted.fscrypt_slot") + DECIMAL_STR_MAX(nr) + 1]; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - int r, encrypted_size_out1, encrypted_size_out2; - uint8_t salt[64], derived[512 / 8] = {}; + int r, encrypted_size_out1 = 0, encrypted_size_out2 = 0; + uint8_t salt[FSCRYPT_SLOT_SALT_SIZE], iv[FSCRYPT_SLOT_GCM_IV_SIZE], + tag[FSCRYPT_SLOT_GCM_TAG_SIZE], derived[512 / 8] = {}; _cleanup_free_ void *encrypted = NULL; const EVP_CIPHER *cc; size_t encrypted_size; @@ -494,12 +708,16 @@ static int fscrypt_slot_set( if (r < 0) return log_error_errno(r, "Failed to generate salt: %m"); + r = crypto_random_bytes(iv, sizeof(iv)); + if (r < 0) + return log_error_errno(r, "Failed to generate IV: %m"); + CLEANUP_ERASE(derived); if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, sizeof(salt), - 0xFFFF, sym_EVP_sha512(), + (int) FSCRYPT_SLOT_PBKDF2_ITERATIONS, sym_EVP_sha512(), sizeof(derived), derived) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); @@ -507,16 +725,24 @@ static int fscrypt_slot_set( if (!context) return log_oom(); - /* We use AES256 in counter mode */ - cc = sym_EVP_aes_256_ctr(); + /* AES-256-GCM: authenticated encryption with explicit random IV */ + assert_se(cc = sym_EVP_aes_256_gcm()); - /* We only use the first half of the derived key */ + /* We only use the first 256 bit of the derived key */ assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); - if (sym_EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, /* impl= */ NULL, /* key= */ NULL, /* iv= */ NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); - encrypted_size = volume_key_size + sym_EVP_CIPHER_get_key_length(cc) * 2; + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, (int) sizeof(iv), /* ptr= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM IV length."); + + if (sym_EVP_EncryptInit_ex(context, /* type= */ NULL, /* impl= */ NULL, derived, iv) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set encryption key/IV."); + + if (!ADD_SAFE(&encrypted_size, volume_key_size, (size_t) sym_EVP_CIPHER_get_block_size(cc))) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Encrypted buffer size would overflow."); + encrypted = malloc(encrypted_size); if (!encrypted) return log_oom(); @@ -529,19 +755,31 @@ static int fscrypt_slot_set( 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); + assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size); encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2; + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, (int) sizeof(tag), tag) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to retrieve GCM tag."); + ss = base64mem(salt, sizeof(salt), &salt_base64); if (ss < 0) return log_oom(); + ss = base64mem(iv, sizeof(iv), &iv_base64); + if (ss < 0) + return log_oom(); + ss = base64mem(encrypted, encrypted_size, &encrypted_base64); if (ss < 0) return log_oom(); - joined = strjoin(salt_base64, ":", encrypted_base64); - if (!joined) + ss = base64mem(tag, sizeof(tag), &tag_base64); + if (ss < 0) + return log_oom(); + + if (asprintf(&joined, "$v2:%" PRIu32 ":%s:%s:%s:%s", + FSCRYPT_SLOT_PBKDF2_ITERATIONS, + salt_base64, iv_base64, encrypted_base64, tag_base64) < 0) return log_oom(); xsprintf(label, "trusted.fscrypt_slot%" PRIu32, nr); @@ -716,9 +954,8 @@ int home_passwd_fscrypt( const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ char **effective_passwords /* new passwords */) { - _cleanup_(erase_and_freep) void *volume_key = NULL; + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; _cleanup_free_ char *xattr_buf = NULL; - size_t volume_key_size = 0; uint32_t slot = 0; int r; @@ -730,13 +967,12 @@ int home_passwd_fscrypt( cache, h->password, setup, - &volume_key, - &volume_key_size); + &volume_key); if (r < 0) return r; STRV_FOREACH(p, effective_passwords) { - r = fscrypt_slot_set(setup->root_fd, volume_key, volume_key_size, *p, slot); + r = fscrypt_slot_set(setup->root_fd, volume_key.iov_base, volume_key.iov_len, *p, slot); if (r < 0) return r; diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 0a6f02cc81b0a..0642eca2805a0 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -1043,6 +1043,14 @@ testcase_fscrypt() { # After deactivation the fscrypt-encrypted directory is locked, so cleartext file names should not be visible [[ ! -e "$MNT/fscrypttest/file1" ]] + # Verify we actually use the v2 format + local SLOT + SLOT="$(getfattr --absolute-names --only-values -n trusted.fscrypt_slot0 "$MNT/fscrypttest")" + [[ "${SLOT:0:4}" == "\$v2:" ]] || { + echo "fscrypt slot 0 is not in v2 format: ${SLOT:0:32}" + return 1 + } + PASSWORD=fsfsfs1234 homectl activate fscrypttest [[ "$(fscrypt_run0 fsfsfs1234 'head -n1 /home/fscrypttest/file1')" == "hello fscrypt" ]] systemctl stop user@"$(id -u fscrypttest)".service 2>/dev/null || true From 86e74db27700992df4b6b810533ab1cf0ee16568 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 14:35:09 +0200 Subject: [PATCH 1971/2155] some NEWS fixed by Claude --- NEWS | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index c8478c0ba63ae..dab0ddecfc598 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,7 @@ 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= + Specification (which allows the 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 @@ -21,7 +21,7 @@ CHANGES WITH 261 in spe: * 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 + forms (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 to select the user service manager scope, matching --system. @@ -36,9 +36,9 @@ CHANGES WITH 261 in spe: 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 also to the hardware CC registers (e.g. Intel - TDX RTMRs) using EFI_CC_MEASUREMENT_PROTOCOL. In particular, + * It was discovered that some of the events systemd-stub measures to + the TPM were not also measured to the hardware CC registers (e.g. + Intel TDX RTMRs) via EFI_CC_MEASUREMENT_PROTOCOL. In particular, devicetree, initrd, ucode addons and the UKI profile were only measured to the TPM. The missing measurements for CC have now been added; however, this changes the expected register values. This @@ -87,7 +87,7 @@ CHANGES WITH 261 in spe: * User session managers now support persisting user units' FD Stores by receiving FDs via the notify socket, and passing them down via - $LISTEN_FDS when the user session is restarted, when the + $LISTEN_FDS when the user session is restarted, if the 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' options are set in the user@.service unit. Combined with the LUO support, this lets user units persist state (e.g.: memfds) across @@ -105,7 +105,7 @@ CHANGES WITH 261 in spe: * A new RestrictFileSystemAccess= setting has been added that uses a BPF LSM program to restrict execution to only binaries that are - stored on a signed and verified dm-verity protected filesystem. + stored on a signed and verified dm-verity-protected filesystem. * The io.systemd.Unit.StartTransient() Varlink method has been added for invoking service units transiently. @@ -142,7 +142,7 @@ CHANGES WITH 261 in spe: is inserted in the last part of shutdown, in order to avoid tight boot loops. - IMDS (Cloud "Instance Metadata Service") Subsystem: + New IMDS (Cloud "Instance Metadata Service") Subsystem: * The hardware database now contains a new database hwdb.d/40-imds.hwdb that recognizes various established public clouds by their SMBIOS @@ -154,7 +154,7 @@ CHANGES WITH 261 in spe: * An IMDS subsystem has been added. Specifically, there's now systemd-imdsd which provides a local Varlink IPC API that makes IMDS services accessible to local programs. It provides both a relatively - low-level interface for querying arbitrary fields, and a higher level + low-level interface for querying arbitrary fields, and a higher-level interface for querying certain well-known keys in a generic way (which maps to various cloud-specific keys via the hwdb). The service can be pulled into the boot transaction automatically if a supported @@ -162,7 +162,7 @@ CHANGES WITH 261 in spe: functionality. This permits implementation of truly generic images that can interact with IMDS if available, but operate without if not. A tool systemd-imds acts as a client to systemd-imdsd and - imports various IMDS provided fields into local system credentials, + imports various IMDS-provided fields into local system credentials, which can then be consumed by later services. The acquired IMDS data is measured before being imported. @@ -170,9 +170,9 @@ CHANGES WITH 261 in spe: 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. + can be used to change the default mode to "locked" at build time. - TPM Subsystem: + Changes in the TPM Subsystem: * A new ConditionSecurity=measured-os unit condition has been added that checks whether the system was booted with measured-boot @@ -184,7 +184,7 @@ CHANGES WITH 261 in spe: TPM is implemented purely in software). * A new service systemd-tpm2-swtpm.service has been added that can run - the IBM "swtpm" as a software TPM, for use as (optional) automatic + the IBM "swtpm" as a software TPM, for use as an (optional) automatic fallback for systems that lack a physical TPM but where TPM functionality should be made available nonetheless. (This functionality must be enabled via systemd.tpm2_software_fallback= on @@ -223,8 +223,8 @@ CHANGES WITH 261 in spe: cannot be used to modify the resources which are used in the early boot. - * A kernel command line kill switch that disables systemd-sysext and - systemd-confext merging entirely is now honoured. + * A kernel command line kill switch that entirely disables + systemd-sysext and systemd-confext merging is now honoured. Changes in systemd-networkd and networkctl: @@ -288,9 +288,9 @@ CHANGES WITH 261 in spe: GPT partition table is detected for a sector size different from the native sector size of the device. (This typically happens if a Hybrid ISO9660/GPT disk image is booted as CDROM, where the native sector - size is 2048 but the GPT header uses 512 sector size). If this + size is 2048 but the GPT header uses a 512-byte sector size). If this happens then a systemd-loop@.service instance is automatically pulled - in via an udev rule that generates a loopback block device from the + in via a udev rule that generates a loopback block device from the discovered block device, exposing the device with the corrected sector size. Or in other words: booting a fully valid GPT disk image on a block device with a non-matching sector size will now just work, @@ -328,7 +328,7 @@ CHANGES WITH 261 in spe: keyboard will be automatically detected and set up without requiring user intervention. - * A new "extra" type-1 Boot Loader Specification stanza is parsed and + * A new "extra" Type #1 Boot Loader Specification stanza is parsed and used to deliver additional resources to a UKI without modifying its contents. This may be used to pass confext DDIs, sysext DDIs or encrypted credentials to a UKI kernel. The generic "addon" handling @@ -402,7 +402,7 @@ CHANGES WITH 261 in spe: * systemd-nspawn now supports persisting the payload's system manager FD Store by receiving FDs via the notify socket, and passing them - down via $LISTEN_FDS when the container is restarted, when the + down via $LISTEN_FDS when the container is restarted, if the 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' options are set in the unit inside which systemd-nspawn is running. Combined with the LUO support in PID1, this lets containers persist @@ -507,7 +507,7 @@ CHANGES WITH 261 in spe: binaries, eliminating the hard runtime dependency for systems that do not actually use it. - Dynamic Linking: + Changes in Dynamic Linking: * libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk and libcryptsetup are now consistently loaded via dlopen() From 477f84cf9191c0b5a139786de0c569bb918d0286 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Mon, 11 May 2026 12:58:05 +0200 Subject: [PATCH 1972/2155] udev-builtin-path_id: emit 'sf-' token for auxiliary sub-functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some drivers expose sub-functions (SFs) of a PCI Physical or Virtual Function as auxiliary devices that carry a stable 'sfnum' sysfs attribute — the user-defined sub-function number (e.g. the value passed to "devlink port add ... sfnum N"). The SF's leaf devices (uverbs, infiniband, net, ...) sit below this aux device in sysfs: /sys/devices/...///.../ Currently path_id walks straight past the aux device, so all leaf devices below an SF end up sharing ID_PATH=pci- with their parent PF or VF. For uverbs this causes a /dev/infiniband/by-path/ symlink collision, and for any other consumer of ID_PATH/ID_PATH_TAG it makes PF and SF (or VF and VF-SF) indistinguishable. Recognise the 'auxiliary' subsystem in path_id's walk: when the aux device exposes 'sfnum', prepend an 'sf-' token; otherwise leave it untokenised. The result for an SF whose parent PF is at PCI 0000:c1:00.0 and which was added with "sfnum 88" is: ID_PATH=pci-0000:c1:00.0-sf-88 ID_PATH_TAG=pci-0000_c1_00_0-sf-88 This is parallel to how net_id's NAMING_SUBFUNC scheme appends 'S' on top of the PF base name. Aux devices without 'sfnum' keep the pre-patch behaviour: the walk skips over them with no token. Existing ID_PATH values for PF and VF leaf devices are therefore unchanged, the change is purely additive, and there is no need to gate it behind a naming scheme (path_id itself is unversioned). Co-developed-by: Claude Opus 4.7 --- src/udev/udev-builtin-path_id.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index f5d33cd246663..8ae66b30f2676 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -711,6 +711,18 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { path_prepend(&path, "serio-%s", sysnum); parent = skip_subsystem(parent, "serio"); } + } else if (device_in_subsystem(parent, "auxiliary") > 0) { + unsigned sfnum; + + /* sfnum is the user-defined sub-function number (devlink port add ... + * sfnum N). Prepend it so an SF leaf device gets an ID_PATH distinct + * from its parent PF/VF; aux devices without 'sfnum' emit no token to + * preserve pre-patch ID_PATH values. */ + if (device_get_sysattr_unsigned(parent, "sfnum", &sfnum) >= 0) { + path_prepend(&path, "sf-%u", sfnum); + if (compat_path) + path_prepend(&compat_path, "sf-%u", sfnum); + } } else if (device_in_subsystem(parent, "pci") > 0) { path_prepend(&path, "pci-%s", sysname); if (compat_path) From 1f7d0a46744a37d02fdbd8927525a4190167e502 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Thu, 7 May 2026 16:52:02 +0200 Subject: [PATCH 1973/2155] udev-builtin-net_id: name auxiliary sub-function (SF) host network devices Some drivers (currently mlx5_core) expose sub-functions (SFs) of a PCI Physical Function as auxiliary devices. Each SF carries a host network interface that sits below the aux device in sysfs: /sys/devices/...//mlx5_core.sf./net/eth Because the network device's immediate parent is the aux device and not a PCI device, names_pci() bails out and these interfaces fall through to the kernel-assigned eth name, which is not stable across reboots, module reloads or topology changes. The naming applies when the SF network device's direct sysfs parent is the aux device that exposes sfnum, i.e. the kernel driver passes the aux device to SET_NETDEV_DEV(). mlx5_core does so. ice's ice_sf_cfg_netdev() currently passes the parent PF's PCI device, so ice SF network devices sit as siblings of the PF rather than below the aux device and fall outside this precondition; pending a kernel change in ice to mirror mlx5's SET_NETDEV_DEV(netdev, &adev->dev), they continue to receive the kernel-assigned name as they do today. The aux device exposes 'sfnum', the user-defined sub-function number (the value passed to "devlink port add ... sfnum N"), which is stable and unique within its parent PF. The aux device's direct sysfs parent is the PF's PCI device. Treat an SF host network device analogously to an SR-IOV VF host network device: walk to the parent PCI function, derive the base name from there, then append a single-character "S" suffix. Lowercase 's' is already taken (slot) and the existing grammar uses one character per token, so 'S' is the best option. E.g. for an SF whose parent PF is at PCI 0000:c1:00.0 and which was added with "sfnum 88": ID_NET_NAME_PATH=enp193s0f0S88 ID_NET_NAME_SLOT=enp193s0f0S88 This is parallel to how SR-IOV VFs get a "v" suffix on top of the parent PF's name. Gate the new behaviour behind NAMING_SUBFUNC and NAMING_V261. Document the new suffix in both the ID_NET_NAME_SLOT and ID_NET_NAME_PATH grammars in systemd.net-naming-scheme(7) and add a v261 history entry. Co-developed-by: Claude Opus 4.7 --- man/systemd.net-naming-scheme.xml | 42 +++++++++++++++ src/shared/netif-naming-scheme.c | 1 + src/shared/netif-naming-scheme.h | 4 ++ src/udev/udev-builtin-net_id.c | 90 +++++++++++++++++++++++++++---- 4 files changed, 128 insertions(+), 9 deletions(-) diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml index 1a024a0c305db..9887aa8701fce 100644 --- a/man/systemd.net-naming-scheme.xml +++ b/man/systemd.net-naming-scheme.xml @@ -170,6 +170,7 @@ ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]uport…[cconfig][iinterface] ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]vslot ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]rslot + ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]Ssfnum This property describes the slot position. Different schemes are used depending on the bus type, as described in the table below. In case of USB, BCMA, and SR-VIO devices, the full @@ -222,6 +223,11 @@ … rslot SR-IOV slot number + + + Ssfnum + Auxiliary sub-function (SF) number + @@ -247,6 +253,20 @@ is linked to the particular representor, with any leading zeros removed. The physical port name and the bus number are ignored. + Auxiliary sub-function (SF) network devices, where the network device's parent is an + auxiliary device exposing a sfnum sysfs attribute (currently mlx5_core SFs), + are named based on the underlying PCI function (the PF, or for VF-SF the PF behind the VF), + with a suffix of S and the user-defined sub-function number from + sfnum. This is analogous to how SR-IOV virtual function devices are named + with a v suffix. + + If the SF's parent PCI function is itself an SR-IOV virtual function (VF-SF), the + name is rooted at the PF and both suffixes are chained, with the v + suffix preceding the S suffix + (e.g. enp193s0f0v0S88). The PF, the VF, and the SF therefore form a + stable, hierarchical sequence regardless of the VF's underlying PCI bus/device/function + numbering. + In some configurations a parent PCI bridge of a given network controller may be associated with a slot. In such case we do not generate this device property to avoid possible naming conflicts. @@ -262,6 +282,7 @@ ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port] ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]bnumber ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]uport…[cconfig][iinterface] + ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]Ssfnum This property describes the device installation location. Different schemes are used depending on the bus type, as described in the table below. For BCMA and USB devices, PCI path @@ -310,6 +331,11 @@ USB port number chain + + Ssfnum + Auxiliary sub-function (SF) number + + @@ -575,6 +601,22 @@ + + + v261 + + Stable names are now generated for auxiliary sub-function (SF) network devices + (such as mlx5_core SFs). The name is built from the parent PCI Physical Function's + path with an Ssfnum suffix, where + sfnum is the user-defined SF number (the value passed to + devlink port add … sfnum N, exposed by the kernel as + the sfnum sysfs attribute on the auxiliary device). This is analogous to the + vN suffix used for SR-IOV virtual functions; for SFs + hosted on SR-IOV VFs (VF-SF), the two suffixes are chained on top of the PF's base name. + + + + Note that latest may be used to denote the latest scheme known (to this diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c index ddf3bdde9e84c..978399ca4139f 100644 --- a/src/shared/netif-naming-scheme.c +++ b/src/shared/netif-naming-scheme.c @@ -31,6 +31,7 @@ static const NamingScheme naming_schemes[] = { { "v258", NAMING_V258 }, { "v259", NAMING_V259 }, { "v260", NAMING_V260 }, + { "v261", NAMING_V261 }, /* … add more schemes here, as the logic to name devices is updated … */ EXTRA_NET_NAMING_MAP diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h index 52c462a6bbd23..1848cb570bc9a 100644 --- a/src/shared/netif-naming-scheme.h +++ b/src/shared/netif-naming-scheme.h @@ -44,6 +44,9 @@ typedef enum NamingSchemeFlags { NAMING_USE_INTERFACE_PROPERTY = 1 << 20, /* Use INTERFACE udev property, rather than sysname, when no renaming is requested. */ NAMING_DEVICETREE_ALIASES_WLAN = 1 << 21, /* Generate names from devicetree aliases for WLAN devices */ NAMING_MCTP = 1 << 22, /* Use "mc" prefix for MCTP devices */ + NAMING_SUBFUNC = 1 << 23, /* Generate names for auxiliary sub-function (SF) network + * devices (e.g. mlx5_core SFs), based on the parent PF's + * PCI path and the user-defined sfnum, with an "S" suffix. */ /* And now the masks that combine the features above */ NAMING_V238 = 0, @@ -67,6 +70,7 @@ typedef enum NamingSchemeFlags { NAMING_V258 = NAMING_V257 | NAMING_USE_INTERFACE_PROPERTY, NAMING_V259 = NAMING_V258 | NAMING_DEVICETREE_ALIASES_WLAN, NAMING_V260 = NAMING_V259 | NAMING_MCTP, + NAMING_V261 = NAMING_V260 | NAMING_SUBFUNC, EXTRA_NET_NAMING_SCHEMES diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index 2491484cf6074..d368fb2337230 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -165,6 +165,46 @@ static int get_virtfn_info(sd_device *pcidev, sd_device **ret_physfn_pcidev, cha return -ENOENT; } +static int get_subfunc_info(sd_device *aux_dev, sd_device **ret_parent_pcidev, char **ret_suffix) { + sd_device *parent; + unsigned sfnum; + char *suffix; + int r; + + assert(aux_dev); + assert(ret_parent_pcidev); + assert(ret_suffix); + + /* The auxiliary device must expose an 'sfnum' attribute. This is currently used by the + * mlx5_core driver for sub-function (SF) auxiliary devices. The sfnum is the user-defined + * stable identifier passed to "devlink port add ... sfnum N". */ + r = device_get_sysattr_unsigned_filtered(aux_dev, "sfnum", &sfnum); + if (r < 0) + return r; + + /* Walk one hop up: the auxiliary device's parent must be a PCI function. It can be either + * the PF directly, or an SR-IOV VF — mlx5 also supports SFs hosted on VFs (VF-SF), see + * Documentation/networking/representors.rst in the kernel tree. The VF case is handled by + * the existing virtfn logic in names_pci(), so here we just return the immediate PCI + * parent and a single "S" suffix piece. */ + r = sd_device_get_parent(aux_dev, &parent); + if (r < 0) + return r; + + r = device_in_subsystem(parent, "pci"); + if (r < 0) + return r; + if (r == 0) + return -ENODEV; + + if (asprintf(&suffix, "S%u", sfnum) < 0) + return log_oom_debug(); + + *ret_parent_pcidev = sd_device_ref(parent); + *ret_suffix = suffix; + return 0; +} + static int get_port_specifier(sd_device *dev, char **ret) { const char *phys_port_name; unsigned dev_port; @@ -928,24 +968,56 @@ static int names_devicetree(UdevEvent *event, const char *prefix) { static int names_pci(UdevEvent *event, const char *prefix) { sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); - _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL; - _cleanup_free_ char *virtfn_suffix = NULL; + _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL, *parent_pcidev = NULL; + _cleanup_free_ char *virtfn_suffix = NULL, *subfunc_suffix = NULL, *combined_suffix = NULL; + const char *suffix = NULL; assert(prefix); - /* check if our direct parent is a PCI device with no other bus in-between */ - if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0) + /* If the network device's direct parent is an auxiliary device that exposes a stable + * 'sfnum' attribute (currently mlx5_core sub-functions), peel off the SF identity into + * a 'S' suffix piece and pick up the aux device's underlying PCI function as + * 'parent'. The aux device is just a bump on the path; everything below — PF/VF + * resolution, slot/onboard lookup — proceeds the same way as for any PCI-rooted + * network device. */ + if (naming_scheme_has(NAMING_SUBFUNC)) { + sd_device *aux; + + if (get_matching_parent(dev, STRV_MAKE("auxiliary"), + /* skip_virtio= */ false, &aux) >= 0) + (void) get_subfunc_info(aux, &parent_pcidev, &subfunc_suffix); + } + + /* SF: parent is the aux device's PCI function. Otherwise the network device's direct + * parent must itself be a PCI device. */ + if (subfunc_suffix) + parent = parent_pcidev; + else if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0) return 0; - /* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */ + /* If parent is an SR-IOV VF, walk to the parent PF and add a 'v' suffix piece. The + * onboard BIOS label is intentionally not exposed for any "child function" (VF, SF, or + * VF-SF), since the label refers to the parent's physical port, not to a logical child. */ + bool is_child_function = !!subfunc_suffix; if (naming_scheme_has(NAMING_SR_IOV_V) && - get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) + get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) { parent = physfn_pcidev; - else + is_child_function = true; + } + if (!is_child_function) (void) names_pci_onboard_label(event, parent, prefix); - (void) names_pci_onboard(event, parent, prefix, virtfn_suffix); - (void) names_pci_slot(event, parent, prefix, virtfn_suffix); + /* Compose the final suffix in PF -> [VF ->] SF order, e.g. "v0", "S88", or "v0S88". */ + if (virtfn_suffix && subfunc_suffix) { + combined_suffix = strjoin(virtfn_suffix, subfunc_suffix); + if (!combined_suffix) + return log_oom_debug(); + suffix = combined_suffix; + } else + suffix = virtfn_suffix ?: subfunc_suffix; + + (void) names_pci_onboard(event, parent, prefix, suffix); + (void) names_pci_slot(event, parent, prefix, suffix); return 0; } From 781d86864c665e65609f8606758537b8d337378b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 14:41:45 +0200 Subject: [PATCH 1974/2155] update TODO --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.md b/TODO.md index eae2f1a16da9b..979bb06e103d1 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,10 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- do not pull dbus daemon/broker anymore, instead lazy activate it. Given how + the Varlinkifcation has progressed various non-desktop usescase might not + need D-Bus running at all anymore. + - Reuse the LoaderKeyboardLayout EFI variable/find_vconsole_keymap_for_bcp47() to pre-select kbd layout in systemd-firstboot. (Right now it's only used in vconsole-setup as fallback). From 48afde6751ced648ffbe1d29bbd3097871b91327 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Tue, 19 May 2026 14:10:32 +0200 Subject: [PATCH 1975/2155] NEWS: mention auxiliary sub-function (SF) network device naming Document user-visible effect of the new NAMING_SUBFUNC / v261 scheme. Co-developed-by: Claude Opus 4.7 --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index dab0ddecfc598..550fcb0eb1060 100644 --- a/NEWS +++ b/NEWS @@ -299,6 +299,11 @@ CHANGES WITH 261 in spe: GPT header (i.e. is partitioned) but the block device has partition table processing turned off. + * Persistent network interface naming has been extended to auxiliary + sub-function (SF) network devices (such as mlx5_core SFs), using an + "S" suffix appended to the parent PCI function's name (e.g. + "enp193s0f0S88"). + Changes in systemd-boot, systemd-stub, bootctl, ukify: * systemd-stub will now maintain a "boot secret" and pass it to the OS From fed82f2a68a846e911212003de8c59853a0eed93 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 19 May 2026 14:11:13 +0100 Subject: [PATCH 1976/2155] Update NEWS --- NEWS | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index dab0ddecfc598..20bd3551ebddd 100644 --- a/NEWS +++ b/NEWS @@ -606,7 +606,8 @@ CHANGES WITH 261 in spe: can hence only be contacted via Varlink that early. * JSON user database records may now optionally carry a birth date - field. homectl gained a new switch --birth-date= to set it. + field to close the gap with LDAP/OpenID/FreeIPA/etc. homectl gained + a new switch --birth-date= to set it. * systemd-vconsole-setup will now gracefully handle the case where the setfont/loadkeys tools are not installed, and skip operation cleanly @@ -1401,6 +1402,12 @@ CHANGES WITH 259: firstboot operation is requested. The invocation in systemd-homed-firstboot.service now turns both off by default. + * systemd-homed's fscrypt backend gained a new key sealing format that + aims to be more future-proof and robust against advances in brute + forcing techniques. Compatibiilty with the existing format is maintained, + but new enrollments will happen with the new format. Users enrolling + new systems must thus take care in not downgrading to an older version. + systemd-boot/systemd-stub: * systemd-boot now supports log levels. The level may be set via From 3f0e95b7a819031a7537a56adb0ff6855a15bccd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 14:42:04 +0200 Subject: [PATCH 1977/2155] vmspawn: port help() to help-util.h APIs --- src/vmspawn/vmspawn.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 7d520bbad5ca2..f39122e481475 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -41,6 +41,7 @@ #include "fs-util.h" #include "gpt.h" #include "group-record.h" +#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "hostname-util.h" @@ -222,15 +223,10 @@ STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-vmspawn", "1", &link); - if (r < 0) - return log_oom(); - static const char* const groups[] = { NULL, "Image", @@ -257,21 +253,18 @@ static int help(void) { (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, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] [ARGUMENTS...]"); + help_abstract("Spawn a command or OS in a virtual machine."); for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal()); + help_section(groups[i] ?: "Options"); r = table_print_or_warn(tables[i]); if (r < 0) return r; } - printf("\nSee the %s for details.\n", link); + help_man_page_reference("systemd-vmspawn", "1"); return 0; } From 881e4717c7981b274853309e68b39153e3b292f4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 19 May 2026 11:29:58 +0100 Subject: [PATCH 1978/2155] test: pin stress-ng --vm-method to a portable scalar method in TEST-55-OOMD The stress-ng "vm" stressor's default --vm-method=all cycles through every VM stress method, including newer ones that use AVX-512 instructions. On CPUs without AVX-512 support (e.g. AMD Zen 1 to 3) those methods crash with SIGILL. In testcase_oom_rulesets_lasting_sec all 10 stress-ng workers die within ~2.34 seconds, so by the time the 6 second sleep elapses the unit is already in failed/exit-code state and the assert_eq for ActiveState=active trips. Pin --vm-method=zero-one, a long-standing scalar method, on all four stress-ng --vm invocations in this test (the two transient services in testcase_oom_rulesets and testcase_oom_rulesets_lasting_sec, plus TEST-55-OOMD-testbloat.service and TEST-55-OOMD-testmunch.service) so the workers do not crash on AVX-512-less CPUs. testbloat, testmunch and testcase_oom_rulesets have not been observed failing because they get OOM-killed by systemd-oomd within ~1 to 2 seconds, before stress-ng cycles into an AVX-512 method, but they share the same latent flake. Journal excerpts from the failing run, TEST-55-OOMD-slowrule.service in testcase_oom_rulesets_lasting_sec (journalctl -o short-monotonic): [ 58.018676] stress-ng[1015]: invoked with '/usr/bin/stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep' by user 0 'root' [ 59.866072] stress-ng[1030]: stress-ng: debug: [1030] caught SIGILL, address 0x000055bd8d609140 (ILL_ILLOPN) [ 59.921050] stress-ng[1030]: stress-ng: debug: [1030] stress-ng: info: 0x000055bd8d609140:<62>71 fd 48 6f 2d 36 14 1c 00 c5 d1 ef ed 49 29 [ 59.929310] stress-ng[1015]: stress-ng: error: [1015] vm: [1021] terminated with an error, exit status=2 (stressor failed) [ 60.364111] stress-ng[1015]: stress-ng: info: [1015] failed: 10: vm (10) [ 60.364493] stress-ng[1015]: stress-ng: info: [1015] unsuccessful run completed in 2.34 secs [ 60.371290] systemd[1]: TEST-55-OOMD-slowrule.service: Main process exited, code=exited, status=2/INVALIDARGUMENT [ 60.371396] systemd[1]: TEST-55-OOMD-slowrule.service: Failed with result 'exit-code'. [ 64.017061] TEST-55-OOMD.sh[1010]: + assert_eq failed active [ 64.018167] TEST-55-OOMD.sh[1039]: FAIL: expected: 'active' actual: 'failed' The faulting bytes marked by stress-ng with <62> (the byte at the instruction pointer) decode unambiguously to an AVX-512 VMOVDQA64 using the 512-bit zmm13 register, confirmed independently by two disassemblers: $ printf '\x62\x71\xfd\x48\x6f\x2d\x36\x14\x1c\x00' | ndisasm -b 64 - 00000000 6271FD486F2D3614 vmovdqa64 zmm13,zword [rel 0x1c1440] -1C00 $ echo '0x62, 0x71, 0xfd, 0x48, 0x6f, 0x2d, 0x36, 0x14, 0x1c, 0x00' | \ llvm-mc -disassemble -triple=x86_64 -mattr=+avx512f .text vmovdqa64 1840182(%rip), %zmm13 The leading 0x62 is the EVEX prefix (exclusive to AVX-512 on this target), zmm13 is a 512-bit register that only exists when AVX-512 is implemented, and VMOVDQA64 requires the AVX512F (Foundation) CPUID feature (Intel SDM Vol 2C). Executing this on a CPU without AVX-512 raises #UD, delivered by the kernel as SIGILL/ILL_ILLOPN, matching the journal entry above. The same journal shows the kernel reporting "kvm_amd: TSC scaling supported", i.e. the guest is on AMD KVM, and AMD did not ship AVX-512 before Zen 4. Co-developed-by: Claude Opus 4.7 --- .../TEST-55-OOMD-testbloat.service | 5 ++++- .../TEST-55-OOMD-testmunch.service | 5 ++++- test/units/TEST-55-OOMD.sh | 13 ++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service index 70c87727c8b10..22bbd210e96f5 100644 --- a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service +++ b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service @@ -7,4 +7,7 @@ Description=Create a lot of memory pressure # to throttle and be put under heavy pressure. MemoryHigh=3M Slice=TEST-55-OOMD-workload.slice -ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep +# Pin --vm-method to a portable method (zero-one): the default 'all' cycles +# through methods, including newer ones using AVX-512 instructions that SIGILL +# on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. +ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=zero-one diff --git a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service index 79bd01838e142..06eea10b79a55 100644 --- a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service +++ b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service @@ -5,4 +5,7 @@ Description=Create some memory pressure [Service] MemoryHigh=12M Slice=TEST-55-OOMD-workload.slice -ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep +# Pin --vm-method to a portable method (zero-one): the default 'all' cycles +# through methods, including newer ones using AVX-512 instructions that SIGILL +# on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. +ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=zero-one diff --git a/test/units/TEST-55-OOMD.sh b/test/units/TEST-55-OOMD.sh index 6689bbdd733c9..b7311e83dca4e 100755 --- a/test/units/TEST-55-OOMD.sh +++ b/test/units/TEST-55-OOMD.sh @@ -365,11 +365,14 @@ EOF systemctl reload systemd-oomd.service - # Run a transient service with OOMRules=testrule that generates memory pressure + # Run a transient service with OOMRules=testrule that generates memory pressure. + # Pin --vm-method to a portable method (zero-one): the default 'all' cycles + # through every method, including newer ones using AVX-512 instructions that + # SIGILL on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. (! systemd-run --wait --unit=TEST-55-OOMD-testrules \ -p MemoryHigh=3M \ -p OOMRules=testrule \ - stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep) + stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep --vm-method=zero-one) # Verify in the journal that the rule triggered journalctl --sync @@ -454,10 +457,14 @@ EOF # Start the unit without --wait so we can check mid-run state. The # stress-ng timeout bounds the test if anything goes wrong. + # Pin --vm-method to a portable method (zero-one): the default 'all' cycles + # through every method, including newer ones using AVX-512 instructions that + # SIGILL on CPUs without AVX-512 (e.g. AMD Zen 1-3) and would cause stress-ng + # to exit before the 6 s wait below elapses, failing the ActiveState check. systemd-run --unit=TEST-55-OOMD-slowrule \ -p MemoryHigh=3M \ -p OOMRules=slowrule \ - stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep + stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep --vm-method=zero-one # Wait long enough for oomd's 1s rule-check loop to evaluate the condition # many times. With LastingSec=1h the kill must not fire. From b13cc855e2053fc72991f8d729783b08df82b7a0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 13:56:57 +0000 Subject: [PATCH 1979/2155] dependabot: Ignore mkosi It doesn't update MinimumVersion= in mkosi.conf which breaks tools/fetch-mkosi.py. --- .github/dependabot.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cb8f6ab23e13f..37b0fcc35379f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,8 +10,10 @@ updates: actions: patterns: - "*" - exclude-patterns: - - "systemd/mkosi" + ignore: + # We pin systemd/mkosi to a specific commit on purpose so the CI environment stays + # reproducible across runs; bump it with tools/fetch-mkosi.py when we need a newer mkosi. + - dependency-name: "systemd/mkosi" cooldown: default-days: 7 open-pull-requests-limit: 2 From d4fad9597c2fe7c1017af286a71c8018622b45fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 19 May 2026 16:01:21 +0000 Subject: [PATCH 1980/2155] 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% (270 of 270 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/tr/ Translation: systemd/main --- po/tr.po | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/po/tr.po b/po/tr.po index 788cc0b84975f..4c1c03ad0b9d1 100644 --- a/po/tr.po +++ b/po/tr.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" +"PO-Revision-Date: 2026-05-19 16:01+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.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -918,12 +918,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "Yerel sanal makineleri ve kapsayıcıları incele" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"Yerel sanal makineleri ve kapsayıcıları incelemek için kimlik doğrulaması " +"gereklidir." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -961,13 +963,15 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "Yerel sanal makine ve kapsayıcı kalıplarını incele" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"Yerel sanal makineler ve kapsayıcı kalıplarını incelemek için kimlik " +"doğrulaması gereklidir." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From e2a49a7fae491199b7ed8ec925ac152b20b7fc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9rico=20Monteiro?= Date: Tue, 19 May 2026 16:01:21 +0000 Subject: [PATCH 1981/2155] 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% (270 of 270 strings) Co-authored-by: Américo Monteiro Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pt/ Translation: systemd/main --- po/pt.po | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/po/pt.po b/po/pt.po index 74f95a518906e..03f5330dcdbb1 100644 --- a/po/pt.po +++ b/po/pt.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-05 22:10+0000\n" +"PO-Revision-Date: 2026-05-19 16:01+0000\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \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 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -907,12 +907,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "Inspecionar máquinas e contentores virtuais locais" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"É necessária autenticação para inspecionar máquinas e contentores virtuais " +"locais." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -949,13 +951,15 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "Inspecionar imagens de máquinas e contentores virtuais locais" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"É necessária autenticação para inspecionar imagens de máquinas e contentores " +"virtuais locais." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From f424d7984ebd16c61048d2389c6d25691e3c73f0 Mon Sep 17 00:00:00 2001 From: Luna Jernberg Date: Tue, 19 May 2026 16:01:22 +0000 Subject: [PATCH 1982/2155] po: Translated using Weblate (Swedish) Currently translated at 100.0% (270 of 270 strings) Co-authored-by: Luna Jernberg Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/sv/ Translation: systemd/main --- po/sv.po | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/po/sv.po b/po/sv.po index b6e321e5fad26..cabca1835d620 100644 --- a/po/sv.po +++ b/po/sv.po @@ -9,12 +9,13 @@ # Anders Jonsson , 2022, 2024. # Weblate Translation Memory , 2024. # Daniel Nylander , 2026. +# Luna Jernberg , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" -"Last-Translator: Daniel Nylander \n" +"PO-Revision-Date: 2026-05-19 16:01+0000\n" +"Last-Translator: Luna Jernberg \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -22,7 +23,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.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -881,12 +882,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "Inspektera lokala virtuella maskiner och behållare" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"Autentisering krävs för att inspektera lokala virtuella maskiner och " +"behållare." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -923,13 +926,15 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "Inspektera lokala virtuella maskiner och behållaravbilder" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"Autentisering krävs för att inspektera lokala virtuella maskiner och " +"behållaravbilder." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From 7d8432485b5b5c3ffce04fb0b6f72671c20cda3f Mon Sep 17 00:00:00 2001 From: Yuri Chornoivan Date: Tue, 19 May 2026 16:01:22 +0000 Subject: [PATCH 1983/2155] po: Translated using Weblate (Ukrainian) Currently translated at 100.0% (270 of 270 strings) Co-authored-by: Yuri Chornoivan Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/uk/ Translation: systemd/main --- po/uk.po | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/po/uk.po b/po/uk.po index 4a5999d0c98bb..e1b72726651f7 100644 --- a/po/uk.po +++ b/po/uk.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" +"PO-Revision-Date: 2026-05-19 16:01+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.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -912,12 +912,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "Інспектування локальних віртуальних машин і контейнерів" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"Для інспектування локальних віртуальних машин і контейнерів слід пройти " +"розпізнавання." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -955,13 +957,15 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "Інспектування локальної віртуальної машини і образів контейнерів" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"Для інспектування локальної віртуальної машини і образів контейнерів слід " +"пройти розпізнавання." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From 48bcf96d3b34d3a4c99b0d68b6f6f0ec8f2874d9 Mon Sep 17 00:00:00 2001 From: Marek Adamski Date: Tue, 19 May 2026 16:01:23 +0000 Subject: [PATCH 1984/2155] po: Translated using Weblate (Polish) Currently translated at 100.0% (270 of 270 strings) Co-authored-by: Marek Adamski Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pl/ Translation: systemd/main --- po/pl.po | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/po/pl.po b/po/pl.po index be4476b2abbb3..7dd2d956d9cfd 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-04-03 19:58+0000\n" +"PO-Revision-Date: 2026-05-19 16:01+0000\n" "Last-Translator: Marek Adamski " "\n" "Language-Team: Polish =2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.16.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -909,12 +909,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "Badanie lokalnych maszyn wirtualnych i kontenerów" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"Wymagane jest uwierzytelnienie, aby zbadać lokalne maszyny wirtualne i " +"kontenery." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -952,13 +954,15 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "Badanie obrazów lokalnych maszyn wirtualnych i kontenerów" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"Wymagane jest uwierzytelnienie, aby zbadać obrazy lokalnych maszyn " +"wirtualnych i kontenerów." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From febfa1342ec3189a0817e83335d65d3f95d5c486 Mon Sep 17 00:00:00 2001 From: Temuri Doghonadze Date: Tue, 19 May 2026 16:01:24 +0000 Subject: [PATCH 1985/2155] po: Translated using Weblate (Georgian) Currently translated at 100.0% (270 of 270 strings) Co-authored-by: Temuri Doghonadze Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ka/ Translation: systemd/main --- po/ka.po | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/po/ka.po b/po/ka.po index 410023021b9d3..ae9beb2cdff93 100644 --- a/po/ka.po +++ b/po/ka.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" +"PO-Revision-Date: 2026-05-19 16:01+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.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -873,12 +873,14 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "ლოკალური ვირტუალური მანქანებისა და კონტეინერების ტექნიკური დათვალიერება" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ტექნიკურ დათვალიერებას " +"ავთენტიკაცია სჭირდება." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -916,12 +918,16 @@ msgstr "" #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ასლის ფაილების ტექნიკური " +"შემოწმება" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ასლის ფაილების ტექნიკური " +"შემოწმებას ავთენტიკაცია სჭირდება." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From 94bbfd2a34dcbb38dc8b1d9596736ba30ef61a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 15:08:45 +0000 Subject: [PATCH 1986/2155] build(deps): bump the actions group with 2 updates Bumps the actions group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials). Updates `github/codeql-action` from 4.35.2 to 4.35.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/95e58e9a2cdfd71adc6e0353d5c52f41a045d225...68bde559dea0fdcac2102bfdf6230c5f70eb485e) Updates `aws-actions/configure-aws-credentials` from 6.1.0 to 6.1.1 - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/ec61189d14ec14c8efccab744f656cffd0e33f37...d979d5b3a71173a29b74b5b88418bfda9437d885) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: aws-actions/configure-aws-credentials dependency-version: 6.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/cifuzz.yml | 2 +- .github/workflows/claude-review.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 41f06cf8f1a56..beac98d3e7ea5 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -67,7 +67,7 @@ jobs: path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 516a4087b52e9..1f9d9ac6f34c1 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -179,7 +179,7 @@ jobs: sudo apt-get update && sudo apt-get install -y bubblewrap socat - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 + uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} role-session-name: GitHubActions-Claude-${{ github.run_id }} From f7e6645ac64043f57363e37b0f8c45f7efaeaee7 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 28 Apr 2026 11:14:48 +0000 Subject: [PATCH 1987/2155] networkd: add RouteTable= to [DHCPv6] section Allow users to allow DHCPv6 unreachable/blackhole routes (installed for delegated prefixes) into a specific routing table, analogous to the existing RouteTable= in [DHCPv4] and [IPv6AcceptRA]. The config parser config_parse_dhcp_or_ra_route_table() is extended with an AF_UNSPEC ltype discriminator for DHCPv6 (AF_INET6 is already taken by NDISC/RA). link_get_dhcp6_route_table() follows the same pattern as link_get_dhcp4_route_table() and link_get_ndisc_route_table(), falling back to the VRF table when not explicitly set. In dhcp_request_unreachable_route(), the table is applied only for NETWORK_CONFIG_SOURCE_DHCP6 routes (the uplink unreachable aggregates), not DHCP_PD routes (per-subnet routes on downstream interfaces), matching the intent of the feature. The !route->table_set guard avoids overriding a table already set by the route code. --- man/systemd.network.xml | 17 ++++++++++++++++ src/network/networkd-dhcp-common.c | 20 ++++++++++++++++--- src/network/networkd-dhcp-common.h | 1 + src/network/networkd-dhcp-prefix-delegation.c | 3 +++ src/network/networkd-network-gperf.gperf | 7 ++++--- src/network/networkd-network.c | 1 + src/network/networkd-network.h | 2 ++ 7 files changed, 45 insertions(+), 6 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 554d8da8ef606..d62e4d62329ed 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3188,6 +3188,23 @@ MultiPathRoute=2001:db8::1@eth0 + + RouteTable=num + + The table identifier for routes installed by the DHCPv6 client, e.g. unreachable + routes for delegated prefixes. Takes one of the predefined names default, + main, and local, names defined in + RouteTable= in + networkd.conf5, + or a number between 1…4294967295. + + When used in combination with VRF=, the VRF's routing table is + used when this parameter is not specified. + + + + + RapidCommit= diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index d2e51e7e91bc3..3368145981f9f 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -24,6 +24,7 @@ #include "networkd-manager.h" #include "networkd-network.h" #include "networkd-route-util.h" +#include "networkd-util.h" #include "parse-util.h" #include "set.h" #include "socket-util.h" @@ -60,6 +61,15 @@ uint32_t link_get_ndisc_route_table(Link *link) { return link_get_vrf_table(link); } +uint32_t link_get_dhcp6_route_table(Link *link) { + assert(link); + assert(link->network); + + if (link->network->dhcp6_route_table_set) + return link->network->dhcp6_route_table; + return link_get_vrf_table(link); +} + bool link_dhcp_enabled(Link *link, int family) { assert(link); assert(IN_SET(family, AF_INET, AF_INET6)); @@ -565,7 +575,7 @@ int config_parse_dhcp_or_ra_route_table( assert(filename); assert(lvalue); - assert(IN_SET(ltype, AF_INET, AF_INET6)); + assert(IN_SET(ltype, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6, NETWORK_CONFIG_SOURCE_NDISC)); assert(rvalue); r = manager_get_route_table_from_string(network->manager, rvalue, &rt); @@ -576,11 +586,15 @@ int config_parse_dhcp_or_ra_route_table( } switch (ltype) { - case AF_INET: + case NETWORK_CONFIG_SOURCE_DHCP4: network->dhcp_route_table = rt; network->dhcp_route_table_set = true; break; - case AF_INET6: + case NETWORK_CONFIG_SOURCE_DHCP6: + network->dhcp6_route_table = rt; + network->dhcp6_route_table_set = true; + break; + case NETWORK_CONFIG_SOURCE_NDISC: network->ndisc_route_table = rt; network->ndisc_route_table_set = true; break; diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 6be8bcd6dc374..b6385492c38a3 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -41,6 +41,7 @@ typedef struct DUID { uint32_t link_get_dhcp4_route_table(Link *link); uint32_t link_get_ndisc_route_table(Link *link); +uint32_t link_get_dhcp6_route_table(Link *link); bool link_dhcp_enabled(Link *link, int family); static inline bool link_dhcp4_enabled(Link *link) { diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index e734b0171d37a..85c52334726be 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -14,6 +14,7 @@ #include "in-addr-prefix-util.h" #include "networkd-address.h" #include "networkd-address-generation.h" +#include "networkd-dhcp-common.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp6.h" #include "networkd-link.h" @@ -851,6 +852,8 @@ static int dhcp_request_unreachable_route( route->protocol = RTPROT_DHCP; route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; + if (source == NETWORK_CONFIG_SOURCE_DHCP6 && !route->table_set) + route->table = link_get_dhcp6_route_table(link); r = route_adjust_nexthops(route, link); if (r < 0) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a5afeed1cb43f..9e6eb7c799572 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -283,7 +283,7 @@ DHCPv4.IAID, config_parse_iaid, DHCPv4.DUIDType, config_parse_network_duid_type, 0, 0 DHCPv4.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 DHCPv4.RouteMetric, config_parse_dhcp_route_metric, AF_INET, 0 -DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 +DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP4, 0 DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCPv4.ServerPort, config_parse_uint16, 0, offsetof(Network, dhcp_port) @@ -332,6 +332,7 @@ DHCPv6.RapidCommit, config_parse_bool, DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release) DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context) +DHCPv6.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP6, 0 IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect) IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway) IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix) @@ -346,7 +347,7 @@ IPv6AcceptRA.UseHopLimit, config_parse_bool, IPv6AcceptRA.UseReachableTime, config_parse_bool, 0, offsetof(Network, ndisc_use_reachable_time) IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ndisc_use_retransmission_time) IPv6AcceptRA.DHCPv6Client, config_parse_ndisc_start_dhcp6_client, 0, offsetof(Network, ndisc_start_dhcp6_client) -IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0 +IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_NDISC, 0 IPv6AcceptRA.RouteMetric, config_parse_ndisc_route_metric, 0, 0 IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ndisc_quickack) IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ndisc_use_captive_portal) @@ -669,7 +670,7 @@ DHCP.IAID, config_parse_iaid, DHCP.DUIDType, config_parse_network_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 DHCP.RouteMetric, config_parse_dhcp_route_metric, AF_UNSPEC, 0 -DHCP.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 +DHCP.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP4, 0 DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 17cf78db37af0..dfe524d150d23 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -424,6 +424,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID, .dhcp6_send_release = true, .dhcp6_pd_prefix_route_type = RTN_UNREACHABLE, + .dhcp6_route_table = RT_TABLE_MAIN, .dhcp_pd = -1, .dhcp_pd_announce = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index d1765a2bf46d3..625b3b9a08c31 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -205,6 +205,8 @@ typedef struct Network { char *dhcp6_netlabel; bool dhcp6_send_release; NFTSetContext dhcp6_nft_set_context; + uint32_t dhcp6_route_table; + bool dhcp6_route_table_set; /* DHCP Server Support */ bool dhcp_server; From e7cd836dcffb5f85d66a156904fc68f8b654a290 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 19 May 2026 14:51:56 +0200 Subject: [PATCH 1988/2155] resolve: cap pre-allocation for questions/RRs Since [0] and [1] questions & answer RRs from the incoming packets are parsed into a hashmap to speed things up. The hashmaps are even pre-allocated to speed things up even more, but there's one caveat - the size for the pre-allocation comes from one or more fields from the incoming packets that are under sender's control. This can be abused by a malicious DNS server which can send a packet with a spoofed QDCOUNT (for question packets) or ANCOUNT/NSCOUNT/ARCOUNT (for answer packets). The limit of the final value in both cases is 64K. This value is then used to pre-allocate the hashmap (via set_reserve()/ordered_set_reserve(), where the caller also multiplies the input value by 2 in both cases), which in turns calls resize_buckets() that memzero()s the pre-allocated area, so all the pages are faulted in, showing in process' RSS. Each such spoofed packet then can translate into a ~4 MiB allocation in the systemd-resolved process, which doesn't sound that bad. However, this can be further amplified if the spoofed packet ends up in resolved's cache. So, if the spoofed packet contains one valid A record and then an OPT record with a spoofed ARCOUNT, the whole packet ends up in the cache that can hold 4K of entries, which can eventually cause resolved to keep up to 16 GiB of memory just for the cache (and thanks to the memzero() above it's all RSS). Note that all this requires someone with enough privileges to configure resolved to actually point to such malicious DNS server or it could come from a malicious DHCP server on the network. This could also get exploited via LLMNR, but in thas case an attacker would have to match an ID of a valid transaction for the packet to end up in resolved's cache. For example, with a malicious DNS already in resolved configuration: $ resolvectl dns eth0 Link 2 (eth0): 192.168.99.1:5354 Filling resolved's cache: $ for i in {0..4200}; do resolvectl query test-$i.example.com; done ... test-4200.example.com: 192.0.2.1 -- link: dummy0 -- Information acquired via protocol DNS in 1.6ms. -- Data is authenticated: no; Data was acquired via local or encrypted transport: no -- Data from: network Yields following memory increase: $ while :; do grep VmRSS /proc/$(pidof systemd-resolved)/status; sleep 1; done VmRSS: 14280 kB VmRSS: 14280 kB ... VmRSS: 403352 kB VmRSS: 1017976 kB VmRSS: 1603876 kB VmRSS: 2202028 kB ... VmRSS: 16795724 kB VmRSS: 16795724 kB In my testing I also noticed one annoyance - after certain threshold the RSS increase persisted even after the malicious entries were evicted from the cache (or flushed via `resolvectl flush-caches`). This was most likely due to mmap_threshold getting bumped to > 4 MiB and neither cache eviction nor flush-caches call malloc_trim(0) (via sd_event_trim_memory() or similar). To mitigate this, let's cap the pre-allocation to a maximum number of records the given packet body can realistically contain. If the minimum size would be, for whatever unlikely reason, not enough, nothing serious would happen - the hashmap would still get resized automatically by resize_buckets(), it'd be just slightly slower. [0] ae45e1a3832fbb6c96707687e42f0b4aaab52c9b [1] 2d34cf0c16dd8fa71fb593e65ce4734cb61d9170 --- src/shared/dns-packet.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index c8c54e7988afc..4fdef2b570a5f 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -2463,8 +2463,15 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) if (!keys) return log_oom(); - r = set_reserve(keys, n * 2); /* Higher multipliers give slightly higher efficiency through - * hash collisions, but the gains quickly drop off after 2. */ + /* Pre-allocate the question hashmap, but cap the pre-allocation to a number of questions the + * packet can realistically contain. That is, pick the minimal value from the claimed number + * of questions (n) and a maximum number of potential questions the remaining packet data can + * actually contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U + * is the minimum size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). + * + * Note for the multiplication: higher multipliers give slightly higher efficiency through + * hash collisions, but the gains quickly drop off after 2. */ + r = set_reserve(keys, MIN(n, (p->size - p->rindex) / 5U) * 2); if (r < 0) return r; @@ -2510,7 +2517,12 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { if (n == 0) return 0; - answer = dns_answer_new(n); + /* Pre-allocate the answer hashmap, but cap the pre-allocation to a number of RRs the packet can + * realistically contain. That is, pick the minimal value from the claimed number of RRs (n) and a + * maximum number of potential RRs the remaining packet data can actually contain: p->size - + * p->rindex are the remaining unread bytes in the packet, and the 11U is the minimum size of each RR + * - 1 (NAME) + 2 (TYPE) + 2 (CLASS) + 4 (TTL) + 2 (RDLENGTH). */ + answer = dns_answer_new(MIN(n, (p->size - p->rindex) / 11U)); if (!answer) return -ENOMEM; From 35bf1c826454bfcaa3c93cae950d36fa216ac3ce Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 13:52:14 +0000 Subject: [PATCH 1989/2155] mkosi: update mkosi ref to 77fce77807a9a92bc37edc8f1c967102e6236d94 * 77fce77807 apk: Implement repository_key_fetch for the postmarketOS distribution * 7068ed49ab postmarketos: Add ruff to tools tree * dea4b6bfc8 Add newline when writing machine id into /etc/machine-id * 944b775d40 tools: add libtss2-tcti-device0 to opensuse tools tree * d856d65d3b mkosi-initrd: Also add cryptsetup-libs explicitly to the initrd * 1cc967c5b3 mkosi-initrd: Trim orphaned GPU/audio modules, add ACPI platform attrs * a3e95a7c29 mkosi-tools: Add fish to misc profile * 76b02d1f84 mkosi-tools: Add jujutsu to misc profile * 0afe4cd254 mkosi-tools: Move gh to misc profile * 9077634bad mkosi-tools: Add cryptsetup-libs to centos/fedora/opensuse * 82846347af box: Drop background tinting * 3e50b97101 mkosi-tools: Add libfido2 * 78c2784827 vmspawn: Use --ephemeral rather than copy_ephemeral() * dc801b00a3 Added second call to update kerneltype after kernel is defined * 0c5cc04a8b vmspawn: Forward journal-remote settings to vmspawn * 2518468c65 nspawn: Use --forward-journal instead of running journal-remote ourselves * d2b798d00c apk: skip removal of packages that aren't installed --- .github/workflows/coverage.yml | 6 +++++- .github/workflows/linter.yml | 2 +- .github/workflows/mkosi.yml | 6 +++++- mkosi/mkosi.conf | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ef366657fc9f7..7a968ac95bbb3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b + - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location @@ -129,6 +129,10 @@ jobs: --max-lines 300 \ --quiet + - name: Fix journal ownership + if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs + - name: Archive failed test journals uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 048391836ebe2..36c0bc9e3384c 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,7 +40,7 @@ jobs: GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false - - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b + - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 - name: Check that tabs are not used in Python code run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 55be6ab86b850..5ed4907665828 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -169,7 +169,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@9a28ad20bbea61894ea7b971d318a71f4374cf3b + - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location @@ -318,6 +318,10 @@ jobs: --num-processes "$(($(nproc) - 1))" \ "${MAX_LINES[@]}" + - name: Fix journal ownership + if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs + - name: Archive failed test journals uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index ca5c061079b85..b807fdb3c50b5 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later [Config] -MinimumVersion=commit:66d51024b7149f40be4702e84275c936373ace97 +MinimumVersion=commit:77fce77807a9a92bc37edc8f1c967102e6236d94 Dependencies= minimal-base minimal-0 From 7ef3b981029cae86c7211dd52c31c59dd4654d29 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 08:32:26 +0000 Subject: [PATCH 1990/2155] bpf-util; Add back caching on error/success Follow up for 7d822ca8 --- src/shared/bpf-util.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/shared/bpf-util.c b/src/shared/bpf-util.c index 03f3281b07548..ea703ce589106 100644 --- a/src/shared/bpf-util.c +++ b/src/shared/bpf-util.c @@ -78,7 +78,14 @@ static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_lis int dlopen_bpf(int log_level) { static void *bpf_dl = NULL; - int r; + static int cached = 0; + int r = -ENOENT; + + if (bpf_dl) + return 1; /* Already loaded */ + + if (cached < 0) + return cached; /* Already tried, and failed. */ SD_ELF_NOTE_DLOPEN( "bpf", @@ -135,8 +142,8 @@ int dlopen_bpf(int log_level) { } REENABLE_WARNING; if (r < 0) - return log_full_errno(in_initrd() ? LOG_DEBUG : log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), - "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled."); + return cached = log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r, + "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled."); /* Version-specific symbols: bpf_create_map exists only in libbpf < 1.0; bpf_map_create and * bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. Unresolved prototypes keep From e62fd8106fdc8a9542928164156e82aa86811c0b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 08:37:56 +0000 Subject: [PATCH 1991/2155] libcrypt-util: Clean up dlopen_libcrypt() --- src/shared/libcrypt-util.c | 44 ++++++++++++++------------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 531684d64ef64..3459bb6ee3d0b 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -15,11 +15,16 @@ #include "strv.h" #if HAVE_LIBCRYPT +#ifdef __GLIBC__ static void *libcrypt_dl = NULL; - static DLSYM_PROTOTYPE(crypt_gensalt_ra) = NULL; static DLSYM_PROTOTYPE(crypt_preferred_method) = NULL; static DLSYM_PROTOTYPE(crypt_ra) = NULL; +#else +static DLSYM_PROTOTYPE(crypt_gensalt_ra) = missing_crypt_gensalt_ra; +static DLSYM_PROTOTYPE(crypt_preferred_method) = missing_crypt_preferred_method; +static DLSYM_PROTOTYPE(crypt_ra) = missing_crypt_ra; +#endif int make_salt(char **ret) { const char *e; @@ -128,10 +133,10 @@ int dlopen_libcrypt(int log_level) { #if HAVE_LIBCRYPT #ifdef __GLIBC__ static int cached = 0; - int r; + int r = -ENOENT; if (libcrypt_dl) - return 0; /* Already loaded */ + return 1; /* Already loaded */ if (cached < 0) return cached; /* Already tried, and failed. */ @@ -145,36 +150,19 @@ int dlopen_libcrypt(int log_level) { 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); + r = dlopen_many_sym_or_warn( + &libcrypt_dl, soname, LOG_DEBUG, + DLSYM_ARG(crypt_gensalt_ra), + DLSYM_ARG(crypt_preferred_method), + DLSYM_ARG(crypt_ra)); + if (r >= 0) 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; + return cached = log_full_errno(log_level, r, "Failed to load libcrypt: %m"); #endif - return 0; + return 1; #else return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "libcrypt support is not compiled in."); From d158595dc7e873b40d7c20bda64ebe7eb67bae69 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 16 May 2026 15:50:31 +0000 Subject: [PATCH 1992/2155] tree-wide: move static dl handles into their dlopen_*() functions Each dlopen_*() wrapper kept its dl handle as a file-scope 'static void *xxx_dl = NULL;' even though only the wrapper itself ever referenced it. Move each one inside the corresponding function so its scope matches its actual use, leaving the rest of each translation unit free of the unused file-scope name. In pcre2-util.c the assert(pcre2_dl) in pattern_matches_and_log() becomes assert(sym_pcre2_match), which carries the same invariant (pattern_compile_and_log() ran dlopen_pcre2()). --- src/basic/compress.c | 20 ++++++++++---------- src/basic/gcrypt-util.c | 4 ++-- src/libsystemd/sd-bus/test-bus-marshal.c | 6 ++++-- src/locale/xkbcommon-util.c | 4 ++-- src/shared/acl-util.c | 4 ++-- src/shared/apparmor-util.c | 4 ++-- src/shared/blkid-util.c | 4 ++-- src/shared/crypto-util.c | 4 ++-- src/shared/cryptsetup-util.c | 3 +-- src/shared/curl-util.c | 4 ++-- src/shared/elf-util.c | 5 ++--- src/shared/fdisk-util.c | 4 ++-- src/shared/gnutls-util.c | 4 ++-- src/shared/libarchive-util.c | 3 +-- src/shared/libaudit-util.c | 4 ++-- src/shared/libcrypt-util.c | 2 +- src/shared/libfido2-util.c | 3 +-- src/shared/libmount-util.c | 4 ++-- src/shared/microhttpd-util.c | 4 ++-- src/shared/module-util.c | 4 ++-- src/shared/pam-util.c | 4 ++-- src/shared/password-quality-util-passwdqc.c | 4 ++-- src/shared/password-quality-util-pwquality.c | 4 ++-- src/shared/pcre2-util.c | 6 +++--- src/shared/pkcs11-util.c | 4 ++-- src/shared/qrcode-util.c | 3 +-- src/shared/seccomp-util.c | 4 ++-- src/shared/selinux-util.c | 4 ++-- src/shared/ssl-util.c | 4 ++-- src/shared/tpm2-util.c | 12 +++++++----- 30 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/basic/compress.c b/src/basic/compress.c index 8386bdb1b1df7..b3598053782e7 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -40,8 +40,6 @@ #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; @@ -62,8 +60,6 @@ static inline void lzma_end_wrapper(lzma_stream *ls) { #endif #if HAVE_LZ4 -static void *lz4_dl = NULL; - static DLSYM_PROTOTYPE(LZ4F_compressBegin) = NULL; static DLSYM_PROTOTYPE(LZ4F_compressBound) = NULL; static DLSYM_PROTOTYPE(LZ4F_compressEnd) = NULL; @@ -86,8 +82,6 @@ static const LZ4F_preferences_t lz4_preferences = { #endif #if HAVE_ZSTD -static void *zstd_dl = NULL; - static DLSYM_PROTOTYPE(ZSTD_CCtx_setParameter) = NULL; static DLSYM_PROTOTYPE(ZSTD_compress) = NULL; static DLSYM_PROTOTYPE(ZSTD_compressStream2) = NULL; @@ -120,8 +114,6 @@ static int zstd_ret_to_errno(size_t ret) { #endif #if HAVE_ZLIB -static void *zlib_dl = NULL; - static DLSYM_PROTOTYPE(deflateInit2_) = NULL; static DLSYM_PROTOTYPE(deflate) = NULL; static DLSYM_PROTOTYPE(deflateEnd) = NULL; @@ -139,8 +131,6 @@ static inline void inflateEnd_wrapper(z_stream *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; @@ -286,6 +276,8 @@ Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_ int dlopen_xz(int log_level) { #if HAVE_XZ + static void *lzma_dl = NULL; + SD_ELF_NOTE_DLOPEN( "lzma", "Support lzma compression in journal and coredump files", @@ -309,6 +301,8 @@ int dlopen_xz(int log_level) { int dlopen_lz4(int log_level) { #if HAVE_LZ4 + static void *lz4_dl = NULL; + SD_ELF_NOTE_DLOPEN( "lz4", "Support lz4 compression in journal and coredump files", @@ -341,6 +335,8 @@ int dlopen_lz4(int log_level) { int dlopen_zstd(int log_level) { #if HAVE_ZSTD + static void *zstd_dl = NULL; + SD_ELF_NOTE_DLOPEN( "zstd", "Support zstd compression in journal and coredump files", @@ -374,6 +370,8 @@ int dlopen_zstd(int log_level) { int dlopen_zlib(int log_level) { #if HAVE_ZLIB + static void *zlib_dl = NULL; + SD_ELF_NOTE_DLOPEN( "zlib", "Support gzip compression and decompression", @@ -397,6 +395,8 @@ int dlopen_zlib(int log_level) { int dlopen_bzip2(int log_level) { #if HAVE_BZIP2 + static void *bzip2_dl = NULL; + SD_ELF_NOTE_DLOPEN( "bzip2", "Support bzip2 compression and decompression", diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index 34444811ad3af..80e9d4ddb45d2 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -9,8 +9,6 @@ #if HAVE_GCRYPT -static void *gcrypt_dl = NULL; - static DLSYM_PROTOTYPE(gcry_control) = NULL; static DLSYM_PROTOTYPE(gcry_check_version) = NULL; DLSYM_PROTOTYPE(gcry_md_close) = NULL; @@ -47,6 +45,8 @@ DLSYM_PROTOTYPE(gcry_strerror) = NULL; int dlopen_gcrypt(int log_level) { #if HAVE_GCRYPT + static void *gcrypt_dl = NULL; + SD_ELF_NOTE_DLOPEN( "gcrypt", "Support for journald forward-sealing", diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 5c2fca0f8357e..602a5d4515121 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -31,13 +31,14 @@ REENABLE_WARNING #include "tests.h" #if HAVE_GLIB -static void *glib_dl = NULL; static DLSYM_PROTOTYPE(g_dbus_message_new_from_blob) = NULL; static DLSYM_PROTOTYPE(g_dbus_message_print) = NULL; static DLSYM_PROTOTYPE(g_free) = NULL; static DLSYM_PROTOTYPE(g_object_unref) = NULL; static int dlopen_glib(void) { + static void *glib_dl = NULL; + return dlopen_many_sym_or_warn( &glib_dl, "libgio-2.0.so.0", LOG_DEBUG, DLSYM_ARG(g_dbus_message_new_from_blob), @@ -48,13 +49,14 @@ static int dlopen_glib(void) { #endif #if HAVE_DBUS -static void *libdbus_dl = NULL; static DLSYM_PROTOTYPE(dbus_error_init) = NULL; static DLSYM_PROTOTYPE(dbus_error_free) = NULL; static DLSYM_PROTOTYPE(dbus_message_demarshal) = NULL; static DLSYM_PROTOTYPE(dbus_message_unref) = NULL; static int dlopen_libdbus(void) { + static void *libdbus_dl = NULL; + return dlopen_many_sym_or_warn( &libdbus_dl, "libdbus-1.so.3", LOG_DEBUG, DLSYM_ARG(dbus_error_init), diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index a55316c73a940..181d14dd17cc8 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -8,8 +8,6 @@ #include "xkbcommon-util.h" #if HAVE_XKBCOMMON -static void *xkbcommon_dl = NULL; - DLSYM_PROTOTYPE(xkb_context_new) = NULL; DLSYM_PROTOTYPE(xkb_context_unref) = NULL; DLSYM_PROTOTYPE(xkb_context_set_log_fn) = NULL; @@ -17,6 +15,8 @@ DLSYM_PROTOTYPE(xkb_keymap_new_from_names) = NULL; DLSYM_PROTOTYPE(xkb_keymap_unref) = NULL; static int dlopen_xkbcommon(int log_level) { + static void *xkbcommon_dl = NULL; + SD_ELF_NOTE_DLOPEN( "xkbcommon", "Support for keyboard locale descriptions", diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index 92d920e171a72..3e6f75d52ac76 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -17,8 +17,6 @@ #include "user-util.h" #if HAVE_ACL -static void *libacl_dl = NULL; - DLSYM_PROTOTYPE(acl_add_perm); DLSYM_PROTOTYPE(acl_calc_mask); DLSYM_PROTOTYPE(acl_copy_entry); @@ -49,6 +47,8 @@ DLSYM_PROTOTYPE(acl_to_any_text); int dlopen_libacl(int log_level) { #if HAVE_ACL + static void *libacl_dl = NULL; + SD_ELF_NOTE_DLOPEN( "acl", "Support for file Access Control Lists (ACLs)", diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index b784d26c5baf6..c2e874553e3ec 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -11,8 +11,6 @@ #include "fileio.h" -static void *libapparmor_dl = NULL; - DLSYM_PROTOTYPE(aa_change_onexec) = NULL; DLSYM_PROTOTYPE(aa_change_profile) = NULL; DLSYM_PROTOTYPE(aa_features_new_from_kernel) = NULL; @@ -48,6 +46,8 @@ bool mac_apparmor_use(void) { int dlopen_libapparmor(int log_level) { #if HAVE_APPARMOR + static void *libapparmor_dl = NULL; + SD_ELF_NOTE_DLOPEN( "apparmor", "Support for AppArmor policies", diff --git a/src/shared/blkid-util.c b/src/shared/blkid-util.c index 18bf100d064d1..022a43ffa90fd 100644 --- a/src/shared/blkid-util.c +++ b/src/shared/blkid-util.c @@ -9,8 +9,6 @@ #include "string-util.h" #if HAVE_BLKID -static void *libblkid_dl = NULL; - DLSYM_PROTOTYPE(blkid_do_fullprobe) = NULL; DLSYM_PROTOTYPE(blkid_do_probe) = NULL; DLSYM_PROTOTYPE(blkid_do_safeprobe) = NULL; @@ -99,6 +97,8 @@ int blkid_probe_lookup_value_u64(blkid_probe b, const char *field, uint64_t *ret int dlopen_libblkid(int log_level) { #if HAVE_BLKID + static void *libblkid_dl = NULL; + SD_ELF_NOTE_DLOPEN( "blkid", "Support for block device identification", diff --git a/src/shared/crypto-util.c b/src/shared/crypto-util.c index bf770f2432175..fd09872a0250e 100644 --- a/src/shared/crypto-util.c +++ b/src/shared/crypto-util.c @@ -37,8 +37,6 @@ struct OpenSSLAskPasswordUI { #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; @@ -336,6 +334,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(UI_METHOD*, sym_UI_destroy_method, UI_de int dlopen_libcrypto(int log_level) { #if HAVE_OPENSSL + static void *libcrypto_dl = NULL; + SD_ELF_NOTE_DLOPEN( "libcrypto", "Support for cryptographic operations", diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index a51783cb296ee..d80824578f252 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -17,8 +17,6 @@ #include "strv.h" #if HAVE_LIBCRYPTSETUP -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; @@ -275,6 +273,7 @@ int cryptsetup_get_volume_key_id( int dlopen_cryptsetup(int log_level) { #if HAVE_LIBCRYPTSETUP + static void *cryptsetup_dl = NULL; int r; /* libcryptsetup added crypt_reencrypt() in 2.2.0, and marked it obsolete in 2.4.0, replacing it with diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index e438ddf61a4d2..aabc3217290eb 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -18,8 +18,6 @@ #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; @@ -604,6 +602,8 @@ int curl_append_to_header(struct curl_slist **list, char **headers) { int dlopen_curl(int log_level) { #if HAVE_LIBCURL + static void *curl_dl = NULL; + SD_ELF_NOTE_DLOPEN( "curl", "Support for downloading and uploading files over HTTP", diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 75e1285c2fe80..2210ab39f1423 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -38,9 +38,6 @@ #if HAVE_ELFUTILS -static void *dw_dl = NULL; -static void *elf_dl = NULL; - /* libdw symbols */ static DLSYM_PROTOTYPE(dwarf_attr_integrate) = NULL; static DLSYM_PROTOTYPE(dwarf_diename) = NULL; @@ -96,6 +93,7 @@ static DLSYM_PROTOTYPE(gelf_getnote) = NULL; int dlopen_dw(int log_level) { #if HAVE_ELFUTILS + static void *dw_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( @@ -161,6 +159,7 @@ bool dlopen_dw_has_dwfl_set_sysroot(void) { int dlopen_elf(int log_level) { #if HAVE_ELFUTILS + static void *elf_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 00f1bfeaea246..5c55135842a28 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -16,8 +16,6 @@ #include "parse-util.h" #include "string-util.h" -static void *fdisk_dl = NULL; - DLSYM_PROTOTYPE(fdisk_add_partition) = NULL; DLSYM_PROTOTYPE(fdisk_apply_table) = NULL; DLSYM_PROTOTYPE(fdisk_ask_get_type) = NULL; @@ -83,6 +81,8 @@ DLSYM_PROTOTYPE(fdisk_write_disklabel) = NULL; int dlopen_fdisk(int log_level) { #if HAVE_LIBFDISK + static void *fdisk_dl = NULL; + SD_ELF_NOTE_DLOPEN( "fdisk", "Support for reading and writing partition tables", diff --git a/src/shared/gnutls-util.c b/src/shared/gnutls-util.c index 1f17c4f5a1693..4a13dbe1ec9cd 100644 --- a/src/shared/gnutls-util.c +++ b/src/shared/gnutls-util.c @@ -6,8 +6,6 @@ #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; @@ -23,6 +21,8 @@ DLSYM_PROTOTYPE(gnutls_x509_crt_init) = NULL; int dlopen_gnutls(int log_level) { #if HAVE_GNUTLS + static void *gnutls_dl = NULL; + SD_ELF_NOTE_DLOPEN( "gnutls", "Support for TLS via GnuTLS", diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index f0f3439f9284d..c037f4a770b76 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -9,8 +9,6 @@ #include "user-util.h" /* IWYU pragma: keep */ #if HAVE_LIBARCHIVE -static void *libarchive_dl = NULL; - DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL; DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL; @@ -79,6 +77,7 @@ DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL; int dlopen_libarchive(int log_level) { #if HAVE_LIBARCHIVE + static void *libarchive_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( diff --git a/src/shared/libaudit-util.c b/src/shared/libaudit-util.c index 478d3d33b6518..41daa372b5874 100644 --- a/src/shared/libaudit-util.c +++ b/src/shared/libaudit-util.c @@ -14,8 +14,6 @@ #include "socket-util.h" #if HAVE_AUDIT -static void *libaudit_dl = NULL; - static DLSYM_PROTOTYPE(audit_close) = NULL; DLSYM_PROTOTYPE(audit_log_acct_message) = NULL; DLSYM_PROTOTYPE(audit_log_user_avc_message) = NULL; @@ -25,6 +23,8 @@ static DLSYM_PROTOTYPE(audit_open) = NULL; int dlopen_libaudit(int log_level) { #if HAVE_AUDIT + static void *libaudit_dl = NULL; + SD_ELF_NOTE_DLOPEN( "audit", "Support for Audit logging", diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 3459bb6ee3d0b..d9710566e6d2e 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -16,7 +16,6 @@ #if HAVE_LIBCRYPT #ifdef __GLIBC__ -static void *libcrypt_dl = NULL; static DLSYM_PROTOTYPE(crypt_gensalt_ra) = NULL; static DLSYM_PROTOTYPE(crypt_preferred_method) = NULL; static DLSYM_PROTOTYPE(crypt_ra) = NULL; @@ -132,6 +131,7 @@ bool looks_like_hashed_password(const char *s) { int dlopen_libcrypt(int log_level) { #if HAVE_LIBCRYPT #ifdef __GLIBC__ + static void *libcrypt_dl = NULL; static int cached = 0; int r = -ENOENT; diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index c25019e6b01d8..74ef66ae4c731 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -23,8 +23,6 @@ #define FIDO_ERR_UV_BLOCKED 0x3c #endif -static void *libfido2_dl = NULL; - DLSYM_PROTOTYPE(fido_assert_allow_cred) = NULL; DLSYM_PROTOTYPE(fido_assert_free) = NULL; DLSYM_PROTOTYPE(fido_assert_hmac_secret_len) = NULL; @@ -82,6 +80,7 @@ static void fido_log_propagate_handler(const char *s) { int dlopen_libfido2(int log_level) { #if HAVE_LIBFIDO2 + static void *libfido2_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index 27e98888d02a8..065fcc2cb2656 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -11,8 +11,6 @@ #include "fstab-util.h" -static void *libmount_dl = NULL; - DLSYM_PROTOTYPE(mnt_free_iter) = NULL; DLSYM_PROTOTYPE(mnt_free_table) = NULL; DLSYM_PROTOTYPE(mnt_fs_get_fs_options) = NULL; @@ -120,6 +118,8 @@ int libmount_is_leaf( int dlopen_libmount(int log_level) { #if HAVE_LIBMOUNT + static void *libmount_dl = NULL; + SD_ELF_NOTE_DLOPEN( "mount", "Support for mount enumeration", diff --git a/src/shared/microhttpd-util.c b/src/shared/microhttpd-util.c index 6dd55d8a0769c..ade36a8c744a4 100644 --- a/src/shared/microhttpd-util.c +++ b/src/shared/microhttpd-util.c @@ -12,8 +12,6 @@ #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; @@ -36,6 +34,8 @@ DLSYM_PROTOTYPE(MHD_stop_daemon) = NULL; int dlopen_microhttpd(int log_level) { #if HAVE_MICROHTTPD + static void *microhttpd_dl = NULL; + SD_ELF_NOTE_DLOPEN( "microhttpd", "Support for embedded HTTP server via libmicrohttpd", diff --git a/src/shared/module-util.c b/src/shared/module-util.c index 9bf1a827b008f..2fb5cd332914b 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -11,8 +11,6 @@ #if HAVE_KMOD -static void *libkmod_dl = NULL; - DLSYM_PROTOTYPE(kmod_list_next) = NULL; DLSYM_PROTOTYPE(kmod_load_resources) = NULL; DLSYM_PROTOTYPE(kmod_module_get_initstate) = NULL; @@ -173,6 +171,8 @@ int module_setup_context(struct kmod_ctx **ret) { int dlopen_libkmod(int log_level) { #if HAVE_KMOD + static void *libkmod_dl = NULL; + SD_ELF_NOTE_DLOPEN( "kmod", "Support for loading kernel modules", diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index 37f2caf31e6f3..fb57dd97575fc 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -20,8 +20,6 @@ #include "stdio-util.h" #include "string-util.h" -static void *libpam_dl = NULL; - DLSYM_PROTOTYPE(pam_acct_mgmt) = NULL; DLSYM_PROTOTYPE(pam_close_session) = NULL; DLSYM_PROTOTYPE(pam_end) = NULL; @@ -383,6 +381,8 @@ int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value) { int dlopen_libpam(int log_level) { #if HAVE_PAM + static void *libpam_dl = NULL; + SD_ELF_NOTE_DLOPEN( "pam", "Support for LinuxPAM", diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index 6ce858c744622..f32245d344cbe 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -16,8 +16,6 @@ #include "memory-util.h" #include "strv.h" -static void *passwdqc_dl = NULL; - static DLSYM_PROTOTYPE(passwdqc_params_reset) = NULL; static DLSYM_PROTOTYPE(passwdqc_params_load) = NULL; static DLSYM_PROTOTYPE(passwdqc_params_parse) = NULL; @@ -140,6 +138,8 @@ int check_password_quality( int dlopen_passwdqc(int log_level) { #if HAVE_PASSWDQC + static void *passwdqc_dl = NULL; + SD_ELF_NOTE_DLOPEN( "passwdqc", "Support for password quality checks", diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index dd0cdcfa136a5..34d9b1aa16e26 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -19,8 +19,6 @@ #include "string-util.h" #include "strv.h" -static void *pwquality_dl = NULL; - static DLSYM_PROTOTYPE(pwquality_check) = NULL; static DLSYM_PROTOTYPE(pwquality_default_settings) = NULL; static DLSYM_PROTOTYPE(pwquality_free_settings) = NULL; @@ -156,6 +154,8 @@ int check_password_quality(const char *password, const char *old, const char *us int dlopen_pwquality(int log_level) { #if HAVE_PWQUALITY + static void *pwquality_dl = NULL; + SD_ELF_NOTE_DLOPEN( "pwquality", "Support for password quality checks", diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 3ac76c1f9c9b8..52b65a95a35f3 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -8,8 +8,6 @@ #include "pcre2-util.h" #if HAVE_PCRE2 -static void *pcre2_dl = NULL; - DLSYM_PROTOTYPE(pcre2_match_data_create) = NULL; DLSYM_PROTOTYPE(pcre2_match_data_free) = NULL; DLSYM_PROTOTYPE(pcre2_code_free) = NULL; @@ -30,6 +28,8 @@ const struct hash_ops pcre2_code_hash_ops_free = {}; int dlopen_pcre2(int log_level) { #if HAVE_PCRE2 + static void *pcre2_dl = NULL; + SD_ELF_NOTE_DLOPEN( "pcre2", "Support for regular expressions", @@ -127,7 +127,7 @@ int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, s assert(message); /* pattern_compile_and_log() must be called before this function is called and that function already * dlopens pcre2 so we can assert on it being available here. */ - assert(pcre2_dl); + assert(sym_pcre2_match); md = sym_pcre2_match_data_create(1, NULL); if (!md) diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index a4c5bb83f7d9e..dc843997a2567 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -43,8 +43,6 @@ bool pkcs11_uri_valid(const char *uri) { #if HAVE_P11KIT -static void *p11kit_dl = NULL; - DLSYM_PROTOTYPE(p11_kit_module_get_name) = NULL; DLSYM_PROTOTYPE(p11_kit_modules_finalize_and_release) = NULL; DLSYM_PROTOTYPE(p11_kit_modules_load_and_initialize) = NULL; @@ -1807,6 +1805,8 @@ static int list_callback( int dlopen_p11kit(int log_level) { #if HAVE_P11KIT + static void *p11kit_dl = NULL; + SD_ELF_NOTE_DLOPEN( "p11-kit", "Support for PKCS11 hardware tokens", diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index d1c639e74c1f0..44d674ba6a3ae 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -22,14 +22,13 @@ #define UNICODE_UPPER_HALF_BLOCK UTF8("▀") #if HAVE_QRENCODE -static void *qrcode_dl = NULL; - static DLSYM_PROTOTYPE(QRcode_encodeString) = NULL; static DLSYM_PROTOTYPE(QRcode_free) = NULL; #endif int dlopen_qrencode(int log_level) { #if HAVE_QRENCODE + static void *qrcode_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 167a0e40de8c3..0484c68b4cc7e 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -34,8 +34,6 @@ #include "strv.h" #if HAVE_SECCOMP -static void *libseccomp_dl = NULL; - DLSYM_PROTOTYPE(seccomp_api_get) = NULL; DLSYM_PROTOTYPE(seccomp_arch_add) = NULL; DLSYM_PROTOTYPE(seccomp_arch_exist) = NULL; @@ -2604,6 +2602,8 @@ int seccomp_suppress_sync(void) { int dlopen_libseccomp(int log_level) { #if HAVE_SECCOMP + static void *libseccomp_dl = NULL; + SD_ELF_NOTE_DLOPEN( "seccomp", "Support for Seccomp Sandboxes", diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 19e0d2b488d4d..6c83f3fdd28d8 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -55,8 +55,6 @@ static int mac_selinux_label_post(int dir_fd, const char *path, bool created) { return 0; } -static void *libselinux_dl = NULL; - DLSYM_PROTOTYPE(avc_open) = NULL; DLSYM_PROTOTYPE(context_free) = NULL; DLSYM_PROTOTYPE(context_new) = NULL; @@ -95,6 +93,8 @@ DLSYM_PROTOTYPE(string_to_security_class) = NULL; int dlopen_libselinux(int log_level) { #if HAVE_SELINUX + static void *libselinux_dl = NULL; + SD_ELF_NOTE_DLOPEN( "selinux", "Support for SELinux", diff --git a/src/shared/ssl-util.c b/src/shared/ssl-util.c index 82ed54d037e14..7b9314be96337 100644 --- a/src/shared/ssl-util.c +++ b/src/shared/ssl-util.c @@ -7,8 +7,6 @@ #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; @@ -36,6 +34,8 @@ DLSYM_PROTOTYPE(TLS_client_method) = NULL; int dlopen_libssl(int log_level) { #if HAVE_OPENSSL + static void *libssl_dl = NULL; + SD_ELF_NOTE_DLOPEN( "libssl", "Support for TLS", diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 4557ad3f41456..533cdd630650e 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -53,11 +53,6 @@ #include "virt.h" #if HAVE_TPM2 -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; static DLSYM_PROTOTYPE(Esys_CreatePrimary) = NULL; @@ -126,6 +121,7 @@ static DLSYM_PROTOTYPE(Tss2_MU_UINT32_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_RC_Decode) = NULL; static int dlopen_tpm2_esys(int log_level) { + static void *libtss2_esys_dl = NULL; int r; SD_ELF_NOTE_DLOPEN( @@ -192,6 +188,8 @@ static int dlopen_tpm2_esys(int log_level) { } static int dlopen_tpm2_rc(int log_level) { + static void *libtss2_rc_dl = NULL; + SD_ELF_NOTE_DLOPEN( "tpm", "Support for TPM", @@ -204,6 +202,8 @@ static int dlopen_tpm2_rc(int log_level) { } static int dlopen_tpm2_mu(int log_level) { + static void *libtss2_mu_dl = NULL; + SD_ELF_NOTE_DLOPEN( "tpm", "Support for TPM", @@ -234,6 +234,8 @@ static int dlopen_tpm2_mu(int log_level) { } static int dlopen_tpm2_tcti_device(int log_level) { + static void *libtss2_tcti_device_dl = NULL; + /* 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. */ From 450a5a474006d10e0323bc6f6c41db3ed46a374b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BC=D1=98=D0=B0=D0=BD=20=D0=93=D0=B5=D0=BE?= =?UTF-8?q?=D1=80=D0=B3=D0=B8=D0=B5=D0=B2=D1=81=D0=BA=D0=B8?= Date: Mon, 18 May 2026 23:07:37 +0200 Subject: [PATCH 1993/2155] socket-proxy: implement PROXY protocol v1 as specified by the haproxy documentation: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt only protocol v1 is implemented for now, protocol v2 is binary, and will be implemented in the future. the proxy protocol allows the destination/target to know the address/port of the client connectiing. in nginx it's supported by enabling the `proxy_protocol` parameter to the `listen` directive. --- man/systemd-socket-proxyd.xml | 54 +++++++++++++++ src/socket-proxy/socket-proxyd.c | 110 +++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/man/systemd-socket-proxyd.xml b/man/systemd-socket-proxyd.xml index dde6d888ada58..30f248220ef18 100644 --- a/man/systemd-socket-proxyd.xml +++ b/man/systemd-socket-proxyd.xml @@ -83,6 +83,17 @@ + + + + Uses the PROXY protocol + to communicate with the server. This allows an appropriately configured server to know the real client IP address. + Takes the version of the PROXY protocol + used, and only supports v1 for now. + Default is not to use a PROXY protocol. + + + @@ -176,6 +187,47 @@ server { Enabling the proxy + + + + PROXY protocol example with nginx + systemd-socket-proxyd and nginx using the PROXY protocol + + proxy-to-nginx.socket + + + + proxy-to-nginx.service + + + + nginx.conf + + + + + + Enabling the proxy + @@ -190,6 +242,8 @@ $ curl http://localhost:80/]]> socat1 nginx1 curl1 + PROXY protocol specification + nginx: Accepting the PROXY Protocol diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 77dc903535633..8be25f176e166 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -15,6 +15,8 @@ #include "event-util.h" #include "fd-util.h" #include "format-table.h" +#include "in-addr-util.h" +#include "io-util.h" #include "log.h" #include "main-func.h" #include "options.h" @@ -24,6 +26,7 @@ #include "set.h" #include "socket-forward.h" #include "socket-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -32,6 +35,21 @@ static unsigned arg_connections_max = 256; static const char *arg_remote_host = NULL; static usec_t arg_exit_idle_time = USEC_INFINITY; +typedef enum ProxyProtocol { + PROXY_NONE, + PROXY_V1, + _PROXY_PROTOCOL_MAX, + _PROXY_PROTOCOL_INVALID = -EINVAL, +} ProxyProtocol; + +static const char* const proxy_protocol_table[_PROXY_PROTOCOL_MAX] = { + [PROXY_V1] = "v1", +}; + +static ProxyProtocol arg_proxy_protocol = PROXY_NONE; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(proxy_protocol, ProxyProtocol); + typedef struct Context { sd_event *event; sd_resolve *resolve; @@ -138,11 +156,96 @@ static int connection_forward_done(SocketForward *sf, int error, void *userdata) return 0; /* ignore errors, continue serving */ } +static int send_proxy_protocol_v1(Connection *c) { + _cleanup_free_ char *header = NULL; + int r, header_len; + union sockaddr_union local_sa, remote_sa; + socklen_t sa_len; + + assert(c); + + r = sd_is_socket(c->server_fd, AF_UNSPEC, SOCK_STREAM, /* listening= */ 0); + if (r < 0) { + log_warning_errno(r, "Failed to issue SO_TYPE, reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } + if (r == 0) { + log_warning("Only TCP is supported by the PROXY protocol, reporting fallback proxy data 'UNKNOWN'."); + goto unknown; + } + + sa_len = sizeof(local_sa); + if (getsockname(c->server_fd, &local_sa.sa, &sa_len) < 0) { + log_warning_errno(errno, "Failed to get local address (getsockname), reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } + + sa_len = sizeof(remote_sa); + if (getpeername(c->server_fd, &remote_sa.sa, &sa_len) < 0) { + log_warning_errno(errno, "Failed to get remote address (getpeername), reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } + + const char *proto = NULL; + switch (remote_sa.sa.sa_family) { + + case AF_INET: + proto = "TCP4"; + break; + + case AF_INET6: + proto = "TCP6"; + break; + + default: + log_warning("Only TCP over IPv4 and IPv6 are supported, reporting fallback proxy data 'UNKNOWN'."); + goto unknown; + } + + const union in_addr_union *remote_addr = sockaddr_in_addr(&remote_sa.sa); + const union in_addr_union *local_addr = sockaddr_in_addr(&local_sa.sa); + + unsigned remote_port, local_port; + assert_se(sockaddr_port(&remote_sa.sa, &remote_port) >= 0); + assert_se(sockaddr_port(&local_sa.sa, &local_port) >= 0); + + header_len = asprintf( + &header, "PROXY %s %s %s %u %u\r\n", + proto, + IN_ADDR_TO_STRING(remote_sa.sa.sa_family, remote_addr), + IN_ADDR_TO_STRING(local_sa.sa.sa_family, local_addr), + remote_port, + local_port); + if (header_len < 0) + return log_oom(); + + r = loop_write_full(c->client_fd, header, header_len, 10 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to write to backend host: %m"); + + /* success */ + return 0; + +unknown: + /* ignore previous errors - server can decide to deny UNKNOWN connections */ + r = loop_write_full(c->client_fd, "PROXY UNKNOWN\r\n", SIZE_MAX, 10 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to write to backend host: %m"); + + return 0; +} + static int connection_complete(Connection *c) { int r; assert(c); + if (arg_proxy_protocol == PROXY_V1) { + r = send_proxy_protocol_v1(c); + if (r < 0) + return r; + } + r = socket_forward_new( c->context->event, TAKE_FD(c->server_fd), @@ -450,6 +553,13 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", opts.arg); break; + + OPTION_LONG("proxy-protocol", "v1", + "Enable PROXY protocol: v1"): + arg_proxy_protocol = proxy_protocol_from_string(opts.arg); + if (arg_proxy_protocol < 0) + return log_error_errno(arg_proxy_protocol, "Failed to parse --proxy-protocol= argument: %s", opts.arg); + break; } char **args = option_parser_get_args(&opts); From b8b2316b0a5d14333c1e2c1eed42c48551727bb8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 16 May 2026 19:09:12 +0000 Subject: [PATCH 1994/2155] tools: add a test wrapper that replays crashing tests under gdb meson test --wrapper hook to print a gdb backtrace inline in the test log when a test exits with an actual crash signal (SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL). Wired into the default add_test_setup() so it runs automatically on every `meson test`. Environmental terminations (SIGTERM/SIGKILL/SIGPIPE/SIGALRM) are passed through without replay, and the original signal is re-raised so the parent's wait() observes WIFSIGNALED rather than a plain exit code. --- .packit.yml | 22 +++++++++++---- meson.build | 1 + tools/test-crash-trace.sh | 56 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100755 tools/test-crash-trace.sh diff --git a/.packit.yml b/.packit.yml index 97e1e58855048..e0aa839d925c2 100644 --- a/.packit.yml +++ b/.packit.yml @@ -28,12 +28,24 @@ actions: jobs: - job: copr_build trigger: pull_request + # Install gdb into the buildroot so post-failure hooks can pull backtraces from any cores the + # %check phase leaves behind. targets: - - fedora-rawhide-aarch64 - - fedora-rawhide-i386 - - fedora-rawhide-ppc64le - - fedora-rawhide-s390x - - fedora-rawhide-x86_64 + fedora-rawhide-aarch64: + additional_packages: + - gdb + fedora-rawhide-i386: + additional_packages: + - gdb + fedora-rawhide-ppc64le: + additional_packages: + - gdb + fedora-rawhide-s390x: + additional_packages: + - gdb + fedora-rawhide-x86_64: + additional_packages: + - gdb - job: tests trigger: pull_request diff --git a/meson.build b/meson.build index c76471e1fe4ff..d9c8b84e4707b 100644 --- a/meson.build +++ b/meson.build @@ -16,6 +16,7 @@ project('systemd', 'c', add_test_setup( 'default', exclude_suites : ['clang-tidy', 'unused-symbols', 'integration-tests'], + exe_wrapper : find_program('tools/test-crash-trace.sh'), is_default : true, ) diff --git a/tools/test-crash-trace.sh b/tools/test-crash-trace.sh new file mode 100755 index 0000000000000..d4a8a60f82b80 --- /dev/null +++ b/tools/test-crash-trace.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# meson test wrapper that replays crashing tests under gdb and prints a +# backtrace, so SIGSEGV/SIGABRT/etc. failures in CI show a stack trace inline +# in the test log instead of just an exit code. +# +# Usage: meson test --wrapper=$PWD/tools/test-crash-trace.sh + +set -euo pipefail + +if [[ $# -eq 0 ]]; then + echo "usage: $0 [args...]" >&2 + exit 2 +fi + +rc=0 +"$@" || rc=$? + +# Replay only on actual crash signals (128 + signal): SIGILL=132, SIGABRT=134, +# SIGBUS=135, SIGFPE=136, SIGSEGV=139. SIGTERM/SIGKILL/SIGPIPE/SIGALRM mean the +# test was killed by the environment (timeout, etc.), not that it crashed — +# replaying those just makes gdb hit the same kill. +case "$rc" in + 132|134|135|136|139) + if command -v gdb >/dev/null 2>&1; then + echo "===== exit $rc — replaying under gdb =====" >&2 + style_args=() + if [[ -t 2 ]]; then + style_args=(--ex 'set style enabled on') + fi + gdb --batch \ + --ex 'set pagination off' \ + ${style_args[@]+"${style_args[@]}"} \ + --ex 'run' \ + --ex 'thread apply all bt full' \ + --args "$@" >&2 || true + else + echo "===== exit $rc — install gdb for a backtrace =====" >&2 + fi + ;; +esac + +# If the child died by signal, re-raise it so the parent's wait() observes +# WIFSIGNALED instead of a plain exit code. Best-effort: SIGKILL can't be +# delivered to ourselves and would have killed us already. +if [[ $rc -gt 128 ]]; then + sig=$((rc - 128)) + trap - "$sig" 2>/dev/null || true + # Suppress the wrapper bash's own core dump from the re-raised crash signal; + # the original test binary already had its chance to dump core on line 18. + ulimit -c 0 + kill -s "$sig" $$ 2>/dev/null || true +fi + +exit "$rc" From 9b21738498affe2b7ec477be1b55e79a052d852f Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Tue, 19 May 2026 17:33:06 -0400 Subject: [PATCH 1995/2155] sd-bus: add depth limit to message_skip_fields() to prevent stack overflow message_skip_fields() recurses for each nested variant ('v') type in D-Bus message header fields. A crafted message with deeply nested variants (e.g., a variant containing a variant containing a variant...) causes unbounded stack growth, leading to stack overflow and crash. Add a depth parameter that increments on each recursive call and rejects messages exceeding BUS_CONTAINER_DEPTH with -EBADMSG. This matches the existing depth limits enforced elsewhere in the sd-bus message processing (e.g., bus_message_enter_container). --- src/libsystemd/sd-bus/bus-message.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 017ffb7a6127a..a719253fa73e6 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -3904,7 +3904,8 @@ static int message_skip_fields( sd_bus_message *m, size_t *ri, uint32_t array_size, - const char **signature) { + const char **signature, + unsigned depth) { size_t original_index; int r; @@ -3913,6 +3914,9 @@ static int message_skip_fields( assert(ri); assert(signature); + if (depth >= BUS_CONTAINER_DEPTH) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Maximum container nesting depth reached, refusing."); + original_index = *ri; for (;;) { @@ -3993,7 +3997,7 @@ static int message_skip_fields( if (r < 0) return r; - r = message_skip_fields(m, ri, nas, (const char**) &s); + r = message_skip_fields(m, ri, nas, (const char**) &s, depth + 1); if (r < 0) return r; } @@ -4007,7 +4011,7 @@ static int message_skip_fields( if (r < 0) return r; - r = message_skip_fields(m, ri, UINT32_MAX, &s); + r = message_skip_fields(m, ri, UINT32_MAX, &s, depth + 1); if (r < 0) return r; @@ -4025,7 +4029,7 @@ static int message_skip_fields( strncpy(sig, *signature + 1, l); sig[l] = '\0'; - r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s); + r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s, depth + 1); if (r < 0) return r; } @@ -4198,7 +4202,7 @@ static int message_parse_fields(sd_bus_message *m, bool got_ctrunc) { break; default: - r = message_skip_fields(m, &ri, UINT32_MAX, &signature); + r = message_skip_fields(m, &ri, UINT32_MAX, &signature, 0); } if (r < 0) return r; From ddb221c3b26d561286518da99d477e2f580076b0 Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Tue, 19 May 2026 17:33:11 -0400 Subject: [PATCH 1996/2155] test: add test for variant recursion depth limit in message_skip_fields() Craft a raw D-Bus message with an unknown header field containing BUS_CONTAINER_DEPTH+1 nested variants and verify that message parsing rejects it with -EBADMSG rather than recursing until stack overflow. --- src/libsystemd/sd-bus/test-bus-marshal.c | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index e26ca43344713..7544b1adc66a5 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -216,6 +216,58 @@ static void test_bus_fds_truncated(void) { log_info("All fd truncation tests passed"); } +static void test_bus_nested_variant_depth_limit(void) { + /* Craft a raw D-Bus message with an unknown header field whose value is a variant + * containing a variant containing a variant... nested beyond BUS_CONTAINER_DEPTH. + * Without the depth limit in message_skip_fields(), this causes unbounded recursion + * and stack overflow. With the fix, it should be rejected with -EBADMSG. */ + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + const unsigned depth = BUS_CONTAINER_DEPTH + 1; /* one past the limit */ + + /* Each nesting level in the fields area is: 1 byte sig_len + 1 byte 'v' + 1 byte NUL = 3 bytes. + * The innermost level has sig_len=1, 'u', NUL, then 4 bytes for the uint32 value. + * The field header is: 1 byte field_code + 1 byte sig_len + 1 byte 'v' + 1 byte NUL = 4 bytes. */ + size_t fields_size = 4 + (depth * 3) + 4; /* field header + nested variant sigs + uint32 */ + size_t padded_fields = ALIGN8(fields_size); + size_t total = sizeof(BusMessageHeader) + padded_fields; + + _cleanup_free_ void *buf = ASSERT_PTR(malloc0(total)); + + BusMessageHeader *h = buf; + *h = (BusMessageHeader) { + .endian = BUS_NATIVE_ENDIAN, + .type = SD_BUS_MESSAGE_METHOD_CALL, + .version = 1, + .serial = 1, + .fields_size = (uint32_t) fields_size, + }; + + uint8_t *p = (uint8_t *) buf + sizeof(BusMessageHeader); + + /* Unknown field code (triggers default: in message_parse_fields) */ + *p++ = 0xFF; + /* Field signature: variant */ + *p++ = 1; /* sig length */ + *p++ = 'v'; + *p++ = '\0'; + + /* Nested variant signatures: each level declares its content is another variant */ + for (unsigned i = 0; i < depth; i++) { + *p++ = 1; /* sig length */ + *p++ = 'v'; + *p++ = '\0'; + } + + /* Innermost value: a uint32 */ + memset(p, 0, 4); + + ASSERT_OK(sd_bus_new(&bus)); + + ASSERT_ERROR(bus_message_from_malloc(bus, buf, total, NULL, 0, false, NULL, &m), EBADMSG); +} + static void test_bus_label_escape(void) { test_bus_label_escape_one("foo123bar", "foo123bar"); test_bus_label_escape_one("foo.bar", "foo_2ebar"); @@ -533,6 +585,7 @@ int main(int argc, char *argv[]) { test_bus_path_encode_unique(); test_bus_path_encode_many(); test_bus_fds_truncated(); + test_bus_nested_variant_depth_limit(); return 0; } From fc0bd373ef3bd1681f135451648297c6b3189019 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 19 May 2026 22:00:58 +0100 Subject: [PATCH 1997/2155] test: fix race in TEST-07-PID1.socket-on-failure.sh The test waited for the OnFailure= service's filesystem side effect (`rmdir` of the directory) and then immediately invoked `systemctl is-active`. Between `rmdir(2)` returning (which causes the shell loop to exit) and PID1 reaping the child and transitioning the oneshot service from `activating` to `active`, there is a small window where `is-active` can observe `activating` and fail the test. Wait directly on the unit state instead, matching the pattern used a few lines above for the `is-failed` case. [ 1880.326704] TEST-07-PID1.sh[21489]: + timeout --foreground 60 bash -c 'while [[ -d '\''/tmp/TEST-07-PID1-socket-8467/test'\'' ]]; do sleep .5; done' [ 1880.330482] TEST-07-PID1.sh[21489]: + [[ ! -e /tmp/TEST-07-PID1-socket-8467/test ]] [ 1880.330482] TEST-07-PID1.sh[21489]: + systemctl is-active TEST-07-PID1-socket-OnFailure.service [ 1880.347470] TEST-07-PID1.sh[21520]: activating [ 1880.349508] TEST-07-PID1.sh[21489]: + at_exit [ 1880.349508] TEST-07-PID1.sh[21489]: + systemctl stop TEST-07-PID1-socket-8467.socket [ 1880.367331] TEST-07-PID1.sh[107]: Subtest /usr/lib/systemd/tests/testdata/units/TEST-07-PID1.socket-on-failure.sh failed [ 1880.367331] TEST-07-PID1.sh[107]: + return 1 Co-developed-by: Claude Opus 4.7 --- test/units/TEST-07-PID1.socket-on-failure.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/units/TEST-07-PID1.socket-on-failure.sh b/test/units/TEST-07-PID1.socket-on-failure.sh index 44b8a43634faa..ff947b10e2675 100755 --- a/test/units/TEST-07-PID1.socket-on-failure.sh +++ b/test/units/TEST-07-PID1.socket-on-failure.sh @@ -65,9 +65,8 @@ mkdir "/tmp/$UNIT_NAME/test" systemctl is-failed "$UNIT_NAME.socket" assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "failed" -timeout --foreground 60 bash -c "while [[ -d '/tmp/$UNIT_NAME/test' ]]; do sleep .5; done" +timeout --foreground 60 bash -c "until systemctl is-active TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" [[ ! -e "/tmp/$UNIT_NAME/test" ]] -systemctl is-active TEST-07-PID1-socket-OnFailure.service systemctl start "$UNIT_NAME.socket" systemctl is-active "$UNIT_NAME.socket" From 4ac23697280bf54bb768f0aa7a5c7d7d0bcf3f6b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 19 May 2026 22:42:25 +0100 Subject: [PATCH 1998/2155] test: switch TEST-55-OOMD stress-ng --vm-method to lfsr32 Commit 881e4717c7 ("test: pin stress-ng --vm-method to a portable scalar method in TEST-55-OOMD") pinned --vm-method=zero-one with the rationale that it is "a long-standing scalar method". That rationale is wrong: stress_vm_zero_one() in stress-ng's stress-vm.c is declared static size_t TARGET_CLONES stress_vm_zero_one(...) i.e. it carries the exact same TARGET_CLONES attribute as 33 of the 35 other vm methods. On x86_64 with GCC >=5, TARGET_CLONES expands (see core-target-clones.h in stress-ng) to a target_clones attribute including "arch=skylake-avx512", "arch=cooperlake", "arch=tigerlake", "arch=sapphirerapids", and several other AVX-512-bearing arch variants, plus "default". GCC generates AVX-512 clones of stress_vm_zero_one() and the IFUNC resolver picks them on any CPU that advertises AVX-512. The only vm methods in stress-ng's registry whose function definitions omit TARGET_CLONES entirely (and are therefore guaranteed not to dispatch to an AVX-512 clone) are lfsr32 (portable, always registered) and write64ds (x86_64-only, gated on HAVE_ASM_X86_MOVDIRI, i.e. Intel Tremont / Tiger Lake+ MOVDIRI instruction). Switch the four stress-ng --vm invocations in TEST-55-OOMD to --vm-method=lfsr32 so the AVX-512 SIGILL on CPUs without AVX-512 (e.g. AMD Zen 1-3) can no longer occur regardless of compiler version, optimization level, or stress-ng package build. Follow-up for 881e4717c7981b274853309e68b39153e3b292f4 Co-developed-by: Claude Opus 4.7 --- .../TEST-55-OOMD-testbloat.service | 7 +++---- .../TEST-55-OOMD-testmunch.service | 7 +++---- test/units/TEST-55-OOMD.sh | 15 ++++++--------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service index 22bbd210e96f5..e4109fd60ddf9 100644 --- a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service +++ b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testbloat.service @@ -7,7 +7,6 @@ Description=Create a lot of memory pressure # to throttle and be put under heavy pressure. MemoryHigh=3M Slice=TEST-55-OOMD-workload.slice -# Pin --vm-method to a portable method (zero-one): the default 'all' cycles -# through methods, including newer ones using AVX-512 instructions that SIGILL -# on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. -ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=zero-one +# Pin --vm-method=lfsr32: the only stress-ng vm method without TARGET_CLONES, +# so it can't dispatch to an AVX-512 clone and SIGILL on CPUs lacking it. +ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=lfsr32 diff --git a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service index 06eea10b79a55..81305c7e974dd 100644 --- a/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service +++ b/test/integration-tests/TEST-55-OOMD/TEST-55-OOMD.units/TEST-55-OOMD-testmunch.service @@ -5,7 +5,6 @@ Description=Create some memory pressure [Service] MemoryHigh=12M Slice=TEST-55-OOMD-workload.slice -# Pin --vm-method to a portable method (zero-one): the default 'all' cycles -# through methods, including newer ones using AVX-512 instructions that SIGILL -# on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. -ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=zero-one +# Pin --vm-method=lfsr32: the only stress-ng vm method without TARGET_CLONES, +# so it can't dispatch to an AVX-512 clone and SIGILL on CPUs lacking it. +ExecStart=stress-ng --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-method=lfsr32 diff --git a/test/units/TEST-55-OOMD.sh b/test/units/TEST-55-OOMD.sh index b7311e83dca4e..84baee12a4294 100755 --- a/test/units/TEST-55-OOMD.sh +++ b/test/units/TEST-55-OOMD.sh @@ -366,13 +366,12 @@ EOF systemctl reload systemd-oomd.service # Run a transient service with OOMRules=testrule that generates memory pressure. - # Pin --vm-method to a portable method (zero-one): the default 'all' cycles - # through every method, including newer ones using AVX-512 instructions that - # SIGILL on CPUs without AVX-512 (e.g. AMD Zen 1-3), making the test flaky. + # Pin --vm-method=lfsr32: the only stress-ng vm method without TARGET_CLONES, + # so it can't dispatch to an AVX-512 clone and SIGILL on CPUs lacking it. (! systemd-run --wait --unit=TEST-55-OOMD-testrules \ -p MemoryHigh=3M \ -p OOMRules=testrule \ - stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep --vm-method=zero-one) + stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep --vm-method=lfsr32) # Verify in the journal that the rule triggered journalctl --sync @@ -457,14 +456,12 @@ EOF # Start the unit without --wait so we can check mid-run state. The # stress-ng timeout bounds the test if anything goes wrong. - # Pin --vm-method to a portable method (zero-one): the default 'all' cycles - # through every method, including newer ones using AVX-512 instructions that - # SIGILL on CPUs without AVX-512 (e.g. AMD Zen 1-3) and would cause stress-ng - # to exit before the 6 s wait below elapses, failing the ActiveState check. + # Pin --vm-method=lfsr32: the only stress-ng vm method without TARGET_CLONES, + # so it can't SIGILL on AVX-512-less CPUs and exit before the 6 s wait below. systemd-run --unit=TEST-55-OOMD-slowrule \ -p MemoryHigh=3M \ -p OOMRules=slowrule \ - stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep --vm-method=zero-one + stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep --vm-method=lfsr32 # Wait long enough for oomd's 1s rule-check loop to evaluate the condition # many times. With LastingSec=1h the kill must not fire. From ae169c99fa803c6405a631a7d9cbaf85e1e37b9a Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 19 May 2026 20:32:41 +0200 Subject: [PATCH 1999/2155] core: improve errors from varlink io.systemd.Unit.StartTransient The existing error reporting for the varlink `StartTransient` code was converting all errors into `VARLINK_ERROR_UNIT_BAD_SETTING`. This is not correct in some cases, we need to have a more targted pattern here, i.e. only convert EINVAL to VARLINK_ERROR_UNIT_BAD_SETTING and otherwise return the matching varlink error from the errno instead. This commit fixes this issue. Thanks to Ivan Kruglov for raising this. --- src/core/varlink-unit.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index ebb20e44d4ae6..600496d285d36 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -1187,15 +1187,19 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters /* Apply unit-level properties from context */ r = transient_unit_apply_properties(u, &p.context); - if (r < 0) + if (r == -EINVAL) return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + if (r < 0) + return sd_varlink_error_errno(link, r); /* Apply exec-specific properties from context.Exec */ ExecContext *c = unit_get_exec_context(u); if (c) { r = transient_exec_context_apply_properties(u, c, &p.context.exec); - if (r < 0) + if (r == -EINVAL) return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + if (r < 0) + return sd_varlink_error_errno(link, r); } else if (p.context.exec.present) return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); @@ -1203,8 +1207,10 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters Service *s = SERVICE(u); if (s) { r = transient_service_apply_properties(s, &p.context.service); - if (r < 0) + if (r == -EINVAL) return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + if (r < 0) + return sd_varlink_error_errno(link, r); } else if (p.context.service.present) return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); From b131473ce58898c52092253a9464872d975d9718 Mon Sep 17 00:00:00 2001 From: Patrick Rohr Date: Mon, 4 May 2026 13:31:10 -0700 Subject: [PATCH 2000/2155] networkd: fix race condition in per-interface ICMPv6 processing There exists a small window of time in icmp6_bind() between creating the ICMPv6 socket and binding it to an ifindex, where the link-scoped socket can process an ICMPv6 packet received on any interface. The applies to both sd-radv and sd-ndisc codepaths. This change adds an explicit check for ifindex on the receive path and ignores packets received on other interfaces. Co-developed-by: OpenAI Codex --- src/libsystemd-network/icmp6-packet.c | 2 +- src/libsystemd-network/icmp6-packet.h | 1 + src/libsystemd-network/icmp6-test-util.c | 7 +++++ src/libsystemd-network/icmp6-test-util.h | 1 + src/libsystemd-network/icmp6-util.c | 10 +++++++ src/libsystemd-network/icmp6-util.h | 1 + src/libsystemd-network/sd-ndisc.c | 6 +++++ src/libsystemd-network/sd-radv.c | 6 +++++ src/libsystemd-network/test-ndisc-ra.c | 33 ++++++++++++++++++++++++ src/libsystemd-network/test-ndisc-rs.c | 33 ++++++++++++++++++++++++ 10 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/icmp6-packet.c b/src/libsystemd-network/icmp6-packet.c index b4e8e7f85147e..23e6582b67a01 100644 --- a/src/libsystemd-network/icmp6-packet.c +++ b/src/libsystemd-network/icmp6-packet.c @@ -112,7 +112,7 @@ int icmp6_packet_receive(int fd, ICMP6Packet **ret) { if (!p) return -ENOMEM; - r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->timestamp); + r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->ifindex, &p->timestamp); if (r == -EADDRNOTAVAIL) return log_debug_errno(r, "ICMPv6: Received a packet from neither link-local nor null address."); if (r == -EMULTIHOP) diff --git a/src/libsystemd-network/icmp6-packet.h b/src/libsystemd-network/icmp6-packet.h index 929fff1f23c91..1b6f15727575c 100644 --- a/src/libsystemd-network/icmp6-packet.h +++ b/src/libsystemd-network/icmp6-packet.h @@ -8,6 +8,7 @@ typedef struct ICMP6Packet { unsigned n_ref; + int ifindex; struct in6_addr sender_address; struct triple_timestamp timestamp; diff --git a/src/libsystemd-network/icmp6-test-util.c b/src/libsystemd-network/icmp6-test-util.c index 38b3537ebe0a2..33315c4384113 100644 --- a/src/libsystemd-network/icmp6-test-util.c +++ b/src/libsystemd-network/icmp6-test-util.c @@ -9,6 +9,7 @@ #include "time-util.h" int test_fd[2] = EBADF_PAIR; +int test_ifindex = 42; static struct in6_addr dummy_link_local = { .s6_addr = { @@ -18,6 +19,8 @@ static struct in6_addr dummy_link_local = { }; int icmp6_bind(int ifindex, bool is_router) { + test_ifindex = ifindex; + if (!is_router && socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) return -errno; @@ -33,6 +36,7 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp) { assert_se(read(fd, buffer, size) == (ssize_t) size); @@ -43,5 +47,8 @@ int icmp6_receive( if (ret_sender) *ret_sender = dummy_link_local; + if (ret_ifindex) + *ret_ifindex = test_ifindex; + return 0; } diff --git a/src/libsystemd-network/icmp6-test-util.h b/src/libsystemd-network/icmp6-test-util.h index 124cab555218a..8e9f17eb952fb 100644 --- a/src/libsystemd-network/icmp6-test-util.h +++ b/src/libsystemd-network/icmp6-test-util.h @@ -2,3 +2,4 @@ #pragma once extern int test_fd[2]; +extern int test_ifindex; diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 4347f8b723bf0..6df78bd2eed76 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -72,6 +72,10 @@ int icmp6_bind(int ifindex, bool is_router) { if (r < 0) return r; + r = socket_set_recvpktinfo(s, AF_INET6, true); + if (r < 0) + return r; + r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); if (r < 0) return r; @@ -108,11 +112,13 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp) { /* This needs to be initialized with zero. See #20741. * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */ + CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE_TIMEVAL) control = {}; struct iovec iov = { buffer, size }; union sockaddr_union sa = {}; @@ -145,6 +151,10 @@ int icmp6_receive( if (hops && *hops != 255) return -EMULTIHOP; + struct in6_pktinfo *pktinfo = CMSG_FIND_DATA(&msg, IPPROTO_IPV6, IPV6_PKTINFO, struct in6_pktinfo); + if (ret_ifindex) + *ret_ifindex = pktinfo ? pktinfo->ipi6_ifindex : 0; + if (ret_timestamp) triple_timestamp_from_cmsg(ret_timestamp, &msg); if (ret_sender) diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index a1183724b289c..d23e0fe0eb3cb 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -28,4 +28,5 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp); diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 30c0de70047e2..b6a55a5095547 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -348,6 +348,12 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda return 0; } + if (packet->ifindex != nd->ifindex) { + log_ndisc(nd, "Received an ICMPv6 packet on interface %i, expected %i, ignoring.", + packet->ifindex, nd->ifindex); + return 0; + } + /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states * that hosts MUST discard messages with the null source address. */ if (in6_addr_is_null(&packet->sender_address)) { diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 21a80da38ee90..948f7bc382cd6 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -226,6 +226,12 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat return 0; } + if (packet->ifindex != ra->ifindex) { + log_radv(ra, "Received an ICMPv6 packet on interface %i, expected %i, ignoring.", + packet->ifindex, ra->ifindex); + return 0; + } + (void) radv_process_packet(ra, packet); return 0; } diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c index 84dc73e84fa5f..2ad42b5285f70 100644 --- a/src/libsystemd-network/test-ndisc-ra.c +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -12,6 +12,7 @@ #include "sd-radv.h" #include "alloc-util.h" +#include "fd-util.h" #include "icmp6-test-util.h" #include "in-addr-util.h" #include "radv-internal.h" @@ -245,6 +246,38 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat return 0; } +TEST(ra_ifindex_mismatch) { + static const struct nd_router_solicit rs = { + .nd_rs_type = ND_ROUTER_SOLICIT, + }; + + _cleanup_free_ uint8_t *buf = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL; + ssize_t buflen; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0); + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_radv_new(&ra) >= 0); + assert_se(sd_radv_attach_event(ra, e, 0) >= 0); + assert_se(sd_radv_set_ifindex(ra, 42) >= 0); + assert_se(sd_radv_start(ra) >= 0); + + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se((buflen = next_datagram_size_fd(test_fd[0])) >= 0); + assert_se(buf = new(uint8_t, buflen)); + assert_se(read(test_fd[0], buf, buflen) == buflen); + + test_ifindex = 43; + assert_se(write(test_fd[0], &rs, sizeof(rs)) == sizeof(rs)); + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se(next_datagram_size_fd(test_fd[0]) == -EAGAIN); + + assert_se(sd_radv_stop(ra) >= 0); + test_fd[0] = safe_close(test_fd[0]); +} + TEST(ra) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *recv_router_advertisement = NULL; diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 51adc4e685316..9c1e6f03a4c7f 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -227,6 +227,18 @@ static void test_callback_ra(sd_ndisc *nd, sd_ndisc_event_t event, void *message sd_event_exit(e, 0); } +static void test_callback_count( + sd_ndisc *nd, + sd_ndisc_event_t event, + void *message, + void *userdata) { + + unsigned *count = ASSERT_PTR(userdata); + + if (event == SD_NDISC_EVENT_ROUTER) + (*count)++; +} + static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; assert_se(icmp6_packet_receive(fd, &packet) >= 0); @@ -234,6 +246,27 @@ static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userda return send_ra(0); } +TEST(rs_ifindex_mismatch) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + unsigned count = 0; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_count, &count) >= 0); + assert_se(sd_ndisc_start(nd) >= 0); + + test_ifindex = 43; + send_ra(0); + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se(count == 0); + + assert_se(sd_ndisc_stop(nd) >= 0); + test_fd[1] = safe_close(test_fd[1]); +} + TEST(rs) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; From d52c8f2068b57e191bafd01d9d60bddbf4a20419 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 12:16:01 +0000 Subject: [PATCH 2001/2155] tree-wide: Replace exp10() with our own impl exp10() has a symbol version > 2.34 on latest glibc. To allow dropping our baseline required glibc runtime version to <= 2.34, let's add our own version to prevent pulling in the newer symbol from glibc. --- src/basic/math-util.c | 24 ++++++++++++++++++++++ src/basic/math-util.h | 5 +++++ src/basic/meson.build | 1 + src/libsystemd/sd-json/sd-json.c | 6 +++++- src/shared/color-util.c | 3 +-- src/shared/dns-rr.c | 9 ++++---- src/test/test-math-util.c | 35 ++++++++++++++++++++++++++++++++ 7 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 src/basic/math-util.c diff --git a/src/basic/math-util.c b/src/basic/math-util.c new file mode 100644 index 0000000000000..3d95e40016c51 --- /dev/null +++ b/src/basic/math-util.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "math-util.h" + +double xexp10i(int n) { + /* Powers of 10 up to 10^22 are exact in IEEE-754 binary64. */ + static const double table[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, + }; + bool negative = n < 0; + + /* Cast before negation so n == INT_MIN doesn't invoke signed-overflow UB. Unsigned negation + * wraps to the magnitude we want. */ + unsigned k = negative ? -(unsigned) n : (unsigned) n; + + /* 10^309 already overflows binary64 to +Inf; anything beyond just stays there. */ + k = MIN(k, 309u); + double r = k < ELEMENTSOF(table) ? table[k] : table[ELEMENTSOF(table) - 1]; + for (unsigned i = ELEMENTSOF(table) - 1; i < k; i++) + r *= 10.0; + + return negative ? 1.0 / r : r; +} diff --git a/src/basic/math-util.h b/src/basic/math-util.h index f3c471b1606ee..cac5fc31311d5 100644 --- a/src/basic/math-util.h +++ b/src/basic/math-util.h @@ -12,3 +12,8 @@ /* To avoid x == y and triggering compile warning -Wfloat-equal. This returns false if one of the argument is * NaN or infinity. One of the argument must be a floating point. */ #define fp_equal(x, y) iszero_safe((x) - (y)) + +/* 10^n. Exact for |n| ≤ 22; otherwise multiplies and may accumulate rounding error. Saturates to + * 0.0 or +Inf outside binary64's exponent range; large |n| is capped internally so untrusted + * inputs can't cause unbounded work. */ +double xexp10i(int n); diff --git a/src/basic/meson.build b/src/basic/meson.build index 9007cff178149..20c424b45bc51 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -64,6 +64,7 @@ basic_sources = files( 'log.c', 'log-context.c', 'login-util.c', + 'math-util.c', 'memfd-util.c', 'memory-util.c', 'mempool.c', diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 1ba07da6f9b4f..dad7a3174d94a 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -2801,7 +2801,11 @@ static int json_parse_number(const char **p, JsonValue *ret) { *p = c; if (is_real) { - ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); + /* Clamp before casting to int — a JSON input with an absurdly large exponent could + * otherwise trigger undefined behaviour in the double→int conversion. xexp10i() + * itself saturates anything beyond ~10^308, so clamping at INT_MAX is harmless. */ + int e = exponent > (double) INT_MAX ? INT_MAX : (int) exponent; + ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * xexp10i(exponent_negative ? -e : e); return JSON_TOKEN_REAL; } else if (negative) { ret->integer = i; diff --git a/src/shared/color-util.c b/src/shared/color-util.c index f2add33b1d85b..1d30732147f83 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "color-util.h" +#include "math-util.h" void rgb_to_hsv(double r, double g, double b, double *ret_h, double *ret_s, double *ret_v) { diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 0ad01de88a684..eb91910e835f8 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "bitmap.h" #include "dns-answer.h" /* IWYU pragma: keep */ @@ -13,6 +11,7 @@ #include "hash-funcs.h" #include "hexdecoct.h" #include "json-util.h" +#include "math-util.h" #include "memory-util.h" #include "siphash24.h" #include "string-table.h" @@ -773,9 +772,9 @@ static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t alt int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + double siz = (size >> 4) * xexp10i(size & 0xF); + double hor = (horiz_pre >> 4) * xexp10i(horiz_pre & 0xF); + double ver = (vert_pre >> 4) * xexp10i(vert_pre & 0xF); if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", (lat / 60000 / 60), diff --git a/src/test/test-math-util.c b/src/test/test-math-util.c index 9771576fcbb6b..3e8a9d5ba321a 100644 --- a/src/test/test-math-util.c +++ b/src/test/test-math-util.c @@ -107,4 +107,39 @@ TEST(fp_equal) { assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); } +TEST(xexp10i) { + /* Table-lookup range: every value is exact in binary64 */ + ASSERT_TRUE(fp_equal(xexp10i(0), 1.0)); + ASSERT_TRUE(fp_equal(xexp10i(1), 10.0)); + ASSERT_TRUE(fp_equal(xexp10i(2), 100.0)); + ASSERT_TRUE(fp_equal(xexp10i(22), 1e22)); + + /* Negative exponents */ + ASSERT_TRUE(fp_equal(xexp10i(-1), 0.1)); + ASSERT_TRUE(fp_equal(xexp10i(-3), 0.001)); + + /* Beyond the table: result still matches a plain 10.0 multiplication chain (no precision + * claim beyond binary64) */ + ASSERT_TRUE(xexp10i(23) > 0.9e23 && xexp10i(23) < 1.1e23); + ASSERT_TRUE(xexp10i(100) > 0.9e100 && xexp10i(100) < 1.1e100); + + /* Overflow saturates to +Inf, underflow to 0 — matching glibc exp10() */ + ASSERT_TRUE(isinf(xexp10i(400))); + ASSERT_TRUE(fp_equal(xexp10i(-400), 0.0)); + + /* Pathological inputs must still terminate quickly thanks to the internal cap */ + ASSERT_TRUE(isinf(xexp10i(INT_MAX))); + ASSERT_TRUE(fp_equal(xexp10i(INT_MIN), 0.0)); + + /* Regression guard for the DBL_MAX round-trip described in math-util.c: 10^308 must be small + * enough that DBL_MAX's mantissa (≈ 1.7976931348623157) multiplied by it does not overflow + * to +Inf. Delegating to __builtin_powi(10.0, n) here breaks this — libgcc's __powidf2 + * accumulates a few ULPs of error through repeated squaring, and the product spills over. + * Matching test-json's delta of 0.0001, the reconstructed value must land within 0.01% of + * DBL_MAX. */ + double dbl_max_reconstructed = 1.7976931348623157 * xexp10i(308); + ASSERT_FALSE(isinf(dbl_max_reconstructed)); + ASSERT_TRUE(ABS(1.0 - DBL_MAX / dbl_max_reconstructed) < 0.0001); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From d9600a2ac0433095189688ebd86a24fcc8ed9f6c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 19:20:02 +0000 Subject: [PATCH 2002/2155] test: add test-link-abi to enforce link-time ABI invariants For every built executable, internal shared library, and plugin module, verify two link-time properties via readelf: 1. No imported GLIBC symbol's version is newer than 2.34. 2. The dynamic section's NEEDED entries reference only glibc, the runtime linker, our own libraries. --- meson.build | 3 + test/meson.build | 12 +++ test/test-link-abi.py | 187 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100755 test/test-link-abi.py diff --git a/meson.build b/meson.build index d9c8b84e4707b..5b880cd95a7e1 100644 --- a/meson.build +++ b/meson.build @@ -2267,6 +2267,7 @@ test_dlopen = executables_by_name.get('test-dlopen') nss_targets = [] pam_targets = [] +module_targets = [] foreach dict : modules name = dict.get('name') is_nss = name.startswith('nss_') @@ -2311,6 +2312,8 @@ foreach dict : modules implicit_include_directories : false, ) + module_targets += lib + if is_nss # We cannot use shared_module because it does not support version suffix. # Unfortunately shared_library insists on creating the symlink… diff --git a/test/meson.build b/test/meson.build index b64f971126df6..fbc88db99dd02 100644 --- a/test/meson.build +++ b/test/meson.build @@ -124,6 +124,18 @@ if want_tests != 'false' exe, suite : 'udev', args : ['verify', '--resolve-names=late', all_rules]) + + link_abi_targets = [libsystemd, libudev, libshared, libcore] + link_abi_targets += module_targets + foreach _, exe : executables_by_name + link_abi_targets += exe + endforeach + test('test-link-abi', + files('test-link-abi.py'), + args : link_abi_targets, + depends : link_abi_targets, + suite : 'dist', + timeout : 120) endif ############################################################ diff --git a/test/test-link-abi.py b/test/test-link-abi.py new file mode 100755 index 0000000000000..a36e6c32d7ec0 --- /dev/null +++ b/test/test-link-abi.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +"""Verify two ABI properties of the supplied ELF objects: + +1. No imported GLIBC symbol is newer than the baseline (default 2.34). +2. Each binary's NEEDED entries only reference glibc itself or our own + internal shared libraries — anything else means we've grown a hard + link-time dependency on a third-party library that should be dlopen()'d + instead. +""" + +import argparse +import re +import subprocess +import sys + +GLIBC_RE: re.Pattern[str] = re.compile(r'\bGLIBC_(\d+)\.(\d+)(?:\.\d+)?\b') +NEEDED_RE: re.Pattern[str] = re.compile(r'\(NEEDED\)\s+Shared library:\s+\[([^\]]+)\]') + +Version = tuple[int, int] + +# Shared libraries that ship as part of the C library itself; always allowed. +# Matched by prefix so the soversion is not hardcoded. glibc splits libc/libm/etc.; +# musl bundles everything into a single libc whose soname encodes the architecture +# (e.g. libc.musl-x86_64.so.1, libc.musl-aarch64.so.1). +LIBC_LIB_PREFIXES: tuple[str, ...] = ( + 'libc.so.', + 'libm.so.', + 'libresolv.so.', + 'libc.musl-', +) + +# GCC runtime support libraries (stack unwinding, soft-float helpers, C++ standard library). The +# toolchain pulls these in automatically and they're part of every glibc toolchain. Matched by +# prefix so the soversion is not hardcoded. +GCC_LIB_PREFIXES: tuple[str, ...] = ( + 'libasan.so.', + 'libclang_rt.asan.so', + 'libclang_rt.ubsan.so', + 'libubsan.so.', + 'libgcc_s.so.', + 'libstdc++.so.', +) + +# Our own shared libraries; matched by prefix because the soname encodes the +# project version (libsystemd-shared-NNN.so, libsystemd-core-NNN.so). +INTERNAL_LIB_PREFIXES: tuple[str, ...] = ( + 'libsystemd-shared-', + 'libsystemd-core-', + 'libsystemd.so.', + 'libudev.so.', +) + + +def parse_baseline(s: str) -> Version: + m = re.fullmatch(r'(\d+)\.(\d+)', s) + if not m: + raise argparse.ArgumentTypeError(f'baseline must be MAJOR.MINOR, got {s!r}') + return (int(m.group(1)), int(m.group(2))) + + +def is_elf(path: str) -> bool: + """Cheap ELF magic check so readelf isn't run on non-ELF inputs.""" + try: + with open(path, 'rb') as f: + return f.read(4) == b'\x7fELF' + except OSError: + return False + + +def is_internal(name: str) -> bool: + return any(name.startswith(p) for p in INTERNAL_LIB_PREFIXES) + + +def is_dynamic_linker(name: str) -> bool: + # The runtime linker's filename depends on the architecture: ld-linux-x86-64.so.2, + # ld-linux-aarch64.so.1, ld-linux-armhf.so.3, ld-linux-riscv64-lp64d.so.1 on the + # "ld-" naming; ld.so.1 (mips, ppc, s390 32-bit), ld64.so.1 (s390x, ppc64 BE), + # and ld64.so.2 (ppc64le) on the older naming. musl uses ld-musl-.so.1 + # (already covered by the "ld-" prefix). + return name.startswith(('ld-', 'ld.so.', 'ld64.so.')) + + +def is_libc(name: str) -> bool: + return name.startswith(LIBC_LIB_PREFIXES) + + +def glibc_violations(path: str, baseline: Version) -> list[tuple[str, str]]: + """Return a sorted list of (symbol, "GLIBC_X.Y") pairs above the baseline.""" + out = subprocess.check_output( + ['readelf', '-W', '--dyn-syms', path], + text=True, + stderr=subprocess.DEVNULL, + ) + + found: set[tuple[str, str]] = set() + for line in out.splitlines(): + m = GLIBC_RE.search(line) + if not m: + continue + # readelf --dyn-syms lists both imported (Ndx=UND) and exported symbols; + # only the former carry a real link-time dependency on the listed glibc + # version. Skip anything that isn't UND. + parts = line.split() + if len(parts) < 8 or parts[6] != 'UND': + continue + ver: Version = (int(m.group(1)), int(m.group(2))) + if ver <= baseline: + continue + sym = next((t for t in parts if '@' in t), line.strip()) + found.add((sym, m.group(0))) + return sorted(found) + + +def needed_violations(path: str) -> list[str]: + """Return NEEDED entries outside the always-allowed set.""" + out = subprocess.check_output( + ['readelf', '-W', '-d', path], + text=True, + stderr=subprocess.DEVNULL, + ) + + bad: list[str] = [] + for line in out.splitlines(): + m = NEEDED_RE.search(line) + if not m: + continue + name = m.group(1) + if ( + is_libc(name) + or name.startswith(GCC_LIB_PREFIXES) + or is_dynamic_linker(name) + or is_internal(name) + ): + continue + bad.append(name) + return bad + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument( + '--baseline', + type=parse_baseline, + default=(2, 34), + help='Maximum allowed GLIBC version (default: 2.34)', + ) + ap.add_argument('paths', nargs='*', metavar='PATH', help='ELF files to check') + args = ap.parse_args() + + baseline: Version = args.baseline + checked = 0 + failed = 0 + for path in args.paths: + if not is_elf(path): + # Some inputs passed by meson may be scripts or non-ELF + # generators; silently skip those rather than fail. + continue + checked += 1 + + glibc_bad = glibc_violations(path, baseline) + needed_bad = needed_violations(path) + + if glibc_bad or needed_bad: + failed += 1 + print(f'{path}:', file=sys.stderr) + if glibc_bad: + print(f' imports symbols newer than GLIBC_{baseline[0]}.{baseline[1]}:', file=sys.stderr) + for sym, ver_str in glibc_bad: + print(f' {sym} ({ver_str})', file=sys.stderr) + if needed_bad: + print(' links against unexpected libraries (dlopen() them instead):', file=sys.stderr) + for name in needed_bad: + print(f' {name}', file=sys.stderr) + + baseline_str = f'GLIBC_{baseline[0]}.{baseline[1]}' + if failed: + print(f'\nFAIL: {failed} of {checked} ELF objects failed the ABI checks.', file=sys.stderr) + return 1 + print( + f'OK: {checked} ELF objects checked; all within {baseline_str} and with only allowed NEEDED entries.' + ) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From 6c119e11b26bde34ac9925cc2ad9cda690442c61 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 20 May 2026 10:37:58 +0200 Subject: [PATCH 2003/2155] dns-packet: drop unnecessary indentation Bail out early if QDCOUNT == 0, similarly to what we already do in dns_packet_extract_answer() when RRCOUNT == 0. --- src/shared/dns-packet.c | 81 ++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index 4fdef2b570a5f..bb17c94d4aa60 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -2452,51 +2452,54 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) assert(ret_question); n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) - return -ENOMEM; + if (n == 0) { + *ret_question = NULL; + return 0; + } - _cleanup_set_free_ Set *keys = NULL; /* references to keys are kept by Question */ + question = dns_question_new(n); + if (!question) + return -ENOMEM; - keys = set_new(&dns_resource_key_hash_ops); - if (!keys) - return log_oom(); + _cleanup_set_free_ Set *keys = NULL; /* references to keys are kept by Question */ + + keys = set_new(&dns_resource_key_hash_ops); + if (!keys) + return log_oom(); + + /* Pre-allocate the question hashmap, but cap the pre-allocation to a number of questions the + * packet can realistically contain. That is, pick the minimal value from the claimed number + * of questions (n) and a maximum number of potential questions the remaining packet data can + * actually contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U + * is the minimum size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). + * + * Note for the multiplication: higher multipliers give slightly higher efficiency through + * hash collisions, but the gains quickly drop off after 2. */ + r = set_reserve(keys, MIN(n, (p->size - p->rindex) / 5U) * 2); + if (r < 0) + return r; - /* Pre-allocate the question hashmap, but cap the pre-allocation to a number of questions the - * packet can realistically contain. That is, pick the minimal value from the claimed number - * of questions (n) and a maximum number of potential questions the remaining packet data can - * actually contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U - * is the minimum size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). - * - * Note for the multiplication: higher multipliers give slightly higher efficiency through - * hash collisions, but the gains quickly drop off after 2. */ - r = set_reserve(keys, MIN(n, (p->size - p->rindex) / 5U) * 2); + for (unsigned i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool qu; + + r = dns_packet_read_key(p, &key, &qu, NULL); if (r < 0) return r; - for (unsigned i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool qu; - - r = dns_packet_read_key(p, &key, &qu, NULL); - if (r < 0) - return r; - - if (!dns_type_is_valid_query(key->type)) - return -EBADMSG; + if (!dns_type_is_valid_query(key->type)) + return -EBADMSG; - r = set_put(keys, key); - if (r < 0) - return r; - if (r == 0) - /* Already in the Question, let's skip */ - continue; + r = set_put(keys, key); + if (r < 0) + return r; + if (r == 0) + /* Already in the Question, let's skip */ + continue; - r = dns_question_add_raw(question, key, qu ? DNS_QUESTION_WANTS_UNICAST_REPLY : 0); - if (r < 0) - return r; - } + r = dns_question_add_raw(question, key, qu ? DNS_QUESTION_WANTS_UNICAST_REPLY : 0); + if (r < 0) + return r; } *ret_question = TAKE_PTR(question); @@ -2514,8 +2517,10 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { assert(ret_answer); n = DNS_PACKET_RRCOUNT(p); - if (n == 0) + if (n == 0) { + *ret_answer = NULL; return 0; + } /* Pre-allocate the answer hashmap, but cap the pre-allocation to a number of RRs the packet can * realistically contain. That is, pick the minimal value from the claimed number of RRs (n) and a From 180985c2cef3648aa1b95338272177610c89c185 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 20 May 2026 10:47:22 +0200 Subject: [PATCH 2004/2155] dns-packet: bail out early if the packet is too short Let's bail out early if the packet claims to contain some questions or answer RRs, but the remaining packet data size is not enough to hold a single such entry. Follow-up for e7cd836dcffb5f85d66a156904fc68f8b654a290. --- src/shared/dns-packet.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index bb17c94d4aa60..429e5c82c1774 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -2446,7 +2446,7 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) { _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - unsigned n; + unsigned n, prealloc; int r; assert(ret_question); @@ -2457,6 +2457,14 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) return 0; } + /* Calculate the maximum number of potential questions the remaining packet data can actually + * contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U is the minimum + * size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). */ + prealloc = (p->size - p->rindex) / 5U; + if (prealloc == 0) + /* QDCOUNT > 0 but there's not enough space left for a single question. */ + return -EMSGSIZE; + question = dns_question_new(n); if (!question) return -ENOMEM; @@ -2470,12 +2478,11 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) /* Pre-allocate the question hashmap, but cap the pre-allocation to a number of questions the * packet can realistically contain. That is, pick the minimal value from the claimed number * of questions (n) and a maximum number of potential questions the remaining packet data can - * actually contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U - * is the minimum size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). + * actually contain, see above. * * Note for the multiplication: higher multipliers give slightly higher efficiency through * hash collisions, but the gains quickly drop off after 2. */ - r = set_reserve(keys, MIN(n, (p->size - p->rindex) / 5U) * 2); + r = set_reserve(keys, MIN(n, prealloc) * 2); if (r < 0) return r; @@ -2509,7 +2516,7 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned n; + unsigned n, prealloc; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; bool bad_opt = false; int r; @@ -2522,12 +2529,18 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { return 0; } + /* Calculate the maximum number of potential RRs the remaining packet data can actually contain: + * p->size - p->rindex are the remaining unread bytes in the packet, and the 11U is the minimum size + * of each RR - 1 (NAME) + 2 (TYPE) + 2 (CLASS) + 4 (TTL) + 2 (RDLENGTH). */ + prealloc = (p->size - p->rindex) / 11U; + if (prealloc == 0) + /* RRCOUNT > 0 but there's not enough space left for a single RR. */ + return -EMSGSIZE; + /* Pre-allocate the answer hashmap, but cap the pre-allocation to a number of RRs the packet can * realistically contain. That is, pick the minimal value from the claimed number of RRs (n) and a - * maximum number of potential RRs the remaining packet data can actually contain: p->size - - * p->rindex are the remaining unread bytes in the packet, and the 11U is the minimum size of each RR - * - 1 (NAME) + 2 (TYPE) + 2 (CLASS) + 4 (TTL) + 2 (RDLENGTH). */ - answer = dns_answer_new(MIN(n, (p->size - p->rindex) / 11U)); + * maximum number of potential RRs the remaining packet data can actually contain, see above. */ + answer = dns_answer_new(MIN(n, prealloc)); if (!answer) return -ENOMEM; From 4f0271e92324a790a4f9c4932a35f871293637dd Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 30 Apr 2026 09:18:41 +0200 Subject: [PATCH 2005/2155] core: report unsupported service fields in varlink calls Just like for the unsupported/bad exec_fields we should show a message about what field is bad for service parameters. This commit adds it using the same pattern. The JSON parser works in fail-fast mode so we only display the first bad field (and it depends on the parser what it finds first). --- src/core/varlink-unit.c | 7 +++++-- test/units/TEST-26-SYSTEMCTL.sh | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 964efa3534f8d..c90ff987e8405 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -753,7 +753,8 @@ typedef struct StartTransientContextParameters { const char *description; TransientExecContextParameters exec; TransientServiceParameters service; - const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */ + const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */ + const char *bad_service_field; } StartTransientContextParameters; static void start_transient_context_parameters_done(StartTransientContextParameters *p) { @@ -885,7 +886,7 @@ static int dispatch_transient_service(const char *name, sd_json_variant *variant StartTransientContextParameters *p = ASSERT_PTR(userdata); p->service.present = true; - return sd_json_dispatch(variant, service_dispatch, /* flags= */ 0, &p->service); + return sd_json_dispatch_full(variant, service_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->service, &p->bad_service_field); } static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { @@ -911,6 +912,8 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant * actual sub-property. */ if (streq(bad_field, "Exec") && !isempty(p->context.bad_exec_field)) p->unsupported_property = strjoin("Exec.", p->context.bad_exec_field); + else if (streq(bad_field, "Service") && !isempty(p->context.bad_service_field)) + p->unsupported_property = strjoin("Service.", p->context.bad_service_field); else p->unsupported_property = strdup(bad_field); if (!p->unsupported_property) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index a98ef7646d33b..d489cc3ec8083 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -690,8 +690,17 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" # Unknown field in Exec is rejected as PropertyNotSupported defer_transient_cleanup varlink-transient-unknown-exec.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.PropertyNotSupported" +unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_exec" | grep "Exec.RootDirectory" +# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported, +# and the offending sub-property is identified +defer_transient_cleanup varlink-transient-unknown-service.service +unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_service" | grep "Service.Restart" set -o pipefail transient_cleanup From 66892d877c8c921f993bff5be9e2978ea215537b Mon Sep 17 00:00:00 2001 From: Quentin Deslandes Date: Mon, 16 Feb 2026 19:39:04 +0100 Subject: [PATCH 2006/2155] udev/net: add IRQAffinityPolicy= option for .link files Add support for configuring IRQ affinity for network interfaces via systemd .link files. For now, the new IRQAffinityPolicy= option in the [Link] section only accepts "single", which pins all MSI IRQs to CPU 0. This allows declarative IRQ affinity configuration for network devices during udev processing, which is useful for optimizing network performance on multi-core systems. Further commits will expand the options supported by IRQAffinityPolicy=. --- src/udev/net/link-config-gperf.gperf | 2 + src/udev/net/link-config.c | 107 +++++++++++++++ src/udev/net/link-config.h | 11 ++ src/udev/net/test-link-config-tables.c | 1 + test/units/TEST-17-UDEV.irq-affinity.sh | 171 ++++++++++++++++++++++++ 5 files changed, 292 insertions(+) create mode 100755 test/units/TEST-17-UDEV.irq-affinity.sh diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index ef68dab339fff..0b63a1e446279 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -132,6 +132,8 @@ Link.TxMaxCoalescedHighFrames, config_parse_coalesce_u32, Link.CoalescePacketRateSampleIntervalSec, config_parse_coalesce_sec, 0, offsetof(LinkConfig, coalesce.rate_sample_interval) /* Rx RPS CPU mask */ Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, 0, offsetof(LinkConfig, rps_cpu_mask) +/* IRQ affinity settings */ +Link.IRQAffinityPolicy, config_parse_irq_affinity_policy, 0, offsetof(LinkConfig, irq_affinity_policy) /* SR-IOV settings */ Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 3c4b886e6759c..4a1512365d6b2 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -15,6 +15,7 @@ #include "creds-util.h" #include "device-private.h" #include "device-util.h" +#include "dirent-util.h" #include "escape.h" #include "ether-addr-util.h" #include "ethtool-util.h" @@ -270,6 +271,7 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) { .eee_enabled = -1, .eee_tx_lpi_enabled = -1, .eee_tx_lpi_timer_usec = USEC_INFINITY, + .irq_affinity_policy = _IRQ_AFFINITY_POLICY_INVALID, }; FOREACH_ELEMENT(feature, config->features) @@ -922,6 +924,96 @@ static int link_apply_sr_iov_config(Link *link) { return 0; } +static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { + _cleanup_free_ char *affinity_path = NULL, *mask_str = NULL; + unsigned n_groups = cpu / 32; + int r; + + assert(link); + + if (asprintf(&affinity_path, "/proc/irq/%u/smp_affinity", irq) < 0) + return log_oom(); + + /* Convert CPU number to hex bitmask. + * For CPU N, set bit N (1 << N). CPUs are split by comma-separated + * 32-bits groups. To assign CPU 32, we should write 1,00000000 */ + + if (asprintf(&mask_str, "%x", 1U << (cpu % 32)) < 0) + return log_oom(); + + for (unsigned i = 0; i < n_groups; i++) + if (!strextend(&mask_str, ",00000000")) + return log_oom(); + + r = write_string_file(affinity_path, mask_str, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to set IRQ %u affinity to CPU %u: %m", irq, cpu); + + log_link_debug(link, "Set IRQ %u affinity to CPU %u.", irq, cpu); + + return 0; +} + +static int link_apply_irq_affinity_single(Link *link) { + _cleanup_closedir_ DIR *dir = NULL; + int r; + + assert(link); + assert(link->config); + assert(ASSERT_PTR(link->event)->dev); + + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); + if (r < 0) { + if (r != -ENOENT) + return log_link_error_errno(link, r, "Failed to open device/msi_irqs: %m"); + log_link_debug_errno(link, r, "No MSI IRQs found, skipping IRQ affinity configuration: %m"); + return 0; + } + + FOREACH_DIRENT(de, dir, return log_link_error_errno(link, errno, "Failed to read directory device/msi_irqs: %m")) { + unsigned irq; + + r = safe_atou(de->d_name, &irq); + if (r < 0) + return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); + + (void) set_irq_affinity(link, irq, /* cpu= */ 0); + } + + log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU 0)."); + + return 0; +} + +static int link_apply_irq_affinity(Link *link) { + _cleanup_(cpu_set_done) CPUSet effective_cpus = {}; + const char *syspath; + int r; + + assert(link); + assert(link->config); + assert(ASSERT_PTR(link->event)->dev); + + if (link->event->event_mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of IRQ affinity settings."); + return 0; + } + + if (link->config->irq_affinity_policy < 0) + return 0; + + r = sd_device_get_syspath(link->event->dev, &syspath); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get syspath: %m"); + + switch (link->config->irq_affinity_policy) { + case IRQ_AFFINITY_POLICY_SINGLE: + return link_apply_irq_affinity_single(link); + default: + assert_not_reached(); + } +} + static int link_apply_rps_cpu_mask(Link *link) { _cleanup_free_ char *mask_str = NULL; LinkConfig *config; @@ -1051,6 +1143,10 @@ int link_apply_config(LinkConfigContext *ctx, Link *link) { if (r < 0) return r; + r = link_apply_irq_affinity(link); + if (r < 0) + return r; + return link_apply_udev_properties(link); } @@ -1400,3 +1496,14 @@ DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy, DEFINE_CONFIG_PARSE_ENUMV(config_parse_alternative_names_policy, alternative_names_policy, NamePolicy, _NAMEPOLICY_INVALID); + +static const char* const irq_affinity_policy_table[_IRQ_AFFINITY_POLICY_MAX] = { + [IRQ_AFFINITY_POLICY_SINGLE] = "single", +}; + +DEFINE_STRING_TABLE_LOOKUP(irq_affinity_policy, IRQAffinityPolicy); +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT( + config_parse_irq_affinity_policy, + irq_affinity_policy, + IRQAffinityPolicy, + _IRQ_AFFINITY_POLICY_INVALID); diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index e00327c01526b..b6853cd922c99 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -22,6 +22,12 @@ typedef enum MACAddressPolicy { _MAC_ADDRESS_POLICY_INVALID = -EINVAL, } MACAddressPolicy; +typedef enum IRQAffinityPolicy { + IRQ_AFFINITY_POLICY_SINGLE, + _IRQ_AFFINITY_POLICY_MAX, + _IRQ_AFFINITY_POLICY_INVALID = -EINVAL, +} IRQAffinityPolicy; + typedef struct Link { UdevEvent *event; LinkConfig *config; @@ -113,6 +119,9 @@ struct LinkConfig { /* Rx RPS CPU mask */ CPUSet rps_cpu_mask; + /* IRQ affinity */ + IRQAffinityPolicy irq_affinity_policy; + /* SR-IOV */ uint32_t sr_iov_num_vfs; OrderedHashmap *sr_iov_by_section; @@ -136,6 +145,7 @@ int link_get_config(LinkConfigContext *ctx, Link *link); int link_apply_config(LinkConfigContext *ctx, Link *link); DECLARE_STRING_TABLE_LOOKUP(mac_address_policy, MACAddressPolicy); +DECLARE_STRING_TABLE_LOOKUP(irq_affinity_policy, IRQAffinityPolicy); /* gperf lookup function */ const struct ConfigPerfItem* link_config_gperf_lookup(const char *str, GPERF_LEN_TYPE length); @@ -150,3 +160,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mac_address_policy); CONFIG_PARSER_PROTOTYPE(config_parse_name_policy); CONFIG_PARSER_PROTOTYPE(config_parse_alternative_names_policy); CONFIG_PARSER_PROTOTYPE(config_parse_rps_cpu_mask); +CONFIG_PARSER_PROTOTYPE(config_parse_irq_affinity_policy); diff --git a/src/udev/net/test-link-config-tables.c b/src/udev/net/test-link-config-tables.c index 499778505c442..58b628bc9b388 100644 --- a/src/udev/net/test-link-config-tables.c +++ b/src/udev/net/test-link-config-tables.c @@ -8,6 +8,7 @@ int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); test_table(MACAddressPolicy, mac_address_policy, MAC_ADDRESS_POLICY); + test_table(IRQAffinityPolicy, irq_affinity_policy, IRQ_AFFINITY_POLICY); return EXIT_SUCCESS; } diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh new file mode 100755 index 0000000000000..5fa8bd331f9d5 --- /dev/null +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -0,0 +1,171 @@ +#!/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 IRQAffinityPolicy configuration + +# Find a network interface with MSI IRQs (typically virtio-net in the VM) +# We need a real device to test actual IRQ affinity application +find_interface_with_msi_irqs() { + for iface in /sys/class/net/*; do + [[ -e "$iface" ]] || continue + iface_name=$(basename "$iface") + [[ "$iface_name" == "lo" ]] && continue + msi_irqs_path="$iface/device/msi_irqs" + if [[ -d "$msi_irqs_path" ]] && [[ -n "$(ls -A "$msi_irqs_path" 2>/dev/null)" ]]; then + echo "$iface_name" + return 0 + fi + done + return 1 +} + +get_interface_irqs() { + local iface="$1" + local msi_irqs_path="/sys/class/net/$iface/device/msi_irqs" + if [[ -d "$msi_irqs_path" ]]; then + ls "$msi_irqs_path" + fi +} + +check_irq_affinity() { + local irq="$1" + local expected_mask="$2" + local affinity + affinity=$(cat "/proc/irq/$irq/smp_affinity") + # Remove leading zeros and commas for comparison + affinity=$(echo "$affinity" | sed 's/^[0,]*//;s/,//g') + expected_mask=$(echo "$expected_mask" | sed 's/^[0,]*//;s/,//g') + [[ "$affinity" == "$expected_mask" ]] +} + +# Test 1: Verify IRQ affinity is actually applied on a real device +if iface=$(find_interface_with_msi_irqs); then + echo "Found interface with MSI IRQs: $iface" + + # Get the MAC address of the interface + mac=$(cat "/sys/class/net/$iface/address") + + # Get IRQs before applying policy + irqs=$(get_interface_irqs "$iface") + echo "Interface $iface has IRQs: $irqs" + + # Test 1a: test single policy + mkdir -p /run/systemd/network/ + cat >/run/systemd/network/00-test-irq-affinity.link </run/systemd/network/10-test-irq.link <&1) +assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq.link" "$output" + +# Test 3: Invalid policy values are rejected/warned +cat >/run/systemd/network/10-test-irq-invalid.link </run/systemd/network/10-test-irq-empty.link < Date: Mon, 16 Feb 2026 20:38:19 +0100 Subject: [PATCH 2007/2155] udev/net: implement IRQAffinityPolicy=spread with topology awareness Implement the spread policy for IRQ affinity distribution using a topology-aware algorithm. The algorithm: 1. Discovers CPU topology from sysfs (NUMA node, package, die/L3, core) 2. Groups CPUs by L3 cache domain (die) with equidistant ordering 3. Round-robins across dies, spreading IRQs across the system 4. Uses first hyperthread of each core before second hyperthreads 5. Applies IRQ affinity via /proc/irq//smp_affinity When there are more IRQs than CPUs, queues wrap around using round-robin. --- src/basic/sort-util.c | 9 + src/basic/sort-util.h | 1 + src/shared/numa-util.c | 57 +++ src/shared/numa-util.h | 2 + src/udev/net/link-config.c | 515 ++++++++++++++++++++++++ src/udev/net/link-config.h | 1 + test/units/TEST-17-UDEV.irq-affinity.sh | 41 ++ 7 files changed, 626 insertions(+) diff --git a/src/basic/sort-util.c b/src/basic/sort-util.c index a76a6c85a8219..8aca5cac769cd 100644 --- a/src/basic/sort-util.c +++ b/src/basic/sort-util.c @@ -81,3 +81,12 @@ int cmp_uint16(const uint16_t *a, const uint16_t *b) { return CMP(*a, *b); } + +int cmp_unsigned(const unsigned *a, const unsigned *b) { + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + + return CMP(*a, *b); +} diff --git a/src/basic/sort-util.h b/src/basic/sort-util.h index 8e7a1991bb692..37c196158eb38 100644 --- a/src/basic/sort-util.h +++ b/src/basic/sort-util.h @@ -44,3 +44,4 @@ void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_ int cmp_int(const int *a, const int *b); int cmp_uint16(const uint16_t *a, const uint16_t *b); +int cmp_unsigned(const unsigned *a, const unsigned *b); diff --git a/src/shared/numa-util.c b/src/shared/numa-util.c index 228ea7ad2d14f..9097ccbc313c7 100644 --- a/src/shared/numa-util.c +++ b/src/shared/numa-util.c @@ -154,6 +154,63 @@ static int numa_max_node(void) { return max_node; } +int numa_get_node_from_cpu(unsigned cpu, unsigned *ret) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(ret); + + d = opendir("/sys/devices/system/node"); + if (!d) + return -errno; + + FOREACH_DIRENT(de, d, break) { + char p[STRLEN("/sys/devices/system/node/node/cpulist") + DECIMAL_STR_MAX(unsigned) + 1]; + _cleanup_(cpu_set_done) CPUSet cpus = {}; + _cleanup_free_ char *cpulist = NULL; + const char *n; + unsigned node; + + if (de->d_type != DT_DIR) + continue; + + n = startswith(de->d_name, "node"); + if (!n) + continue; + + r = safe_atou(n, &node); + if (r < 0) { + log_debug_errno(r, "Failed to parse node number %s to unsigned, ignoring: %m", n); + continue; + } + + xsprintf(p, "/sys/devices/system/node/node%u/cpulist", node); + + r = read_virtual_file(p, SIZE_MAX, &cpulist, /* ret_size= */ NULL); + if (r < 0) { + log_debug_errno(r, "Failed to read %s, ignoring: %m", p); + continue; + } + + r = parse_cpu_set(cpulist, &cpus); + if (r < 0) { + log_debug_errno(r, "Failed to parse cpu set %s, ignoring: %m", cpulist); + continue; + } + + if (CPU_ISSET_S(cpu, cpus.allocated, cpus.set)) { + *ret = node; + return 0; + } + } + + /* CPU not found in any NUMA node, assume node 0 */ + log_debug("CPU %u not found in any NUMA node, assuming node 0.", cpu); + *ret = 0; + + return 0; +} + int numa_mask_add_all(CPUSet *mask) { int m; diff --git a/src/shared/numa-util.h b/src/shared/numa-util.h index c684dea803eca..6fec7c587baa8 100644 --- a/src/shared/numa-util.h +++ b/src/shared/numa-util.h @@ -31,6 +31,8 @@ static inline void numa_policy_reset(NUMAPolicy *p) { int apply_numa_policy(const NUMAPolicy *policy); int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret); +int numa_get_node_from_cpu(unsigned cpu, unsigned *ret); + int numa_mask_add_all(CPUSet *mask); DECLARE_STRING_TABLE_LOOKUP(mpol, int); diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 4a1512365d6b2..6211081d79a64 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include @@ -32,13 +33,16 @@ #include "netif-util.h" #include "netlink-util.h" #include "network-util.h" +#include "numa-util.h" #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" #include "random-util.h" #include "socket-util.h" +#include "sort-util.h" #include "specifier.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -924,6 +928,456 @@ static int link_apply_sr_iov_config(Link *link) { return 0; } +/* CPU topology information for IRQ affinity spread algorithm. */ +typedef struct CPUTopology { + unsigned cpu; + unsigned numa_node; + unsigned package_id; + unsigned die_id; /* L3 cache domain / chiplet */ + unsigned core_id; + bool is_first_thread; /* First hyperthread of a physical core */ +} CPUTopology; + +/* Die (L3 cache domain) information for spread algorithm */ +typedef struct DieInfo { + unsigned die_id; + unsigned *cpus; /* CPUs in this die (first HT only, sorted by core) */ + size_t cpu_count; + size_t next_idx; /* For round-robin within die */ +} DieInfo; + +/* Returns the first thread of a CPU siblings list */ +static int cpu_topology_get_first_thread(sd_device *cpu_node, unsigned *ret) { + const char *content, *end; + int r; + + assert(cpu_node); + assert(ret); + + r = sd_device_get_sysattr_value(cpu_node, "topology/thread_siblings_list", &content); + if (r < 0) + return r; + + end = content + strcspn(content, ",-"); + + _cleanup_free_ char *first = strndup(content, end - content); + if (!first) + return -ENOMEM; + + return safe_atou(first, ret); +} + +static int cpu_topology_compare(const CPUTopology *a, const CPUTopology *b) { + int r; + + assert(a); + assert(b); + + /* Sort by die first (for L3 cache grouping), then core, then CPU number */ + r = CMP(a->die_id, b->die_id); + if (r != 0) + return r; + + r = CMP(a->core_id, b->core_id); + if (r != 0) + return r; + + return CMP(a->cpu, b->cpu); +} + +/* Comparison function for sorting CPUs by CPU number (for die ID assignment) */ +static int cpu_number_compare(const CPUTopology *a, const CPUTopology *b) { + assert(a); + assert(b); + + return CMP(a->cpu, b->cpu); +} + +/* Assign logical die IDs based on L3 cache sharing topology. + * + * For IRQ spreading, the goal is to distribute interrupts across CPUs that + * don't share cache, minimizing cache line contention when processing packets. + * The L3 cache boundary is the key locality domain: CPUs sharing an L3 can + * exchange data cheaply, while cross-L3 communication is expensive. + * + * We use L3 shared_cpu_list rather than the kernel's physical die_id because: + * - On AMD EPYC, multiple CCXs on the same physical die have separate L3 caches + * - On Intel with Sub-NUMA Clustering, one die may have multiple L3 domains + * - L3 sharing reflects actual data locality, not physical packaging */ +static int assign_sequential_die_ids(CPUTopology *cpus, size_t count) { + _cleanup_strv_free_ char **l3_groups = NULL; + int r; + + assert(cpus); + + /* First, sort CPUs by CPU number for consistent discovery order */ + typesafe_qsort(cpus, count, cpu_number_compare); + + /* Assign die IDs based on order of L3 shared_cpu_list discovery */ + FOREACH_ARRAY(cpu, cpus, count) { + _cleanup_(sd_device_unrefp) sd_device *cpu_node = NULL; + char cpu_path[STRLEN("/sys/devices/system/cpu/cpu") + DECIMAL_STR_MAX(unsigned) + 1]; + const char *l3_list; + unsigned die_id = 0; + bool found = false; + + xsprintf(cpu_path, "/sys/devices/system/cpu/cpu%u", cpu->cpu); + r = sd_device_new_from_syspath(&cpu_node, cpu_path); + if (r < 0) + return r; + + r = sd_device_get_sysattr_value(cpu_node, "cache/index3/shared_cpu_list", &l3_list); + if (r < 0) { + /* No L3 info, fall back to package ID */ + cpu->die_id = cpu->package_id; + continue; + } + + /* Check if we've seen this L3 group before */ + STRV_FOREACH(g, l3_groups) { + if (streq(*g, l3_list)) { + cpu->die_id = die_id; + found = true; + break; + } + die_id++; + } + + if (!found) { + /* New L3 group, assign next sequential die ID */ + cpu->die_id = die_id; + r = strv_extend(&l3_groups, l3_list); + if (r < 0) + return r; + } + } + + return 0; +} + +static int discover_cpu_topology(CPUTopology **ret, size_t *ret_count) { + _cleanup_(sd_device_unrefp) sd_device *parent_node = NULL; + _cleanup_free_ CPUTopology *cpus = NULL; + const char *name; + size_t count = 0; + int r; + + assert(ret); + assert(ret_count); + + r = sd_device_new_from_syspath(&parent_node, "/sys/devices/system/cpu"); + if (r < 0) + return r; + + FOREACH_DEVICE_CHILD_WITH_SUFFIX(parent_node, cpu_node, name) { + char topo_path[STRLEN("/sys/devices/system/cpu/cpu/topology") + DECIMAL_STR_MAX(unsigned) + 1]; + const char *n; + unsigned cpu, online, first_thread; + + n = startswith(name, "cpu"); + if (!n) + continue; + + r = safe_atou(n, &cpu); + if (r < 0) { + log_debug_errno(r, "Failed to convert %s to unsigned, skipping: %m", n); + continue; + } + + r = device_get_sysattr_unsigned(cpu_node, "online", &online); + if (r == -ENOENT) + online = 1; /* CPU 0 lacks 'online' file, assume online */ + else if (r < 0 || online == 0) + continue; + + /* Check if topology directory exists (filters out cpu0 on some systems) */ + xsprintf(topo_path, "/sys/devices/system/cpu/cpu%u/topology", cpu); + if (access(topo_path, F_OK) < 0) { + log_debug_errno(errno, "Failed to access %s, ignoring: %m", topo_path); + continue; + } + + if (!GREEDY_REALLOC(cpus, count + 1)) + return -ENOMEM; + + cpus[count].cpu = cpu; + + r = numa_get_node_from_cpu(cpu, &cpus[count].numa_node); + if (r < 0) { + log_debug_errno(r, "Failed to get NUMA node for CPU %u, assuming NUMA node 0: %m", cpu); + cpus[count].numa_node = 0; + } + + r = device_get_sysattr_unsigned(cpu_node, "topology/physical_package_id", &cpus[count].package_id); + if (r < 0) { + log_device_debug_errno(cpu_node, r, "Failed to get physical_package_id, assuming package ID 0: %m"); + cpus[count].package_id = 0; + } + + /* die_id will be assigned later by assign_sequential_die_ids() */ + cpus[count].die_id = 0; + + r = device_get_sysattr_unsigned(cpu_node, "topology/core_id", &cpus[count].core_id); + if (r < 0) { + log_device_debug_errno(cpu_node, r, "Failed to get core_id, assuming core ID %u: %m", cpu); + cpus[count].core_id = cpu; + } + + r = cpu_topology_get_first_thread(cpu_node, &first_thread); + if (r < 0) + cpus[count].is_first_thread = true; + else + cpus[count].is_first_thread = (first_thread == cpu); + + count++; + } + + if (count == 0) + return -ENOENT; + + /* Assign sequential die IDs based on L3 discovery order */ + r = assign_sequential_die_ids(cpus, count); + if (r < 0) + return r; + + /* Sort CPUs by topology for consistent ordering */ + typesafe_qsort(cpus, count, cpu_topology_compare); + + *ret = TAKE_PTR(cpus); + *ret_count = count; + + return 0; +} + +/* Reorder indices so consecutive elements are maximally spread apart. + * + * Uses recursive divide-and-conquer: split in half, permute each half, + * then interleave. This ensures elements originally far apart become adjacent. + * + * Example trace for [0,1,2,3,4,5,6,7]: + * split into [0,1,2,3] and [4,5,6,7] + * recurse left: [0,1,2,3] -> [0,2,1,3] + * recurse right: [4,5,6,7] -> [4,6,5,7] + * interleave -> [0,4,2,6,1,5,3,7] + * + * The first N elements of the output are roughly evenly distributed across the + * original range, for any N. This is useful when assigning IRQs to CPUs: if a + * NIC has fewer IRQs than CPUs, the assigned CPUs will still be spread across + * the CPUs rather than all at the beginning. */ +static int equidist_permute(size_t *indices, size_t n_indices) { + _cleanup_free_ size_t *left = NULL, *right = NULL; + size_t left_count, right_count; + size_t li = 0, ri = 0, ti = 0; + int r; + + assert(indices); + + if (n_indices <= 1) + return 0; + + left_count = DIV_ROUND_UP(n_indices, 2); + right_count = n_indices - left_count; + + /* Recursively permute each half */ + left = newdup(size_t, indices, left_count); + right = newdup(size_t, &indices[left_count], right_count); + if (!left || !right) + return log_oom(); + + r = equidist_permute(left, left_count); + if (r < 0) + return r; + + r = equidist_permute(right, right_count); + if (r < 0) + return r; + + /* Interleave: left[0], right[0], left[1], right[1], ... */ + for (size_t i = 0; i < n_indices; i++) { + if (i % 2 == 0 && li < left_count) + indices[ti++] = left[li++]; + else if (ri < right_count) + indices[ti++] = right[ri++]; + else if (li < left_count) + indices[ti++] = left[li++]; + } + + return 0; +} + +static void die_info_free(DieInfo *dies, size_t count) { + assert(dies || count == 0); + + FOREACH_ARRAY(die, dies, count) + free(die->cpus); + free(dies); +} + +/* Build die information from topology, grouping CPUs by L3/die and filtering to first HT only */ +static int build_die_info(const CPUTopology *topology, size_t topology_count, DieInfo **ret, size_t *ret_count) { + DieInfo *dies = NULL; + size_t die_count = 0; + int r; + + assert(topology); + assert(ret); + assert(ret_count); + + CLEANUP_ARRAY(dies, die_count, die_info_free); + + FOREACH_ARRAY(cpu_topology, topology, topology_count) { + DieInfo *die = NULL; + + /* Only consider first hyperthreads for initial spread */ + if (!cpu_topology->is_first_thread) + continue; + + /* Find or create die entry */ + for (size_t j = 0; j < die_count; j++) + if (dies[j].die_id == cpu_topology->die_id) { + die = &dies[j]; + break; + } + + if (!die) { + if (!GREEDY_REALLOC(dies, die_count + 1)) + return log_oom(); + die = &dies[die_count++]; + *die = (DieInfo) { .die_id = cpu_topology->die_id }; + } + + if (!GREEDY_REALLOC(die->cpus, die->cpu_count + 1)) + return log_oom(); + + die->cpus[die->cpu_count++] = cpu_topology->cpu; + } + + /* Sort dies by die_id for determinism, then apply equidist to CPUs within each die */ + FOREACH_ARRAY(die, dies, die_count) { + _cleanup_free_ unsigned *reordered = NULL; + _cleanup_free_ size_t *indices = new(size_t, die->cpu_count); + if (!indices) + return log_oom(); + + for (size_t j = 0; j < die->cpu_count; j++) + indices[j] = j; + + r = equidist_permute(indices, die->cpu_count); + if (r < 0) + return r; + + /* Reorder CPUs according to equidist permutation */ + reordered = new(unsigned, die->cpu_count); + if (!reordered) + return log_oom(); + + for (size_t j = 0; j < die->cpu_count; j++) + reordered[j] = die->cpus[indices[j]]; + + memcpy(die->cpus, reordered, die->cpu_count * sizeof(unsigned)); + } + + *ret = TAKE_PTR(dies); + *ret_count = die_count; + + return 0; +} + +/* Select CPUs for IRQ affinity spreading with optimal topology distribution. + * + * Algorithm: + * 1. Group CPUs by die (L3 cache domain), using only first hyperthreads + * 2. Apply equidistant permutation to both die order and CPUs within each die, + * so consecutive selections are maximally spread (e.g., [0,1,2,3] -> [0,2,1,3]) + * 3. Round-robin across dies, picking one CPU per die per round + * 4. If more IRQs than physical cores, wrap around and reuse the same CPUs + * + * Ensures each IRQ gets a dedicated physical core before any core handles + * multiple IRQs. Two IRQs on one physical core time-share but benefit from warm + * cache, whereas spreading across SMT siblings causes resource contention with + * no cache benefit. + * Maximizes physical distance between consecutively assigned IRQs, improving + * cache distribution even when only a few IRQs are assigned. */ +static int select_spread_cpus( + const CPUTopology *topology, + size_t topology_count, + size_t n_irqs, + unsigned **ret, + size_t *ret_count) { + + _cleanup_free_ unsigned *selected = NULL; + _cleanup_free_ size_t *die_order = NULL; + DieInfo *dies = NULL; + size_t die_count = 0, selected_count = 0; + int r; + + assert(topology); + assert(ret); + assert(ret_count); + + CLEANUP_ARRAY(dies, die_count, die_info_free); + + selected = new(unsigned, n_irqs); + if (!selected) + return -ENOMEM; + + /* Build die information with first HT CPUs only */ + r = build_die_info(topology, topology_count, &dies, &die_count); + if (r < 0) + return r; + + if (die_count == 0) + return -ENOENT; + + /* Create equidistant die ordering */ + die_order = new(size_t, die_count); + if (!die_order) + return -ENOMEM; + + for (size_t i = 0; i < die_count; i++) + die_order[i] = i; + + r = equidist_permute(die_order, die_count); + if (r < 0) + return r; + + /* Round-robin across dies, picking one CPU from each die at a time */ + size_t dies_exhausted = 0; + while (selected_count < n_irqs) { + bool made_progress = false; + + for (size_t i = 0; i < die_count && selected_count < n_irqs; i++) { + DieInfo *die = &dies[die_order[i]]; + + if (die->next_idx >= die->cpu_count) + continue; + + selected[selected_count++] = die->cpus[die->next_idx++]; + made_progress = true; + + if (die->next_idx >= die->cpu_count) + dies_exhausted++; + } + + if (made_progress) + continue; + + /* All first HTs exhausted, wrap around for remaining IRQs */ + if (dies_exhausted < die_count) + break; + + /* Reset all dies for round-robin wrap */ + FOREACH_ARRAY(die, dies, die_count) + die->next_idx = 0; + dies_exhausted = 0; + } + + *ret = TAKE_PTR(selected); + *ret_count = selected_count; + + return 0; +} + static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { _cleanup_free_ char *affinity_path = NULL, *mask_str = NULL; unsigned n_groups = cpu / 32; @@ -954,6 +1408,64 @@ static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { return 0; } +static int link_apply_irq_affinity_spread(Link *link) { + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_free_ CPUTopology *topology = NULL; + _cleanup_free_ unsigned *irqs = NULL; + _cleanup_free_ unsigned *spread_cpus = NULL; + size_t topology_count = 0, irq_count = 0, spread_count = 0; + int r; + + assert(link); + + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); + if (r < 0) { + if (r != -ENOENT) + return log_link_error_errno(link, r, "Failed to open device/msi_irqs: %m"); + log_link_debug_errno(link, r, "No MSI IRQs found, skipping IRQ affinity configuration: %m"); + return 0; + } + + FOREACH_DIRENT(de, dir, return log_link_error_errno(link, errno, "Failed to read directory device/msi_irqs: %m")) { + unsigned irq; + + r = safe_atou(de->d_name, &irq); + if (r < 0) + return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); + + if (!GREEDY_REALLOC(irqs, irq_count + 1)) + return log_oom(); + + irqs[irq_count++] = irq; + } + + if (irq_count == 0) { + log_link_debug(link, "No IRQs found, skipping spread."); + return 0; + } + + typesafe_qsort(irqs, irq_count, cmp_unsigned); + + r = discover_cpu_topology(&topology, &topology_count); + if (r < 0) + return log_link_error_errno(link, r, "Failed to discover CPU topology: %m"); + + log_link_debug(link, "Discovered %zu CPUs, spreading %zu IRQs.", topology_count, irq_count); + + /* Select CPUs using maximum distance algorithm */ + r = select_spread_cpus(topology, topology_count, irq_count, &spread_cpus, &spread_count); + if (r < 0) + return log_link_error_errno(link, r, "Failed to select spread CPUs: %m"); + + for (size_t i = 0; i < spread_count; i++) + (void) set_irq_affinity(link, irqs[i], spread_cpus[i]); + + log_link_info(link, "Applied IRQ affinity policy 'spread' across %zu CPUs for %zu IRQs.", + MIN(topology_count, irq_count), irq_count); + + return 0; +} + static int link_apply_irq_affinity_single(Link *link) { _cleanup_closedir_ DIR *dir = NULL; int r; @@ -1009,6 +1521,8 @@ static int link_apply_irq_affinity(Link *link) { switch (link->config->irq_affinity_policy) { case IRQ_AFFINITY_POLICY_SINGLE: return link_apply_irq_affinity_single(link); + case IRQ_AFFINITY_POLICY_SPREAD: + return link_apply_irq_affinity_spread(link); default: assert_not_reached(); } @@ -1499,6 +2013,7 @@ DEFINE_CONFIG_PARSE_ENUMV(config_parse_alternative_names_policy, alternative_nam static const char* const irq_affinity_policy_table[_IRQ_AFFINITY_POLICY_MAX] = { [IRQ_AFFINITY_POLICY_SINGLE] = "single", + [IRQ_AFFINITY_POLICY_SPREAD] = "spread", }; DEFINE_STRING_TABLE_LOOKUP(irq_affinity_policy, IRQAffinityPolicy); diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index b6853cd922c99..ff581d8b021eb 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -24,6 +24,7 @@ typedef enum MACAddressPolicy { typedef enum IRQAffinityPolicy { IRQ_AFFINITY_POLICY_SINGLE, + IRQ_AFFINITY_POLICY_SPREAD, _IRQ_AFFINITY_POLICY_MAX, _IRQ_AFFINITY_POLICY_INVALID = -EINVAL, } IRQAffinityPolicy; diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh index 5fa8bd331f9d5..f9dde104fa6a7 100755 --- a/test/units/TEST-17-UDEV.irq-affinity.sh +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -85,6 +85,47 @@ EOF fi done + # Test 1b: test spread policy on the same interface + cat >/run/systemd/network/00-test-irq-affinity.link <1 CPU and >1 IRQ)" + fi + # Cleanup rm -f /run/systemd/network/00-test-irq-affinity.link udevadm control --reload From a9e821549bc757ed34b3f6dd7aee0997be126e1e Mon Sep 17 00:00:00 2001 From: Quentin Deslandes Date: Mon, 16 Feb 2026 20:40:38 +0100 Subject: [PATCH 2008/2155] udev/net: add IRQAffinity= option to filter eligible CPUs Add IRQAffinity= option to .link files that filters the set of CPUs eligible for IRQ placement. This works in conjunction with IRQAffinityPolicy= to constrain which CPUs receive network IRQs. When specified with spread policy, only the listed CPUs are considered for IRQ distribution. When specified with single policy, IRQs are pinned to the first CPU in the allowed set instead of CPU 0. --- src/udev/net/link-config-gperf.gperf | 1 + src/udev/net/link-config.c | 56 ++++++++++++++-- src/udev/net/link-config.h | 1 + test/units/TEST-17-UDEV.irq-affinity.sh | 89 +++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 7 deletions(-) diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 0b63a1e446279..90eebe120d155 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -134,6 +134,7 @@ Link.CoalescePacketRateSampleIntervalSec, config_parse_coalesce_sec, Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, 0, offsetof(LinkConfig, rps_cpu_mask) /* IRQ affinity settings */ Link.IRQAffinityPolicy, config_parse_irq_affinity_policy, 0, offsetof(LinkConfig, irq_affinity_policy) +Link.IRQAffinity, config_parse_cpu_set, 0, offsetof(LinkConfig, irq_affinity_cpus) /* SR-IOV settings */ Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 6211081d79a64..5fd59f9453a66 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -83,6 +83,7 @@ static LinkConfig* link_config_free(LinkConfig *config) { free(config->wol_password_file); erase_and_free(config->wol_password); cpu_set_done(&config->rps_cpu_mask); + cpu_set_done(&config->irq_affinity_cpus); ordered_hashmap_free(config->sr_iov_by_section); @@ -1408,7 +1409,7 @@ static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { return 0; } -static int link_apply_irq_affinity_spread(Link *link) { +static int link_apply_irq_affinity_spread(Link *link, const CPUSet *allowed_cpus) { _cleanup_closedir_ DIR *dir = NULL; _cleanup_free_ CPUTopology *topology = NULL; _cleanup_free_ unsigned *irqs = NULL; @@ -1450,7 +1451,30 @@ static int link_apply_irq_affinity_spread(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Failed to discover CPU topology: %m"); - log_link_debug(link, "Discovered %zu CPUs, spreading %zu IRQs.", topology_count, irq_count); + /* Filter topology by allowed CPUs if specified */ + if (allowed_cpus && allowed_cpus->set) { + _cleanup_free_ CPUTopology *filtered_topology = new(CPUTopology, topology_count); + size_t filtered_count = 0; + + if (!filtered_topology) + return log_oom(); + + for (size_t i = 0; i < topology_count; i++) + if (CPU_ISSET_S(topology[i].cpu, allowed_cpus->allocated, allowed_cpus->set)) + filtered_topology[filtered_count++] = topology[i]; + + if (filtered_count == 0) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping spread."); + return 0; + } + + log_link_debug(link, "Filtered to %zu CPUs (from %zu) based on IRQAffinity=.", filtered_count, topology_count); + + free_and_replace(topology, filtered_topology); + topology_count = filtered_count; + } + + log_link_debug(link, "Spreading %zu IRQs across %zu CPUs.", irq_count, topology_count); /* Select CPUs using maximum distance algorithm */ r = select_spread_cpus(topology, topology_count, irq_count, &spread_cpus, &spread_count); @@ -1466,14 +1490,32 @@ static int link_apply_irq_affinity_spread(Link *link) { return 0; } -static int link_apply_irq_affinity_single(Link *link) { +static int link_apply_irq_affinity_single(Link *link, const CPUSet *allowed_cpus) { _cleanup_closedir_ DIR *dir = NULL; + unsigned target_cpu = 0; int r; assert(link); assert(link->config); assert(ASSERT_PTR(link->event)->dev); + /* If IRQAffinity= is specified, use the first allowed CPU instead of CPU 0 */ + if (allowed_cpus && allowed_cpus->set) { + bool found = false; + + for (unsigned cpu = 0; cpu < allowed_cpus->allocated * 8; cpu++) + if (CPU_ISSET_S(cpu, allowed_cpus->allocated, allowed_cpus->set)) { + target_cpu = cpu; + found = true; + break; + } + + if (!found) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping single."); + return 0; + } + } + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); if (r < 0) { if (r != -ENOENT) @@ -1489,10 +1531,10 @@ static int link_apply_irq_affinity_single(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); - (void) set_irq_affinity(link, irq, /* cpu= */ 0); + (void) set_irq_affinity(link, irq, target_cpu); } - log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU 0)."); + log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU %u).", target_cpu); return 0; } @@ -1520,9 +1562,9 @@ static int link_apply_irq_affinity(Link *link) { switch (link->config->irq_affinity_policy) { case IRQ_AFFINITY_POLICY_SINGLE: - return link_apply_irq_affinity_single(link); + return link_apply_irq_affinity_single(link, &link->config->irq_affinity_cpus); case IRQ_AFFINITY_POLICY_SPREAD: - return link_apply_irq_affinity_spread(link); + return link_apply_irq_affinity_spread(link, &link->config->irq_affinity_cpus); default: assert_not_reached(); } diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index ff581d8b021eb..da28f569807d8 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -122,6 +122,7 @@ struct LinkConfig { /* IRQ affinity */ IRQAffinityPolicy irq_affinity_policy; + CPUSet irq_affinity_cpus; /* SR-IOV */ uint32_t sr_iov_num_vfs; diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh index f9dde104fa6a7..7c3662243e294 100755 --- a/test/units/TEST-17-UDEV.irq-affinity.sh +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -126,6 +126,69 @@ EOF echo "Skipping spread verification (need >1 CPU and >1 IRQ)" fi + # Test 1c: Test IRQAffinity= CPU filtering with single policy + # Pin to CPU 1 instead of default CPU 0 + cat >/run/systemd/network/00-test-irq-affinity.link <1 CPU)" + fi + + # Test 1d: Test IRQAffinity= with spread policy (restrict to subset of CPUs) + if [[ "$n_cpus" -ge 4 ]] && [[ "$irq_count" -gt 1 ]]; then + cat >/run/systemd/network/00-test-irq-affinity.link <=4 CPUs and >1 IRQ)" + fi + # Cleanup rm -f /run/systemd/network/00-test-irq-affinity.link udevadm control --reload @@ -200,13 +263,39 @@ udevadm wait --settle --timeout=30 /sys/class/net/testirq2 output=$(udevadm info --query property /sys/class/net/testirq2) assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-empty.link" "$output" +# Test 5: IRQAffinity= config parsing +cat >/run/systemd/network/10-test-irq-affinity-cpus.link <&1) +assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" "$output" + # Cleanup ip link del dev testirq0 ip link del dev testirq1 ip link del dev testirq2 +ip link del dev testirq3 rm -f /run/systemd/network/10-test-irq.link rm -f /run/systemd/network/10-test-irq-invalid.link rm -f /run/systemd/network/10-test-irq-empty.link +rm -f /run/systemd/network/10-test-irq-affinity-cpus.link exit 0 From 7dfbbfb90dd162ce14d94a4a18a5856235c3fac2 Mon Sep 17 00:00:00 2001 From: Quentin Deslandes Date: Mon, 16 Feb 2026 20:43:37 +0100 Subject: [PATCH 2009/2155] udev/net: add IRQAffinityNUMA= option for NUMA-aware filtering Add support for filtering IRQ affinity to CPUs on a specific NUMA node via the new IRQAffinityNUMA= option in .link files. The option accepts: - "local": use the NUMA node local to the NIC's PCIe slot - Explicit node number (0, 1, 2, ...): use CPUs on the specified node When both IRQAffinity= and IRQAffinityNUMA= are specified, their intersection is used. If the intersection is empty, an error is logged and IRQ affinity configuration is skipped. When "local" is specified but the device's NUMA node cannot be determined (numa_node shows -1), a warning is logged and IRQ affinity configuration is skipped. --- src/shared/numa-util.c | 27 +++-- src/shared/numa-util.h | 1 + src/udev/net/link-config-gperf.gperf | 1 + src/udev/net/link-config.c | 145 +++++++++++++++++++++++- src/udev/net/link-config.h | 8 ++ test/units/TEST-17-UDEV.irq-affinity.sh | 128 +++++++++++++++++++++ 6 files changed, 298 insertions(+), 12 deletions(-) diff --git a/src/shared/numa-util.c b/src/shared/numa-util.c index 9097ccbc313c7..34ddc0e547f5a 100644 --- a/src/shared/numa-util.c +++ b/src/shared/numa-util.c @@ -89,6 +89,22 @@ int apply_numa_policy(const NUMAPolicy *policy) { return 0; } +int numa_node_get_cpus(size_t node, CPUSet *ret) { + char p[STRLEN("/sys/devices/system/node/node//cpulist") + DECIMAL_STR_MAX(size_t) + 1]; + _cleanup_free_ char *cpulist = NULL; + int r; + + assert(ret); + + xsprintf(p, "/sys/devices/system/node/node%zu/cpulist", node); + + r = read_virtual_file(p, SIZE_MAX, &cpulist, /* ret_size= */ NULL); + if (r < 0) + return r; + + return parse_cpu_set(cpulist, ret); +} + int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) { _cleanup_(cpu_set_done) CPUSet s = {}; int r; @@ -97,20 +113,11 @@ int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) { assert(ret); for (size_t i = 0; i < policy->nodes.allocated * 8; i++) { - _cleanup_free_ char *l = NULL; - char p[STRLEN("/sys/devices/system/node/node//cpulist") + DECIMAL_STR_MAX(size_t) + 1]; - if (!CPU_ISSET_S(i, policy->nodes.allocated, policy->nodes.set)) continue; - xsprintf(p, "/sys/devices/system/node/node%zu/cpulist", i); - - r = read_one_line_file(p, &l); - if (r < 0) - return r; - _cleanup_(cpu_set_done) CPUSet part = {}; - r = parse_cpu_set(l, &part); + r = numa_node_get_cpus(i, &part); if (r < 0) return r; diff --git a/src/shared/numa-util.h b/src/shared/numa-util.h index 6fec7c587baa8..01079351b07ff 100644 --- a/src/shared/numa-util.h +++ b/src/shared/numa-util.h @@ -29,6 +29,7 @@ static inline void numa_policy_reset(NUMAPolicy *p) { } int apply_numa_policy(const NUMAPolicy *policy); +int numa_node_get_cpus(size_t node, CPUSet *ret); int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret); int numa_get_node_from_cpu(unsigned cpu, unsigned *ret); diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 90eebe120d155..a005cadd49a61 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -135,6 +135,7 @@ Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, /* IRQ affinity settings */ Link.IRQAffinityPolicy, config_parse_irq_affinity_policy, 0, offsetof(LinkConfig, irq_affinity_policy) Link.IRQAffinity, config_parse_cpu_set, 0, offsetof(LinkConfig, irq_affinity_cpus) +Link.IRQAffinityNUMA, config_parse_irq_affinity_numa, 0, offsetof(LinkConfig, irq_affinity_numa) /* SR-IOV settings */ Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 5fd59f9453a66..306686845e916 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -277,6 +277,7 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) { .eee_tx_lpi_enabled = -1, .eee_tx_lpi_timer_usec = USEC_INFINITY, .irq_affinity_policy = _IRQ_AFFINITY_POLICY_INVALID, + .irq_affinity_numa = IRQ_AFFINITY_NUMA_UNSET, }; FOREACH_ELEMENT(feature, config->features) @@ -929,6 +930,28 @@ static int link_apply_sr_iov_config(Link *link) { return 0; } +/* Get the local NUMA node for a network device from sysfs. + * Returns -ENOENT if numa_node file doesn't exist or shows -1 (no NUMA). */ +static int link_get_device_numa_node(Link *link, unsigned *ret) { + int r, node; + + assert(link); + assert(link->event); + assert(link->event->dev); + assert(ret); + + r = device_get_sysattr_int(link->event->dev, "device/numa_node", &node); + if (r < 0) + return r; + + /* -1 means no NUMA node (non-NUMA system or device not associated with a node) */ + if (node < 0) + return -ENOENT; + + *ret = (unsigned) node; + return 0; +} + /* CPU topology information for IRQ affinity spread algorithm. */ typedef struct CPUTopology { unsigned cpu; @@ -1542,6 +1565,7 @@ static int link_apply_irq_affinity_single(Link *link, const CPUSet *allowed_cpus static int link_apply_irq_affinity(Link *link) { _cleanup_(cpu_set_done) CPUSet effective_cpus = {}; const char *syspath; + unsigned numa_node = IRQ_AFFINITY_NUMA_UNSET; int r; assert(link); @@ -1560,11 +1584,82 @@ static int link_apply_irq_affinity(Link *link) { if (r < 0) return log_link_warning_errno(link, r, "Failed to get syspath: %m"); + /* Compute effective CPU set from IRQAffinity= and IRQAffinityNUMA= */ + if (link->config->irq_affinity_numa != IRQ_AFFINITY_NUMA_UNSET) { + _cleanup_(cpu_set_done) CPUSet numa_cpus = {}; + + /* Resolve "local" to the actual NUMA node */ + if (link->config->irq_affinity_numa == IRQ_AFFINITY_NUMA_LOCAL) { + r = link_get_device_numa_node(link, &numa_node); + if (r < 0) { + log_link_warning_errno( + link, r, + "Failed to determine local NUMA node for device, skipping IRQ affinity configuration: %m"); + return 0; + } + log_link_debug(link, "Device is on NUMA node %u.", numa_node); + } else + numa_node = link->config->irq_affinity_numa; + + /* Get CPUs for the NUMA node */ + r = numa_node_get_cpus(numa_node, &numa_cpus); + if (r < 0) { + log_link_warning_errno( + link, r, + "Failed to get CPUs for NUMA node %u, skipping IRQ affinity configuration: %m", + numa_node); + return 0; + } + + /* If IRQAffinity= is also specified, compute intersection */ + if (link->config->irq_affinity_cpus.set) { + /* Compute intersection of IRQAffinity= and NUMA CPUs */ + size_t max_allocated = MAX(numa_cpus.allocated, link->config->irq_affinity_cpus.allocated); + + r = cpu_set_realloc(&effective_cpus, max_allocated * 8); + if (r < 0) + return log_oom(); + + for (size_t i = 0; i < max_allocated * 8; i++) { + bool in_numa = i < numa_cpus.allocated * 8 && + CPU_ISSET_S(i, numa_cpus.allocated, numa_cpus.set); + bool in_affinity = i < link->config->irq_affinity_cpus.allocated * 8 && + CPU_ISSET_S(i, link->config->irq_affinity_cpus.allocated, link->config->irq_affinity_cpus.set); + + if (in_numa && in_affinity) { + r = cpu_set_add(&effective_cpus, i); + if (r < 0) + return log_oom(); + } + } + + /* Check if intersection is empty */ + if (!effective_cpus.set || CPU_COUNT_S(effective_cpus.allocated, effective_cpus.set) == 0) { + log_link_warning( + link, + "IRQAffinity= and IRQAffinityNUMA= intersection is empty, skipping IRQ affinity configuration."); + return 0; + } + + log_link_debug(link, "Using intersection of IRQAffinity= and NUMA node %u CPUs.", numa_node); + } else { + /* Only NUMA filtering, use NUMA CPUs directly */ + effective_cpus = TAKE_STRUCT(numa_cpus); + log_link_debug(link, "Using CPUs from NUMA node %u.", numa_node); + } + } else if (link->config->irq_affinity_cpus.set) { + /* Only IRQAffinity= specified, copy it */ + r = cpu_set_add_set(&effective_cpus, &link->config->irq_affinity_cpus); + if (r < 0) + return log_oom(); + } + /* else: no filtering, effective_cpus remains empty (meaning use all CPUs) */ + switch (link->config->irq_affinity_policy) { case IRQ_AFFINITY_POLICY_SINGLE: - return link_apply_irq_affinity_single(link, &link->config->irq_affinity_cpus); + return link_apply_irq_affinity_single(link, effective_cpus.set ? &effective_cpus : NULL); case IRQ_AFFINITY_POLICY_SPREAD: - return link_apply_irq_affinity_spread(link, &link->config->irq_affinity_cpus); + return link_apply_irq_affinity_spread(link, effective_cpus.set ? &effective_cpus : NULL); default: assert_not_reached(); } @@ -2053,6 +2148,52 @@ DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy, DEFINE_CONFIG_PARSE_ENUMV(config_parse_alternative_names_policy, alternative_names_policy, NamePolicy, _NAMEPOLICY_INVALID); +int config_parse_irq_affinity_numa( + 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) { + + unsigned tmp, *numa = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *numa = IRQ_AFFINITY_NUMA_UNSET; + return 0; + } + + if (streq(rvalue, "local")) { + *numa = IRQ_AFFINITY_NUMA_LOCAL; + return 0; + } + + /* Parse as NUMA node number */ + r = safe_atou(rvalue, &tmp); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + /* UINT_MAX and UINT_MAX-1 are used to flag "unset" and "local NUMA node" respectively. */ + if (tmp >= IRQ_AFFINITY_NUMA_LOCAL) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid NUMA node number %u, ignoring assignment: %s", tmp, rvalue); + return 0; + } + + *numa = tmp; + + return 0; +} + static const char* const irq_affinity_policy_table[_IRQ_AFFINITY_POLICY_MAX] = { [IRQ_AFFINITY_POLICY_SINGLE] = "single", [IRQ_AFFINITY_POLICY_SPREAD] = "spread", diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index da28f569807d8..a2b0def4c3372 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-device.h" #include "cpu-set-util.h" @@ -29,6 +31,10 @@ typedef enum IRQAffinityPolicy { _IRQ_AFFINITY_POLICY_INVALID = -EINVAL, } IRQAffinityPolicy; +/* Special values for IRQAffinityNUMA= */ +#define IRQ_AFFINITY_NUMA_UNSET UINT_MAX +#define IRQ_AFFINITY_NUMA_LOCAL (IRQ_AFFINITY_NUMA_UNSET - 1) + typedef struct Link { UdevEvent *event; LinkConfig *config; @@ -123,6 +129,7 @@ struct LinkConfig { /* IRQ affinity */ IRQAffinityPolicy irq_affinity_policy; CPUSet irq_affinity_cpus; + unsigned irq_affinity_numa; /* SR-IOV */ uint32_t sr_iov_num_vfs; @@ -163,3 +170,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_name_policy); CONFIG_PARSER_PROTOTYPE(config_parse_alternative_names_policy); CONFIG_PARSER_PROTOTYPE(config_parse_rps_cpu_mask); CONFIG_PARSER_PROTOTYPE(config_parse_irq_affinity_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_irq_affinity_numa); diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh index 7c3662243e294..863fc91a39b69 100755 --- a/test/units/TEST-17-UDEV.irq-affinity.sh +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -189,6 +189,67 @@ EOF echo "Skipping IRQAffinity= spread test (need >=4 CPUs and >1 IRQ)" fi + # Test 1e: Test IRQAffinityNUMA= if NUMA is available + if [[ -d /sys/devices/system/node/node0 ]]; then + # Get CPUs on NUMA node 0 + numa0_cpus=$(cat /sys/devices/system/node/node0/cpulist) + echo "NUMA node 0 has CPUs: $numa0_cpus" + + cat >/run/systemd/network/00-test-irq-affinity.link </run/systemd/network/00-test-irq-affinity.link </dev/null; then + echo "Empty intersection correctly detected and logged" + else + echo "Note: Empty intersection test - check journal for error message" + fi + else + echo "Skipping empty intersection test (need 2 NUMA nodes)" + fi + # Cleanup rm -f /run/systemd/network/00-test-irq-affinity.link udevadm control --reload @@ -287,15 +348,82 @@ assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" output=$(udevadm test-builtin --action add net_setup_link /sys/class/net/testirq3 2>&1) assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" "$output" +# Test 6: IRQAffinityNUMA= config parsing +cat >/run/systemd/network/10-test-irq-affinity-numa.link </run/systemd/network/10-test-irq-affinity-numa-explicit.link </run/systemd/network/10-test-irq-affinity-combined.link < Date: Thu, 8 Jan 2026 01:25:56 +0100 Subject: [PATCH 2010/2155] udev/net: document IRQAffinityPolicy=, IRQAffinity=, and IRQAffinityNUMA= --- man/systemd.link.xml | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/man/systemd.link.xml b/man/systemd.link.xml index d26431b2b2bbe..1d6fbf9fb876d 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -1080,6 +1080,62 @@ + + IRQAffinityPolicy= + + Specifies the IRQ distribution strategy for network interface MSI/MSI-X interrupts. Takes one + of spread or single. When set to spread, + queue IRQs are distributed across CPUs using a topology-aware maximum distance algorithm that + prefers CPUs on different NUMA nodes, then different physical cores, then different hyperthreads. + When set to single, all IRQs are pinned to a single CPU (CPU 0 by default, or + the first CPU in the set specified by IRQAffinity=). When there are more IRQs + than available CPUs, queues wrap around using round-robin assignment. When unset, no affinity + management is performed. Setting this to an empty string explicitly disables affinity management. + + This option only applies to devices with MSI/MSI-X interrupts discoverable via + /sys/class/net/iface/device/msi_irqs/. Virtual + devices (veth, tap) and legacy INTx devices are skipped with a notice logged. + + Note that if irqbalance1 + or similar IRQ management daemons are running, they may override the configured + affinity. Consider disabling such daemons or configuring them to exclude managed interfaces. + + + + + + IRQAffinity= + + Filters the set of CPUs eligible for IRQ placement. Takes a list of CPU indices or ranges + separated by either whitespace or commas (e.g., 0-3, 0,2,4,6, + 0-3,8-11). This option works in conjunction with + IRQAffinityPolicy= and IRQAffinityNUMA= to constrain which + CPUs receive network IRQs. When specified with spread policy, only the listed + CPUs are considered for IRQ distribution. When specified with single policy, + IRQs are pinned to the first CPU in the allowed set instead of CPU 0. Has no effect if + IRQAffinityPolicy= is not set. + + + + + + IRQAffinityNUMA= + + Filters CPUs to those belonging to the specified NUMA node. Takes either + local or an explicit NUMA node number (0, 1, 2, ...). When set to + local, the NUMA node local to the NIC's PCIe slot is used (determined from + /sys/class/net/iface/device/numa_node). If the + device's NUMA node cannot be determined (e.g., non-NUMA system), a warning is logged and IRQ + affinity configuration is skipped. + + When both IRQAffinity= and IRQAffinityNUMA= are + specified, their intersection is used. If the intersection results in an empty set, an error is + logged and no affinity is applied. Has no effect if IRQAffinityPolicy= is not + set. + + + + ReceiveVLANCTAGHardwareAcceleration= From 1f05824d095e8b6246a518a1c46f4d8c16131bd5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 12:15:33 +0200 Subject: [PATCH 2011/2155] update TODO --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index 979bb06e103d1..37c5c58760487 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- add tooling for generating dictionary-based hostnames + - do not pull dbus daemon/broker anymore, instead lazy activate it. Given how the Varlinkifcation has progressed various non-desktop usescase might not need D-Bus running at all anymore. From 4f8215add24ac9018fb68399f0b957cf0eb3b0c6 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Mon, 18 May 2026 07:50:34 +0200 Subject: [PATCH 2012/2155] vmspawn: initial support for SEV-SNP guests Add --confidential-computing=sev-snp to run the guest as an AMD SEV-SNP confidential VM. Loads a raw OVMF firmware blob via -bios (SNP doesn't support the pflash + NVRAM split), attaches a sev-snp-guest object, and hashes the kernel, initrd and cmdline into the launch measurement when direct kernel boot is used. Incompatible features (Secure Boot, CXL, virtio-balloon, SMBIOS credentials) are rejected or disabled; an attached vTPM must be treated as untrusted by the guest. The feature is marked experimental in the man page. Co-developed-by: Claude Opus 4.7 Signed-off-by: Paul Meyer --- man/systemd-vmspawn.xml | 24 ++++++++ src/vmspawn/vmspawn-settings.c | 7 +++ src/vmspawn/vmspawn-settings.h | 8 +++ src/vmspawn/vmspawn.c | 106 +++++++++++++++++++++++++++++---- 4 files changed, 135 insertions(+), 10 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index d75993846f754..20be0c099b16d 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -385,6 +385,30 @@ + + + + Caveat: This feature is experimental, and is likely to be changed (or removed in + its current form) in a future version of systemd. + + Configures whether to run the guest as a confidential VM. Takes one of + no or sev-snp. Defaults to no. + + sev-snp enables AMD SEV-SNP. This requires KVM on an x86_64 host with + SNP-capable hardware and firmware. must point to a raw SNP-built + OVMF .fd image; the standard pflash + NVRAM split is not supported under + SNP, so the firmware is loaded via QEMU's and Secure Boot is + unavailable. SMBIOS credentials passed via or + are rejected because they are outside the SNP launch + measurement. Direct kernel boot via is required so that the + kernel, initrd and command line are hashed into the launch measurement + (kernel-hashes=on); booting the kernel off the disk image via the + firmware would leave it outside the measurement. A vTPM, if attached via + , must be treated as untrusted by the guest. + + + + diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 502189e7bea63..d05c4e1a9e1ad 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -36,3 +36,10 @@ static const char *const firmware_table[_FIRMWARE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware); + +static const char *const confidential_computing_table[_COCO_MAX] = { + [COCO_NO] = "no", + [COCO_AMD_SEV_SNP] = "sev-snp", +}; + +DEFINE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 596a66cecddb8..9be3afdef4c42 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -42,6 +42,13 @@ typedef enum Firmware { _FIRMWARE_INVALID = -EINVAL, } Firmware; +typedef enum ConfidentialComputing { + COCO_NO, + COCO_AMD_SEV_SNP, + _COCO_MAX, + _COCO_INVALID = -EINVAL, +} ConfidentialComputing; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, @@ -55,3 +62,4 @@ typedef enum SettingsMask { DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); DECLARE_STRING_TABLE_LOOKUP(firmware, Firmware); +DECLARE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 7d520bbad5ca2..e52e9d9852b82 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -161,6 +161,7 @@ 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; +static ConfidentialComputing arg_confidential_computing = COCO_NO; 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; @@ -644,6 +645,14 @@ static int parse_argv(int argc, char *argv[]) { break; } + OPTION_LONG("coco", "no|sev-snp", "Run the guest as a confidential VM"): { + ConfidentialComputing cc = confidential_computing_from_string(opts.arg); + if (cc < 0) + return log_error_errno(cc, "Unknown --coco= value: %s", opts.arg); + arg_confidential_computing = cc; + break; + } + OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk); if (r < 0) @@ -2596,7 +2605,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { use_kvm = r; } - if (arg_firmware_type == FIRMWARE_UEFI) { + if (arg_confidential_computing == COCO_AMD_SEV_SNP && !use_kvm) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--coco=sev-snp requires KVM, but KVM is not available."); + + if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing != COCO_AMD_SEV_SNP) { if (arg_firmware) r = load_ovmf_config(arg_firmware, &ovmf_config); else @@ -2670,13 +2683,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + r = qemu_config_key(config_file, "kernel-irqchip", "split"); + if (r < 0) + return r; + } + if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) { r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb)); if (r < 0) return r; } - if (ARCHITECTURE_SUPPORTS_CXL) { + if (ARCHITECTURE_SUPPORTS_CXL && arg_confidential_computing == COCO_NO) { r = qemu_config_key(config_file, "cxl", "on"); if (r < 0) return r; @@ -2694,6 +2713,12 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + r = qemu_config_key(config_file, "confidential-guest-support", "snp0"); + if (r < 0) + return r; + } + r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL, "cpus", arg_cpus ?: "1"); if (r < 0) @@ -2726,11 +2751,13 @@ 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", "balloon0", - "driver", "virtio-balloon", - "free-page-reporting", "on"); - if (r < 0) - return r; + if (arg_confidential_computing == COCO_NO) { + 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; @@ -2884,6 +2911,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + /* SNP marks encrypted guest pages via the "C-bit" in the page table entry. On all + * SNP-capable processors (Milan and later) the C-bit lives at bit 51, which reduces + * the usable guest physical address space by one bit. + * Embed the hashes of kernel, initrd and cmdline into the firmware + * so they are covered by the launch measurement and the guest's + * boot chain starts from a measured state. */ + r = qemu_config_section(config_file, "object", "snp0", + "qom-type", "sev-snp-guest", + "cbitpos", "51", + "reduced-phys-bits", "1", + "kernel-hashes", "on"); + if (r < 0) + return r; + } + unsigned child_cid = arg_vsock_cid; if (use_vsock) { config.vsock.fd = TAKE_FD(vhost_device_fd); @@ -3039,9 +3082,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } _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_confidential_computing != COCO_NO) { + r = strv_extend_many(&cmdline, "-bios", arg_firmware); + if (r < 0) + return r; + } else { + r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars); + if (r < 0) + return r; + } if (arg_linux) { r = strv_extend_many(&cmdline, "-kernel", arg_linux); @@ -4016,6 +4065,43 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'."); + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + if (native_architecture() != ARCHITECTURE_X86_64) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--coco=sev-snp is only supported on x86_64."); + if (arg_kvm == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires KVM, remove --kvm=no."); + if (arg_firmware_type != FIRMWARE_UEFI) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco can't be used with %s firmware", + firmware_to_string(arg_firmware_type)); + /* SNP can't use pflash + NVRAM split, so the firmware-descriptor + * machinery doesn't apply. Require an explicit raw .fd path and + * use it verbatim with -bios later. */ + if (!arg_firmware) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires --firmware=PATH " + "pointing at a raw SNP-built OVMF .fd binary."); + log_debug("Using raw SNP firmware at %s (no NVRAM, no Secure Boot).", arg_firmware); + if (set_contains(arg_firmware_features_include, "secure-boot")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--secure-boot=yes cannot be combined with --coco."); + if (arg_credentials.n_credentials != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "SMBIOS credentials aren't trusted by the confidential computing guest and will be rejected."); + if (arg_tpm > 0) + log_warning("TPM can't be trusted by the confidential computing guest"); + /* kernel-hashes=on only covers what QEMU itself loads via -kernel/-initrd/-append. + * Without --linux= the kernel and initrd come off disk via OVMF and aren't part + * of the launch measurement, leaving the guest unattestable in any meaningful + * way. Require direct kernel boot so the boot chain starts from a measured state. */ + if (!arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires --linux= " + "so kernel, initrd and cmdline are covered by the launch measurement."); + } + return 0; } From 1ccda1533d6b7880495d194f41112a9eb1c8ad42 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Tue, 19 May 2026 13:56:46 +0200 Subject: [PATCH 2013/2155] vmspawn: use EPYC-v4 cpu for SNP SNP requires a named, stable CPU model so the launch measurement is reproducible across hosts. EPYC-v4 is the baseline that covers all SNP-capable processors (Milan and later). Signed-off-by: Paul Meyer --- src/vmspawn/vmspawn.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e52e9d9852b82..73f2a19eef1ec 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2944,14 +2944,17 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { config.vsock.cid = child_cid; } - /* -cpu stays on cmdline since not all flags are supported in config */ - r = strv_extend_many(&cmdline, "-cpu", + /* -cpu stays on cmdline since not all flags are supported in config. SNP needs a stable, + * named CPU model so the launch measurement is reproducible across hosts; EPYC-v4 is the + * baseline that covers all SNP-capable processors (Milan and later). */ + const char *cpu_model = #ifdef __x86_64__ - "max,hv_relaxed,hv-vapic,hv-time" + arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4" + : "max,hv_relaxed,hv-vapic,hv-time"; #else - "max" + "max"; #endif - ); + r = strv_extend_many(&cmdline, "-cpu", cpu_model); if (r < 0) return log_oom(); From d3f38e09ac2a421e700123ce3615d07e16257925 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 12:56:39 +0100 Subject: [PATCH 2014/2155] po: skip automated fuzzy translations when generating new po files The fuzzy translations are always wrong, but meson's integration does not allow skipping them. Add a tiny wrapper for 'msgmerge' to workaround the issue and skip them when running ninja systemd-update-po --- po/meson.build | 6 ++++++ tools/msgmerge-no-fuzzy.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100755 tools/msgmerge-no-fuzzy.py diff --git a/po/meson.build b/po/meson.build index 0a140b40b50cb..682c16ab1182e 100644 --- a/po/meson.build +++ b/po/meson.build @@ -3,6 +3,12 @@ want_translations = get_option('translations') if want_translations + # The msgmerge invocation hidden behind i18n.gettext()'s update-po target inserts auto-guessed + # "fuzzy" translations for new strings, which are almost always wrong, use a wrapper to skip it. + meson.override_find_program( + 'msgmerge', + find_program('../tools/msgmerge-no-fuzzy.py')) + i18n = import('i18n') i18n.gettext(meson.project_name(), preset : 'glib', diff --git a/tools/msgmerge-no-fuzzy.py b/tools/msgmerge-no-fuzzy.py new file mode 100755 index 0000000000000..a9171db695ffa --- /dev/null +++ b/tools/msgmerge-no-fuzzy.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +""" +Fuzzy translations are always bogus, but the meson integration doesn't allow overriding. With this wrapper +we can skip them. +""" + +import os +import shutil +import sys + +msgmerge = shutil.which('msgmerge') +if msgmerge is None: + sys.exit('msgmerge-no-fuzzy: msgmerge not found in PATH') + +os.execv(msgmerge, [msgmerge, '--no-fuzzy-matching', *sys.argv[1:]]) From ee7bd1d8a0c7dd237adc0fa1c7b12dd63969ab42 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 18 May 2026 18:32:23 +0200 Subject: [PATCH 2015/2155] core: add User,Group,SupplementaryGroups,Nice to varlink Unit.StartTransient This commit adds more writable fields to the io.systemd.Unit.StartTransient varlink method. With this its possible to set: User,Group,SupplementaryGroups,Nice values. Plus tests for them. --- src/core/varlink-unit.c | 73 ++++++++++++++++++++++++++++++--- test/units/TEST-26-SYSTEMCTL.sh | 28 +++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index c90ff987e8405..da0c0d234a51e 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -18,12 +18,14 @@ #include "manager.h" #include "path-util.h" #include "pidref.h" +#include "process-util.h" #include "selinux-access.h" #include "service.h" #include "set.h" #include "strv.h" #include "unit-name.h" #include "unit.h" +#include "user-util.h" #include "varlink-automount.h" #include "varlink-cgroup.h" #include "varlink-common.h" @@ -685,6 +687,11 @@ typedef struct TransientExecContextParameters { bool set_credentials_encrypted_set; TransientSetCredential *set_credentials_encrypted; size_t n_set_credentials_encrypted; + + const char *user; + const char *group; + char **supplementary_groups; + int nice; /* INT_MAX means unset */ } TransientExecContextParameters; static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) { @@ -696,6 +703,7 @@ static void transient_set_credential_array_free(TransientSetCredential *items, s static void transient_exec_context_parameters_done(TransientExecContextParameters *p) { assert(p); strv_free(p->environment); + strv_free(p->supplementary_groups); transient_set_credential_array_free(p->set_credentials, p->n_set_credentials); transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted); } @@ -862,12 +870,16 @@ static int dispatch_transient_set_credential_encrypted(const char *name, sd_json } static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - /* Key names compatible with D-Bus property names */ + /* Key names compatible with D-Bus property names. */ static const sd_json_dispatch_field exec_dispatch[] = { - { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, - { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, - { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 }, - { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 }, + { "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 }, + { "Group", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, group), SD_JSON_RELAX }, + { "Nice", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int32, offsetof(TransientExecContextParameters, nice), 0 }, + { "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 }, + { "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 }, + { "SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0 }, + { "User", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, user), SD_JSON_RELAX }, + { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 }, {} }; @@ -1026,6 +1038,56 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran return r; } + /* User/Group names already validated by json_dispatch_const_user_group_name() in the dispatch table: + * either NULL or a non-empty validated name. */ + if (p->user) { + r = free_and_strdup(&c->user, p->user); + if (r < 0) + return r; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "User", + "User=%s", p->user); + } + + if (p->group) { + r = free_and_strdup(&c->group, p->group); + if (r < 0) + return r; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "Group", + "Group=%s", p->group); + } + + if (p->supplementary_groups) { + _cleanup_free_ char *joined = NULL; + + STRV_FOREACH(g, p->supplementary_groups) + if (!isempty(*g) && !valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid supplementary group name: %s", *g); + + r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true); + if (r < 0) + return r; + + joined = strv_join(p->supplementary_groups, " "); + if (!joined) + return -ENOMEM; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "SupplementaryGroups", + "SupplementaryGroups=%s", joined); + } + + if (p->nice != INT_MAX) { + if (!nice_is_valid(p->nice)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice); + + c->nice = p->nice; + c->nice_set = true; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Nice", "Nice=%i", p->nice); + } + return 0; } @@ -1125,6 +1187,7 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters .notify_unit_changes = -1, .context.service.type = _SERVICE_TYPE_INVALID, .context.service.remain_after_exit = -1, + .context.exec.nice = INT_MAX, }; Manager *manager = ASSERT_PTR(userdata); const char *bad_field = NULL; diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index d489cc3ec8083..93afa655a2094 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -656,6 +656,26 @@ timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do grep '^secret-value$' "$CRED_OUTPUT" >/dev/null rm -f "$CRED_OUTPUT" +# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice +# The nobody group is different on different distros so resolve here. +NOBODY_GROUP=$(id -gn nobody) +defer_transient_cleanup varlink-transient-ids.service +ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \ + '{context:{ID:"varlink-transient-ids.service", + Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5}, + Service:{Type:"oneshot",RemainAfterExit:true, + ExecStart:[{path:"/bin/true"}]}}}') +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload") +echo "$result" | jq -e '.context.Exec.User == "nobody"' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]' +echo "$result" | jq -e '.context.Exec.Nice == 5' +timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done' +systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null +systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null +systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null +systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null + # Error cases: verify specific varlink error types set +o pipefail varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ @@ -677,6 +697,14 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ defer_transient_cleanup varlink-transient-bad-env.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Invalid User= name is rejected at JSON dispatch time as a parameter error +defer_transient_cleanup varlink-transient-bad-user.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument" +# Out-of-range Nice= value is rejected +defer_transient_cleanup varlink-transient-bad-nice.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" # Invalid credential ID defer_transient_cleanup varlink-transient-bad-cred-id.service varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ From 8b518c67fe1145514be7cb8afc021ce4b7f8af69 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:43:24 +0200 Subject: [PATCH 2016/2155] update TODO --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index 37c5c58760487..7672fdb9f731f 100644 --- a/TODO.md +++ b/TODO.md @@ -128,6 +128,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Features +- systemd-report: implement signing via callout varlink dir + - add tooling for generating dictionary-based hostnames - do not pull dbus daemon/broker anymore, instead lazy activate it. Given how From ddedddffde27e5834e4a90edcbc6e70aedf218ca Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 20 May 2026 08:52:20 +0200 Subject: [PATCH 2017/2155] core: tweak error handling around VARLINK_ERROR_UNIT_BAD_SETTING This commit move the erros in StartTransient from VARLINK_ERROR_UNIT_BAD_SETTING to SD_VARLINK_ERROR_INVALID_PARAMETER and it also ensures we have the bad field in the error. Thanks to Ivan Kruglov for suggesting this. --- src/core/varlink-unit.c | 73 ++++++++++++++++++++++++--------- test/units/TEST-26-SYSTEMCTL.sh | 50 ++++++++++++++-------- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index da0c0d234a51e..142de0db8f420 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -976,7 +976,12 @@ static int transient_apply_set_credentials( return 0; } -static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p) { +static int transient_exec_context_apply_properties( + Unit *u, + ExecContext *c, + TransientExecContextParameters *p, + const char **reterr_field) { + int r; assert(u); @@ -987,23 +992,35 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran TransientWorkingDirectory *wd = &p->working_directory; _cleanup_free_ char *simplified = NULL; - if (wd->home && wd->path) + if (wd->home && wd->path) { + if (reterr_field) + *reterr_field = "Exec.WorkingDirectory"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "WorkingDirectory: 'home' and 'path' are mutually exclusive"); - if (!wd->home && !wd->path) + } + if (!wd->home && !wd->path) { + if (reterr_field) + *reterr_field = "Exec.WorkingDirectory"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "WorkingDirectory: must specify either 'home' or 'path'"); + } if (!wd->home) { - if (!path_is_absolute(wd->path)) + if (!path_is_absolute(wd->path)) { + if (reterr_field) + *reterr_field = "Exec.WorkingDirectory"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "WorkingDirectory: expects an absolute path"); + } r = path_simplify_alloc(wd->path, &simplified); if (r < 0) return r; - if (!path_is_normalized(simplified)) + if (!path_is_normalized(simplified)) { + if (reterr_field) + *reterr_field = "Exec.WorkingDirectory"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "WorkingDirectory: expects a normalized path"); + } } free_and_replace(c->working_directory, simplified); @@ -1018,22 +1035,29 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran if (p->environment_set) { r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE); - if (r == -E2BIG) - return log_debug_errno(r, "Too many environment assignments."); - if (r == -EINVAL) - return log_debug_errno(r, "Invalid Environment list."); + if (IN_SET(r, -E2BIG, -EINVAL)) { + if (reterr_field) + *reterr_field = "Exec.Environment"; + /* Convert E2BIG into EINVAL to ensure upper layers get the right error context */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list."); + } if (r < 0) return r; } if (p->set_credentials_set) { r = transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); + if (r == -EINVAL && reterr_field) + *reterr_field = "Exec.SetCredential"; if (r < 0) return r; } if (p->set_credentials_encrypted_set) { r = transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); + if (r == -EINVAL && reterr_field) + *reterr_field = "Exec.SetCredentialEncrypted"; if (r < 0) return r; } @@ -1062,9 +1086,12 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran _cleanup_free_ char *joined = NULL; STRV_FOREACH(g, p->supplementary_groups) - if (!isempty(*g) && !valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) + if (!valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) { + if (reterr_field) + *reterr_field = "Exec.SupplementaryGroups"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid supplementary group name: %s", *g); + } r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true); if (r < 0) @@ -1079,8 +1106,11 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran } if (p->nice != INT_MAX) { - if (!nice_is_valid(p->nice)) + if (!nice_is_valid(p->nice)) { + if (reterr_field) + *reterr_field = "Exec.Nice"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice); + } c->nice = p->nice; c->nice_set = true; @@ -1091,7 +1121,7 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran return 0; } -static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp) { +static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp, const char **reterr_field) { Unit *u = UNIT(ASSERT_PTR(s)); int r; @@ -1111,8 +1141,11 @@ static int transient_service_apply_properties(Service *s, TransientServiceParame _cleanup_(exec_command_freep) ExecCommand *c = NULL; _cleanup_strv_free_ char **argv = NULL; - if (!filename_or_absolute_path_is_valid(item->path)) + if (!filename_or_absolute_path_is_valid(item->path)) { + if (reterr_field) + *reterr_field = "Service.ExecStart"; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path); + } if (!strv_isempty(item->arguments)) { argv = strv_copy(item->arguments); @@ -1243,16 +1276,17 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters /* Apply unit-level properties from context */ r = transient_unit_apply_properties(u, &p.context); if (r == -EINVAL) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + return sd_varlink_error_invalid_parameter_name(link, "context"); if (r < 0) return sd_varlink_error_errno(link, r); /* Apply exec-specific properties from context.Exec */ ExecContext *c = unit_get_exec_context(u); if (c) { - r = transient_exec_context_apply_properties(u, c, &p.context.exec); + bad_field = NULL; + r = transient_exec_context_apply_properties(u, c, &p.context.exec, &bad_field); if (r == -EINVAL) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Exec"); if (r < 0) return sd_varlink_error_errno(link, r); } else if (p.context.exec.present) @@ -1261,9 +1295,10 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters /* Apply service-specific properties from context.Service */ Service *s = SERVICE(u); if (s) { - r = transient_service_apply_properties(s, &p.context.service); + bad_field = NULL; + r = transient_service_apply_properties(s, &p.context.service, &bad_field); if (r == -EINVAL) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Service"); if (r < 0) return sd_varlink_error_errno(link, r); } else if (p.context.service.present) @@ -1273,7 +1308,7 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters manager_dispatch_load_queue(manager); if (u->load_state == UNIT_BAD_SETTING) - return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + return sd_varlink_error_invalid_parameter_name(link, "context"); if (!UNIT_IS_LOAD_COMPLETE(u->load_state)) return sd_varlink_error(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT, NULL); diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 93afa655a2094..d50ae68ef83b3 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -682,37 +682,55 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Apply-time and dispatch-time validation errors both surface as +# org.varlink.service.InvalidParameter, with the offending field name in the +# response parameters. Use --graceful to treat the expected error as success +# so jq can assert on the dumped parameters JSON directly. +expect_invalid_parameter() { + local payload="$1" field="$2" + varlinkctl call --graceful=org.varlink.service.InvalidParameter \ + "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \ + | jq -e --arg f "$field" '.parameter == $f' >/dev/null +} defer_transient_cleanup varlink-transient-bad.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \ + "context" # Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() defer_transient_cleanup varlink-transient-badpath.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \ + "Service.ExecStart" # Relative WorkingDirectory path is rejected defer_transient_cleanup varlink-transient-bad-wd.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.WorkingDirectory" # Malformed environment entry (not KEY=VALUE) defer_transient_cleanup varlink-transient-bad-env.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Environment" # Invalid User= name is rejected at JSON dispatch time as a parameter error defer_transient_cleanup varlink-transient-bad-user.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" # Out-of-range Nice= value is rejected defer_transient_cleanup varlink-transient-bad-nice.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Nice" # Invalid credential ID defer_transient_cleanup varlink-transient-bad-cred-id.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.SetCredential" # Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) defer_transient_cleanup varlink-transient-bad-cred-value.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "Invalid argument" +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" # Exec on a unit type without an exec context (.slice) is rejected varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" From 619ea8f57344068250d46d598184d4de61dbd099 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 20 May 2026 11:47:00 +0200 Subject: [PATCH 2018/2155] core: move StartTransient varlink tests to the right place This commit moves the io.systemd.Unit.StartTransient tests into the right place in TEST-74-AUX-UTILS.varlinkctl-unit.sh. Thanks to Ivan Kruglov for suggesting this. --- test/units/TEST-26-SYSTEMCTL.sh | 233 ------------------ .../TEST-74-AUX-UTILS.varlinkctl-unit.sh | 233 ++++++++++++++++++ 2 files changed, 233 insertions(+), 233 deletions(-) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index d50ae68ef83b3..75feedd3b097e 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -519,239 +519,6 @@ systemctl show -P Markers "$UNIT_NAME" | grep needs-stop (! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload) varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}" -# Test io.systemd.Unit.StartTransient -MANAGER_SOCKET="/run/systemd/io.systemd.Manager" - -TRANSIENT_UNITS=() -defer_transient_cleanup() { - TRANSIENT_UNITS+=("$1") -} -transient_cleanup() { - for u in "${TRANSIENT_UNITS[@]}"; do - systemctl stop "$u" 2>/dev/null || true - systemctl reset-failed "$u" 2>/dev/null || true - done -} -trap transient_cleanup EXIT - -# Basic oneshot transient service -defer_transient_cleanup varlink-transient-test.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') -echo "$result" | grep '"context"' >/dev/null -echo "$result" | grep '"runtime"' >/dev/null - -# Wait for completion -timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done' -systemctl show -P Result varlink-transient-test.service | grep success >/dev/null - -# With explicit mode -defer_transient_cleanup varlink-transient-test2.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}') -echo "$result" | grep '"context"' >/dev/null - -# Streaming with notifyJobChanges: should get intermediate state updates and a final result -# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on -# CentOS 9) -e checks only the last input record's output, so a select() that filters -# out the trailing record makes jq exit non-zero even when earlier records match. -defer_transient_cleanup varlink-transient-test3.service -result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null - -# Fire-and-forget: --more without notify flags should return immediately with context+runtime -defer_transient_cleanup varlink-transient-fireforget.service -result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null - -# Streaming with notifyUnitChanges: should get unit state change notifications -defer_transient_cleanup varlink-transient-unitnotify.service -result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null - -# Streaming with both notifyJobChanges and notifyUnitChanges -defer_transient_cleanup varlink-transient-both.service -result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null - -# prepare for the error case below: create a long-running service, then try to create it again while it's active -defer_transient_cleanup varlink-transient-exists.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' -timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done' - -# Multiple ExecStart commands (oneshot allows multiple) -defer_transient_cleanup varlink-transient-multi.service -result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') -printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null - -# Transient service with Description and RemainAfterExit -defer_transient_cleanup varlink-transient-desc.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') -echo "$result" | jq -e '.context.Description == "Test description property"' -echo "$result" | jq -e '.context.Service.Type == "oneshot"' -echo "$result" | jq -e '.context.Service.RemainAfterExit == true' -echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"' -echo "$result" | jq -e '.runtime' - -# Transient service with explicit arguments -defer_transient_cleanup varlink-transient-args.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}') -echo "$result" | jq -e '.context' -echo "$result" | jq -e '.runtime' -echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"' -echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]' -timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done' - -# Verify that omitting arguments defaults argv[0] to the path -defer_transient_cleanup varlink-transient-noargs.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') -echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' -timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' - -# Exec.WorkingDirectory and Exec.Environment -defer_transient_cleanup varlink-transient-exec.service -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') -echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"' -echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null' -echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null' -timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done' -systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null -systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null -systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null - -# WorkingDirectory with missingOK=true (path does not exist but unit still starts) -defer_transient_cleanup varlink-transient-wd-missing.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' -timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done' - -# WorkingDirectory with home=true, missingOK omitted (defaults to false) -defer_transient_cleanup varlink-transient-wd-home.service -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' -timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' -systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null - -# Exec.SetCredential: pass a credential and verify the running process can read it -defer_transient_cleanup varlink-transient-cred.service -CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0) -CRED_OUTPUT=$(mktemp) -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - "{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}" -timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done" -grep '^secret-value$' "$CRED_OUTPUT" >/dev/null -rm -f "$CRED_OUTPUT" - -# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice -# The nobody group is different on different distros so resolve here. -NOBODY_GROUP=$(id -gn nobody) -defer_transient_cleanup varlink-transient-ids.service -ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \ - '{context:{ID:"varlink-transient-ids.service", - Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5}, - Service:{Type:"oneshot",RemainAfterExit:true, - ExecStart:[{path:"/bin/true"}]}}}') -result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload") -echo "$result" | jq -e '.context.Exec.User == "nobody"' -echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g' -echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]' -echo "$result" | jq -e '.context.Exec.Nice == 5' -timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done' -systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null -systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null -systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null -systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null - -# Error cases: verify specific varlink error types -set +o pipefail -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" -# Apply-time and dispatch-time validation errors both surface as -# org.varlink.service.InvalidParameter, with the offending field name in the -# response parameters. Use --graceful to treat the expected error as success -# so jq can assert on the dumped parameters JSON directly. -expect_invalid_parameter() { - local payload="$1" field="$2" - varlinkctl call --graceful=org.varlink.service.InvalidParameter \ - "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \ - | jq -e --arg f "$field" '.parameter == $f' >/dev/null -} -defer_transient_cleanup varlink-transient-bad.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \ - "context" -# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() -defer_transient_cleanup varlink-transient-badpath.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \ - "Service.ExecStart" -# Relative WorkingDirectory path is rejected -defer_transient_cleanup varlink-transient-bad-wd.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "Exec.WorkingDirectory" -# Malformed environment entry (not KEY=VALUE) -defer_transient_cleanup varlink-transient-bad-env.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "Exec.Environment" -# Invalid User= name is rejected at JSON dispatch time as a parameter error -defer_transient_cleanup varlink-transient-bad-user.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "context" -# Out-of-range Nice= value is rejected -defer_transient_cleanup varlink-transient-bad-nice.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "Exec.Nice" -# Invalid credential ID -defer_transient_cleanup varlink-transient-bad-cred-id.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "Exec.SetCredential" -# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) -defer_transient_cleanup varlink-transient-bad-cred-value.service -expect_invalid_parameter \ - '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ - "context" -# Exec on a unit type without an exec context (.slice) is rejected -varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" -# Unknown field in Exec is rejected as PropertyNotSupported -defer_transient_cleanup varlink-transient-unknown-exec.service -unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) -echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported" -echo "$unsupported_exec" | grep "Exec.RootDirectory" -# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported, -# and the offending sub-property is identified -defer_transient_cleanup varlink-transient-unknown-service.service -unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ - '{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) -echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported" -echo "$unsupported_service" | grep "Service.Restart" -set -o pipefail - -transient_cleanup -trap - EXIT - # --dry-run with destructive verbs # kexec is skipped intentionally, as it requires a bit more involved setup VERBS=( diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh index 2311e14a14588..c631c6f8896e1 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -81,3 +81,236 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_par testuser_uid=$(id -u testuser) systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' + +# Test io.systemd.Unit.StartTransient +MANAGER_SOCKET="/run/systemd/io.systemd.Manager" + +TRANSIENT_UNITS=() +defer_transient_cleanup() { + TRANSIENT_UNITS+=("$1") +} +transient_cleanup() { + for u in "${TRANSIENT_UNITS[@]}"; do + systemctl stop "$u" 2>/dev/null || true + systemctl reset-failed "$u" 2>/dev/null || true + done +} +trap transient_cleanup EXIT + +# Basic oneshot transient service +defer_transient_cleanup varlink-transient-test.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | grep '"context"' >/dev/null +echo "$result" | grep '"runtime"' >/dev/null + +# Wait for completion +timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done' +systemctl show -P Result varlink-transient-test.service | grep success >/dev/null + +# With explicit mode +defer_transient_cleanup varlink-transient-test2.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}') +echo "$result" | grep '"context"' >/dev/null + +# Streaming with notifyJobChanges: should get intermediate state updates and a final result +# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on +# CentOS 9) -e checks only the last input record's output, so a select() that filters +# out the trailing record makes jq exit non-zero even when earlier records match. +defer_transient_cleanup varlink-transient-test3.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# Fire-and-forget: --more without notify flags should return immediately with context+runtime +defer_transient_cleanup varlink-transient-fireforget.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null + +# Streaming with notifyUnitChanges: should get unit state change notifications +defer_transient_cleanup varlink-transient-unitnotify.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null + +# Streaming with both notifyJobChanges and notifyUnitChanges +defer_transient_cleanup varlink-transient-both.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# prepare for the error case below: create a long-running service, then try to create it again while it's active +defer_transient_cleanup varlink-transient-exists.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' +timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done' + +# Multiple ExecStart commands (oneshot allows multiple) +defer_transient_cleanup varlink-transient-multi.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# Transient service with Description and RemainAfterExit +defer_transient_cleanup varlink-transient-desc.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Description == "Test description property"' +echo "$result" | jq -e '.context.Service.Type == "oneshot"' +echo "$result" | jq -e '.context.Service.RemainAfterExit == true' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"' +echo "$result" | jq -e '.runtime' + +# Transient service with explicit arguments +defer_transient_cleanup varlink-transient-args.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}') +echo "$result" | jq -e '.context' +echo "$result" | jq -e '.runtime' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"' +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done' + +# Verify that omitting arguments defaults argv[0] to the path +defer_transient_cleanup varlink-transient-noargs.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' + +# Exec.WorkingDirectory and Exec.Environment +defer_transient_cleanup varlink-transient-exec.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"' +echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null' +echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null' +timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null + +# WorkingDirectory with missingOK=true (path does not exist but unit still starts) +defer_transient_cleanup varlink-transient-wd-missing.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done' + +# WorkingDirectory with home=true, missingOK omitted (defaults to false) +defer_transient_cleanup varlink-transient-wd-home.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null + +# Exec.SetCredential: pass a credential and verify the running process can read it +defer_transient_cleanup varlink-transient-cred.service +CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0) +CRED_OUTPUT=$(mktemp) +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + "{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}" +timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done" +grep '^secret-value$' "$CRED_OUTPUT" >/dev/null +rm -f "$CRED_OUTPUT" + +# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice +# The nobody group is different on different distros so resolve here. +NOBODY_GROUP=$(id -gn nobody) +defer_transient_cleanup varlink-transient-ids.service +ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \ + '{context:{ID:"varlink-transient-ids.service", + Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5}, + Service:{Type:"oneshot",RemainAfterExit:true, + ExecStart:[{path:"/bin/true"}]}}}') +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload") +echo "$result" | jq -e '.context.Exec.User == "nobody"' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]' +echo "$result" | jq -e '.context.Exec.Nice == 5' +timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done' +systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null +systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null +systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null +systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null + +# Error cases: verify specific varlink error types +set +o pipefail +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Apply-time and dispatch-time validation errors both surface as +# org.varlink.service.InvalidParameter, with the offending field name in the +# response parameters. Use --graceful to treat the expected error as success +# so jq can assert on the dumped parameters JSON directly. +expect_invalid_parameter() { + local payload="$1" field="$2" + varlinkctl call --graceful=org.varlink.service.InvalidParameter \ + "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \ + | jq -e --arg f "$field" '.parameter == $f' >/dev/null +} +defer_transient_cleanup varlink-transient-bad.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \ + "context" +# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() +defer_transient_cleanup varlink-transient-badpath.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \ + "Service.ExecStart" +# Relative WorkingDirectory path is rejected +defer_transient_cleanup varlink-transient-bad-wd.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.WorkingDirectory" +# Malformed environment entry (not KEY=VALUE) +defer_transient_cleanup varlink-transient-bad-env.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Environment" +# Invalid User= name is rejected at JSON dispatch time as a parameter error +defer_transient_cleanup varlink-transient-bad-user.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" +# Out-of-range Nice= value is rejected +defer_transient_cleanup varlink-transient-bad-nice.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Nice" +# Invalid credential ID +defer_transient_cleanup varlink-transient-bad-cred-id.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.SetCredential" +# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) +defer_transient_cleanup varlink-transient-bad-cred-value.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" +# Exec on a unit type without an exec context (.slice) is rejected +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Unknown field in Exec is rejected as PropertyNotSupported +defer_transient_cleanup varlink-transient-unknown-exec.service +unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_exec" | grep "Exec.RootDirectory" +# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported, +# and the offending sub-property is identified +defer_transient_cleanup varlink-transient-unknown-service.service +unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_service" | grep "Service.Restart" +set -o pipefail + +transient_cleanup +trap - EXIT From c61a9a6dba42f005f94d1641022b801bc3c4347e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 23:01:41 +0200 Subject: [PATCH 2019/2155] repart: canonicalize node in varlink Run method Run acquire_root_devno() on the varlink-provided node so symlinks (e.g. /dev/disk/by-id/...) resolve to their canonical /dev/ path before being used. Without this, sym_fdisk_partname() produces a "-partN" symlink that udev hasn't created yet when repart calls open() on it right after BLKPG_ADD_PARTITION, failing with ENOENT. This also brings the varlink path in line with the CLI path's partition-to-whole-disk and dm-crypt-to-backing resolution. --- src/repart/repart.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 133a8e02118dc..8cfb798339538 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -10964,7 +10964,7 @@ static int find_root(Context *context) { } else if (r < 0) return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m"); else { - r = acquire_root_devno(device, NULL, open_flags, &context->node, &context->backing_fd); + r = acquire_root_devno(device, /* root= */ NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, device); if (r < 0) @@ -11389,7 +11389,9 @@ static int vl_method_run( return r; if (p.node) { - context->node = TAKE_PTR(p.node); + r = acquire_root_devno(p.node, NULL, O_CLOEXEC|context_open_mode(context), &context->node, &context->backing_fd); + if (r < 0) + return log_error_errno(r, "Failed to open file or determine backing device of %s: %m", p.node); r = context_load_partition_table(context); if (r == -EHWPOISON) From 68910161491cdd161bff29a32032e52301831164 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 20:46:54 +0000 Subject: [PATCH 2020/2155] meson: shrink developer-mode build artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two complementary changes in the developer-mode branch of meson.build: 1. -ffunction-sections -fdata-sections: pair with the existing -Wl,--gc-sections so the linker can drop unused individual functions and data instead of being forced to pull whole .o files into each binary. Biggest impact on statically-linked NSS/PAM modules (a single call into creds-util.c used to drag in the entire creds-util translation unit, which transitively pulled TPM2, OpenSSL, PKCS11 and KDF helpers via tpm2-util.c / openssl-util.c) and on tests that embed daemon objects via meson's objects: extraction. 2. -gz=zstd + -fdebug-types-section + -Wl,--compress-debug-sections=zstd: compress every .debug_* section with zstd, and move type DIEs into a COMDAT-mergeable section so identical types described across many TUs land once. Both are transparent to GDB / readelf / addr2line. Gated to mode == 'developer' for now: no major distro (Fedora, Debian/ Ubuntu, Arch, Alpine, Gentoo, openSUSE, Yocto) enables -ffunction-sections in their system-wide default CFLAGS, and the interaction with -flto=auto + -ffat-lto-objects (which Fedora et al. ship by default) deserves a broader evaluation before turning it on for release builds. Developer mode benefits straightforwardly: smaller plugins, smaller tests, smaller libraries, no interference with the hardening/LTO flag combinations distros pin. Size impact on a clean developer-mode build, 626 ELF objects: | Category | n | Before | After | Δ | % | | -------------------------- | --: | ----------: | ----------: | --------------: | ----: | | NSS plugins | 4 | 22,163,272 | 6,681,120 | −15,482,152 | −69.9 | | PAM plugins | 3 | 18,132,160 | 5,764,880 | −12,367,280 | −68.2 | | libsystemd.so (public ABI) | 1 | 6,009,360 | 3,350,168 | −2,659,192 | −44.3 | | libudev.so (public ABI) | 1 | 4,379,336 | 1,441,256 | −2,938,080 | −67.1 | | libsystemd-shared-261.so | 1 | 15,130,264 | 11,293,952 | −3,836,312 | −25.4 | | libsystemd-core-261.so | 1 | 7,356,408 | 4,992,600 | −2,363,808 | −32.1 | | cryptsetup-token plugins | 3 | 139,800 | 113,344 | −26,456 | −18.9 | | daemons / CLI tools | 178 | 43,581,288 | 31,911,368 | −11,669,920 | −26.8 | | test binaries | 386 | 124,717,072 | 60,185,904 | −64,531,168 | −51.7 | | fuzzers | 48 | 33,138,056 | 13,864,952 | −19,273,104 | −58.2 | | **TOTAL** | 626 | 274,747,016 | 139,599,544 | **−135,147,472** | **−49.2** | Biggest individual wins: | Binary | Before | After | Δ | | ------------------------ | -------: | -------: | -----: | | test-networkd-address | 6.21 MB | 0.82 MB | −86.8% | | test-network-tables | 6.23 MB | 0.85 MB | −86.4% | | test-networkd-conf | 6.27 MB | 1.32 MB | −78.9% | | libnss_myhostname.so.2 | 6.40 MB | 1.53 MB | −76.1% | | fuzz-netdev-parser | 6.22 MB | 1.51 MB | −75.7% | | fuzz-network-parser | 6.22 MB | 1.70 MB | −72.6% | | libnss_systemd.so.2 | 6.90 MB | 2.04 MB | −70.4% | | libnss_resolve.so.2 | 4.42 MB | 1.38 MB | −68.8% | | pam_systemd_home.so | 6.91 MB | 2.22 MB | −67.9% | | libudev.so.1.7.13 | 4.18 MB | 1.37 MB | −67.1% | | pam_systemd.so | 7.02 MB | 2.55 MB | −63.6% | | libsystemd-shared-261.so | 14.43 MB | 10.77 MB | −25.4% | The big test wins come from the ~30 daemons (systemd-networkd, systemd-resolved, systemd-journald, systemd-logind, systemd-homed, systemd-importd, systemd-machined, …) whose compiled .o files are embedded directly into their unit tests via meson's objects: extraction mechanism. With per-function sections on the daemon sources, the test binary can GC the bulk of code it never exercises; the remaining DWARF is then shared zstd-compressed across every .o. Build-speed cost is below noise on a 24-core build: across four clean builds (with-flags / sections-only / baseline / with-flags rerun) the range was 23.6–26.0 s real time and 7m39s–7m48s user time, with the two with-flags runs faster than the baseline by a couple of seconds — overhead from per-function-section bookkeeping and zstd compression disappears into parallel-build noise. --- meson.build | 6 ++++++ .../build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/meson.build b/meson.build index 5b880cd95a7e1..98dfb6784049d 100644 --- a/meson.build +++ b/meson.build @@ -496,6 +496,12 @@ possible_cc_flags = [ if get_option('mode') == 'developer' possible_cc_flags += '-fno-omit-frame-pointer' + # Let -Wl,--gc-sections drop unused functions/data instead of whole + # .o files; biggest win on statically-linked NSS/PAM modules. + possible_cc_flags += ['-ffunction-sections', '-fdata-sections'] + # Compress and dedupe DWARF; transparent to GDB / readelf. + possible_cc_flags += ['-gz=zstd', '-fdebug-types-section'] + possible_link_flags += '-Wl,--compress-debug-sections=zstd' endif add_project_arguments( diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot index 02d4c3e5cb9cf..3db60289037ac 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot @@ -40,6 +40,12 @@ if [[ -d "$SRCDIR/debian/patches" ]]; then mount --bind /tmp/patches "$SRCDIR/debian/patches" fi +# mode=developer enables -Wl,--compress-debug-sections=zstd in meson.build, which dwz cannot process. +cp "$SRCDIR/debian/rules" /tmp/rules +chmod +x /tmp/rules +printf '\noverride_dh_dwz:\n' >>/tmp/rules +mount --bind /tmp/rules "$SRCDIR/debian/rules" + # While the build directory can be specified through DH_OPTIONS, the default one is hardcoded everywhere so # we have to use that. Because it is architecture dependent, we query it using dpkg-architecture first. DEB_HOST_GNU_TYPE="$(dpkg-architecture --query DEB_HOST_GNU_TYPE)" From 933a45835f842c418ebab18d05d3b04fb794d5a6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 14:59:49 +0000 Subject: [PATCH 2021/2155] basic/math-util: drop libm where possible - test-random-util is reworked to not use sqrt() - pretty-print.c inlines ceil() so libm doesn't have to be linked into libshared - We add fno-math-errno to allow inlining of more math functions by not requiring standard math functions to set errno on invalid input. --- meson.build | 5 +++++ src/basic/meson.build | 1 - src/libsystemd/meson.build | 1 - src/pcrlock/meson.build | 1 - src/resolve/meson.build | 1 - src/shared/pretty-print.c | 6 ++++-- src/test/meson.build | 2 -- src/test/test-random-util.c | 28 +++++++++++++++------------- 8 files changed, 24 insertions(+), 21 deletions(-) diff --git a/meson.build b/meson.build index 98dfb6784049d..9529ea7b0648a 100644 --- a/meson.build +++ b/meson.build @@ -425,6 +425,11 @@ possible_common_cc_flags = [ '-fstack-protector', '-fstack-protector-strong', '-fstrict-flex-arrays=3', + # We don't read errno from any libm call, and the math-errno fallback + # forces a DT_NEEDED on libm via the cold error path even when the hot + # path is a single hardware instruction (sqrtsd, etc.). Drop it so the + # builtins lower to pure hardware instructions. + '-fno-math-errno', '--param=ssp-buffer-size=4', ] diff --git a/src/basic/meson.build b/src/basic/meson.build index 20c424b45bc51..7cc62c7b8bdac 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -215,7 +215,6 @@ libbasic_static = static_library( dependencies : [libbzip2_cflags, libgcrypt_cflags, liblz4_cflags, - libm, libxz_cflags, libz_cflags, libzstd_cflags, diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index b7e11c9bb22b0..c6cf0fad34b60 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -220,7 +220,6 @@ libsystemd_tests += [ libgio_cflags, libglib_cflags, libgobject_cflags, - libm, ], }, { diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index a80cd31947977..dbe816402deca 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -12,7 +12,6 @@ executables += [ 'pcrlock-firmware.c', ), 'dependencies' : [ - libm, libopenssl_cflags, tpm2, ], diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 3650313355576..f024b02047c7b 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -71,7 +71,6 @@ resolve_common_template = { 'dependencies' : [ libidn2_cflags, libopenssl_cflags, - libm, ], } diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 7a4bc10eebf2a..3328293c5d256 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -567,7 +566,10 @@ void draw_progress_bar_unbuffered(const char *prefix, double percentage) { * https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC * https://github.com/microsoft/terminal/pull/8055 */ - fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, (unsigned) ceil(percentage)); + unsigned percentage_ceil = (unsigned) percentage; + if ((double) percentage_ceil < percentage) + percentage_ceil++; + fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, percentage_ceil); size_t cols = columns(); size_t prefix_width = utf8_console_width(prefix) + 1 /* space */; diff --git a/src/test/meson.build b/src/test/meson.build index b581de293b307..0c8dba64d5678 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -407,7 +407,6 @@ executables += [ }, test_template + { 'sources' : files('test-parse-util.c'), - 'dependencies' : libm, }, test_template + { 'sources' : files('test-shift-uid.c'), @@ -432,7 +431,6 @@ executables += [ }, test_template + { 'sources' : files('test-random-util.c'), - 'dependencies' : libm, 'timeout' : 120, }, test_template + { diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index d5043779b702a..bceb7b937e0a8 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "hexdecoct.h" #include "log.h" #include "memory-util.h" @@ -15,7 +13,7 @@ TEST(random_bytes) { for (size_t i = 1; i < sizeof buf; i++) { random_bytes(buf, i); if (i + 1 < sizeof buf) - assert_se(buf[i] == 0); + ASSERT_EQ(buf[i], 0); hexdump(stdout, buf, i); } @@ -25,9 +23,9 @@ TEST(crypto_random_bytes) { uint8_t buf[16] = {}; for (size_t i = 1; i < sizeof buf; i++) { - assert_se(crypto_random_bytes(buf, i) == 0); + ASSERT_OK(crypto_random_bytes(buf, i)); if (i + 1 < sizeof buf) - assert_se(buf[i] == 0); + ASSERT_EQ(buf[i], 0); hexdump(stdout, buf, i); } @@ -53,21 +51,25 @@ static void test_random_u64_range_one(unsigned mod) { /* Print histogram: vertical axis — value, horizontal axis — count. * * The expected value is always TOTAL/mod, because the distribution should be flat. The expected - * variance is TOTAL×p×(1-p), where p==1/mod, and standard deviation the root of the variance. - * Assert that the deviation from the expected value is less than 6 standard deviations. + * variance is TOTAL×p×(1-p), where p==1/mod. Assert that the deviation from the expected value + * is less than 6 standard deviations by comparing squared values (diff² < 36·variance), which + * avoids a sqrt() call and the libm dependency that comes with it at -O0. */ unsigned scale = 2 * max / (columns() < 20 ? 80 : columns() - 20); double exp = (double) TOTAL / mod; + double variance = exp * (mod > 1 ? mod - 1 : 1) / mod; for (size_t i = 0; i < mod; i++) { - double dev = (count[i] - exp) / sqrt(exp * (mod > 1 ? mod - 1 : 1) / mod); - log_debug("%02zu: %5u (%+.3f)%*s", - i, count[i], dev, + double diff = count[i] - exp; + double dev_sq = diff * diff / variance; + + log_debug("%02zu: %5u (z²=%.3f)%*s", + i, count[i], dev_sq, (int) (count[i] / scale), "x"); - assert_se(ABS(dev) < 6); /* 6 sigma is excessive, but this check should be enough to - * identify catastrophic failure while minimizing false - * positives. */ + ASSERT_TRUE(dev_sq < 36); /* 36 = 6²; 6 sigma is excessive, but this check should be + * enough to identify catastrophic failure while minimizing + * false positives. */ } } From 183051f23ffebbab57c1760a1781140bddf8b8a2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 14:51:41 +0200 Subject: [PATCH 2022/2155] vconsole-util: move code to read EFI keyboard layout into generic code --- src/shared/vconsole-util.c | 27 +++++++++++++++++++++++++++ src/shared/vconsole-util.h | 1 + src/vconsole/vconsole-setup.c | 17 +++-------------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index aa156f4736fc6..9e56ce823df42 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-util.h" #include "extract-word.h" #include "fd-util.h" @@ -655,6 +656,32 @@ int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { return 1; } +int vconsole_keymap_from_efi(char **ret) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + _cleanup_free_ char *tag = NULL; + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable: %m"); + + r = find_vconsole_keymap_for_bcp47(tag, ret); + if (r < 0) + return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s': %m", tag); + + return r; +} + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { int r; diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h index 3d52ec9866ce8..416eb86eb54b9 100644 --- a/src/shared/vconsole-util.h +++ b/src/shared/vconsole-util.h @@ -40,5 +40,6 @@ int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Co int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); int find_vconsole_keymap_for_bcp47(const char *tag, char **ret); +int vconsole_keymap_from_efi(char **ret); int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index c1bf2b0f4f5d9..f8c5dffd1eea9 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -15,7 +15,6 @@ #include "alloc-util.h" #include "creds-util.h" -#include "efivars.h" #include "env-file.h" #include "errno-util.h" #include "fd-util.h" @@ -76,25 +75,15 @@ static void context_merge_config( static int context_read_efi(Context *c) { _cleanup_(context_done) Context v = {}; - _cleanup_free_ char *tag = NULL; int r; assert(c); - if (!is_efi_boot()) - return 0; - - r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); - if (r == -ENOENT) - return 0; + r = vconsole_keymap_from_efi(&v.keymap); if (r < 0) - return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable, ignoring: %m"); - - r = find_vconsole_keymap_for_bcp47(tag, &v.keymap); - if (r < 0) - return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s', ignoring: %m", tag); + return r; if (r == 0) { - log_debug("No vconsole keymap matches firmware-provided keyboard layout '%s', ignoring.", tag); + log_debug("No vconsole keymap matches firmware-provided keyboard layout, ignoring."); return 0; } From a80024e96d9d09c85422499fe4de5dd59458af75 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 14:59:38 +0200 Subject: [PATCH 2023/2155] ask-string: support prefilling a string query with an explicit string --- src/basic/terminal-util.c | 14 ++++++++++++-- src/basic/terminal-util.h | 4 ++-- src/firstboot/firstboot.c | 6 ++++++ src/home/homectl-prompts.c | 2 ++ src/home/homectl.c | 1 + src/shared/prompt-util.c | 2 ++ src/shared/prompt-util.h | 1 + src/sysinstall/sysinstall.c | 4 ++++ 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 5b3fc8990d696..0d5b57138bc1d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -283,6 +283,7 @@ static void clear_by_backspace(size_t n) { int ask_string_full( char **ret, + const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) { @@ -296,8 +297,17 @@ int ask_string_full( _cleanup_free_ char *string = NULL; size_t n = 0; - if (get_completions) { - /* Figure out what string to preselect the query with */ + if (prefill) { + /* Prefill query with explicit data if specified */ + + string = strdup(prefill); + if (!string) + return -ENOMEM; + + n = strlen(string); + + } else if (get_completions) { + /* Otherwise, figure out what string to preselect the query with */ _cleanup_strv_free_ char **completions = NULL; r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata); if (r < 0) diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index abf999e6d5562..f8cd74f410521 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -95,8 +95,8 @@ typedef enum GetCompletionsFlags { } GetCompletionsFlags; typedef int (*GetCompletionsCallback)(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata); -int ask_string_full(char **ret, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(4, 5); -#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, text, ##__VA_ARGS__) +int ask_string_full(char **ret, const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(5, 6); +#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, NULL, text, ##__VA_ARGS__) bool any_key_to_proceed(void); int show_menu(char **x, size_t n_columns, size_t column_width, unsigned ellipsize_percentage, const char *grey_prefix, bool with_numbers); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 8048ec0e810ca..ea36b910646d6 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -295,6 +295,7 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new system locale name or number", GLYPH_WORLD, + /* prefill= */ NULL, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -312,6 +313,7 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new system message locale name or number", GLYPH_WORLD, + /* prefill= */ NULL, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -457,6 +459,7 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new keymap name or number", GLYPH_KEYBOARD, + /* prefill= */ NULL, kmaps, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -573,6 +576,7 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new timezone name or number", GLYPH_CLOCK, + /* prefill= */ NULL, zones, /* accepted= */ NULL, /* ellipsize_percentage= */ 30, @@ -680,6 +684,7 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new hostname", GLYPH_LABEL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 100, @@ -888,6 +893,7 @@ static int prompt_root_shell(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new root shell", GLYPH_SHELL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 0, diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c index 71640377e3888..95a6ec3d04f40 100644 --- a/src/home/homectl-prompts.c +++ b/src/home/homectl-prompts.c @@ -117,6 +117,7 @@ int prompt_groups(const char *username, char ***ret_groups) { _cleanup_free_ char *s = NULL; r = ask_string_full( &s, + /* prefill= */ NULL, group_completion_callback, &available, "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", @@ -231,6 +232,7 @@ int prompt_shell(const char *username, char **ret_shell) { return prompt_loop( q, GLYPH_SHELL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 0, diff --git a/src/home/homectl.c b/src/home/homectl.c index ab81efbed6dd1..6891475f3780f 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3009,6 +3009,7 @@ static int create_interactively(void) { r = prompt_loop("Please enter user name to create", GLYPH_IDCARD, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 2f334f9b52832..69728e0cb4c4c 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -60,6 +60,7 @@ static int get_completions( int prompt_loop( const char *text, Glyph emoji, + const char *prefill, /* if non-NULL: prefill prompt with this string */ char **menu, /* if non-NULL: choices to suggest */ char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ unsigned ellipsize_percentage, @@ -116,6 +117,7 @@ int prompt_loop( _cleanup_free_ char *p = NULL; r = ask_string_full( &p, + prefill, get_completions, &(CompletionData) { menu, accepted }, "%s%s%s%s: ", diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 436127b32c237..7afccd1b3dec5 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -14,6 +14,7 @@ typedef enum PromptFlags { int prompt_loop(const char *text, Glyph emoji, + const char *prefill, char **menu, char **accepted, unsigned ellipsize_percentage, diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 7d08b4866b272..23f4472e1c0a4 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -384,6 +384,7 @@ static int prompt_block_device(sd_varlink **repart_link, char **ret_node) { r = prompt_loop("Please enter target disk device", GLYPH_COMPUTER_DISK, + /* prefill= */ NULL, menu, accepted, /* ellipsize_percentage= */ 20, @@ -540,6 +541,7 @@ static int prompt_erase( "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" : "Please type 'erase' to confirm that all data on the disk shall be erased", GLYPH_BROOM, + /* prefill= */ NULL, /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, @@ -578,6 +580,7 @@ static int prompt_touch_variables(void) { _cleanup_free_ char *reply = NULL; r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise", GLYPH_ROCKET, + /* prefill= */ NULL, /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, @@ -616,6 +619,7 @@ static int prompt_confirm(void) { r = prompt_loop(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" : "Please type 'yes' to begin the installation", GLYPH_WARNING_SIGN, + /* prefill= */ NULL, /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, From 1d646aed6505c3f3161a57edb55d89dca7690605 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:07:32 +0200 Subject: [PATCH 2024/2155] firstboot: prefill keymap question with EFI provided info --- src/firstboot/firstboot.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index ea36b910646d6..470caf8283f2b 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -456,10 +456,13 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) vconsole_keymap_from_efi(&prefill); + return prompt_loop( "Please enter the new keymap name or number", GLYPH_KEYBOARD, - /* prefill= */ NULL, + prefill, kmaps, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, From 706de22cba827b5b0e7ab3ba4f505f5ddd1e3ece Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:08:46 +0200 Subject: [PATCH 2025/2155] sysinstall: prefill firmware variable question with 'yes' --- src/sysinstall/sysinstall.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 23f4472e1c0a4..0092f3f9c8c92 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -580,7 +580,7 @@ static int prompt_touch_variables(void) { _cleanup_free_ char *reply = NULL; r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise", GLYPH_ROCKET, - /* prefill= */ NULL, + /* prefill= */ "yes", /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, From 862816a1fcdc1f3455caa519ab436d15af608164 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:50:20 +0200 Subject: [PATCH 2026/2155] locale-setup: pick up platform lang from firmware --- src/shared/locale-setup.c | 76 +++++++++++++++++++++++++++++++++++++++ src/shared/locale-setup.h | 7 ++++ 2 files changed, 83 insertions(+) diff --git a/src/shared/locale-setup.c b/src/shared/locale-setup.c index 6b66c1544366c..294963640e0ea 100644 --- a/src/shared/locale-setup.c +++ b/src/shared/locale-setup.c @@ -5,10 +5,12 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-file.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" +#include "iovec-util.h" #include "locale-setup.h" #include "log.h" #include "proc-cmdline.h" @@ -318,3 +320,77 @@ const char* etc_vconsole_conf(void) { return cached; } + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + /* NB: unlike most other UEFI variables, PlatformLang is actually in 7bit ASCII! Hence we are not + * using efi_get_variable_string() here */ + _cleanup_(iovec_done) struct iovec iov = {}; + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("PlatformLang"), /* ret_attribute= */ NULL, &iov.iov_base, &iov.iov_len); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read PlatformLang EFI variable: %m"); + + _cleanup_free_ char *tag = NULL; + r = make_cstring(iov.iov_base, iov.iov_len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &tag); + if (r < 0) + return log_debug_errno(r, "Failed to convert PlatformLang EFI variable to C string: %m"); + + /* Convert the UEFI BCP47 language tag into a glibc tag. We'll not bother with the complexity of the + * whole spec, but just convert "xx-XX" into "xx_XX", with some flexibility on the case + * sensitivity. This is a best-effort thing anyway. */ + + if (strlen(tag) != 5 || + !strchr(LETTERS, tag[0]) || + !strchr(LETTERS, tag[1]) || + tag[2] != '-' || + !strchr(LETTERS, tag[3]) || + !strchr(LETTERS, tag[4])) { + log_debug("PlatformLang variable does not have the form 'xx-XX', ignoring: %s", tag); + *ret = NULL; + return 0; + } + + tag[0] = ascii_tolower(tag[0]); + tag[1] = ascii_tolower(tag[1]); + tag[2] = '_'; + tag[3] = ascii_toupper(tag[3]); + tag[4] = ascii_toupper(tag[4]); + + /* Let's optionally suppress en_US locale, since that's almost certainly just the built-in default + * locale of the firmware. Since we typically prefer C.UTF-8 over en_US.UTF-8 as default, let's hence + * suppress it. */ + if (FLAGS_SET(flags, LOCALE_SUPPRESS_EN_US) && streq(tag, "en_US")) { + log_debug("Firmware language is en_US, suppressing because likely just the firmware default."); + *ret = NULL; + return 0; + } + + if (!strextend(&tag, ".UTF-8")) + return -ENOMEM; + + if (FLAGS_SET(flags, LOCALE_REQUIRE_INSTALLED)) { + r = locale_is_installed(tag); + if (r < 0) + return r; + if (r == 0) { + log_debug("Determined locale '%s' from PlatformLang, but it isn't installed, ignoring.", tag); + *ret = NULL; + return 0; + } + } + + *ret = TAKE_PTR(tag); + return 1; +} diff --git a/src/shared/locale-setup.h b/src/shared/locale-setup.h index 8b71064cad13c..d720a53adb268 100644 --- a/src/shared/locale-setup.h +++ b/src/shared/locale-setup.h @@ -31,3 +31,10 @@ int locale_setup(char ***environment); const char* etc_locale_conf(void); const char* etc_vconsole_conf(void); + +typedef enum LocaleLangFromEfiFlags { + LOCALE_REQUIRE_INSTALLED = 1 << 0, + LOCALE_SUPPRESS_EN_US = 1 << 1, +} LocaleLangFromEfiFlags; + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags); From ad19aced00256667c9f5825b11bdc6d9d0ce7f91 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:50:35 +0200 Subject: [PATCH 2027/2155] bootctl: show platform lang in bootctl status output --- src/bootctl/bootctl-status.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 4cd0e3aca0e75..4aac25a74dc42 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -17,6 +17,7 @@ #include "efivars.h" #include "errno-util.h" #include "fd-util.h" +#include "locale-setup.h" #include "log.h" #include "pager.h" #include "pretty-print.h" @@ -501,6 +502,18 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { errno = -k; printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } + + _cleanup_free_ char *lang = NULL; + k = locale_lang_from_efi(&lang, /* flags= */ 0); + if (k > 0) + printf(" Platform Lang: %s\n", lang); + else if (k == 0) + printf(" Platform Lang: n/a\n"); + else { + errno = -k; + printf(" Platform Lang: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + } + printf("\n"); if (loader) { From 460dab3936180a2d340bedc8d849175e809cfcae Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:50:39 +0200 Subject: [PATCH 2028/2155] firstboot: prefill language prompt with firmware language if it makes sense --- src/firstboot/firstboot.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 470caf8283f2b..754c82ace0718 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -293,9 +293,12 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { } else { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) locale_lang_from_efi(&prefill, LOCALE_REQUIRE_INSTALLED|LOCALE_SUPPRESS_EN_US); + r = prompt_loop("Please enter the new system locale name or number", GLYPH_WORLD, - /* prefill= */ NULL, + prefill, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, From ec1f40c89dc89ab6e82a845204d36121dfa0fcd4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 16:34:13 +0200 Subject: [PATCH 2029/2155] firstboot: port help() to help-util.[ch] APIs --- src/firstboot/firstboot.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 754c82ace0718..60ec63e2daed6 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -8,6 +8,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "build.h" #include "bus-error.h" @@ -26,6 +27,7 @@ #include "format-table.h" #include "fs-util.h" #include "glyph-util.h" +#include "help-util.h" #include "hostname-util.h" #include "image-policy.h" #include "kbd-util.h" @@ -44,7 +46,6 @@ #include "password-quality-util.h" #include "path-util.h" #include "plymouth-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "prompt-util.h" #include "runtime-scope.h" @@ -1252,29 +1253,22 @@ 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(); - 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, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...]"); + help_abstract("Configures basic settings of the system."); + 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-firstboot", "1"); return 0; } From 10151303abb7dd8ee67dbbf51729b129325a36fe Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:54:48 +0200 Subject: [PATCH 2030/2155] update TODO --- TODO.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/TODO.md b/TODO.md index 7672fdb9f731f..5c8f92ac42172 100644 --- a/TODO.md +++ b/TODO.md @@ -136,24 +136,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later the Varlinkifcation has progressed various non-desktop usescase might not need D-Bus running at all anymore. -- Reuse the LoaderKeyboardLayout EFI variable/find_vconsole_keymap_for_bcp47() - to pre-select kbd layout in systemd-firstboot. (Right now it's only used in - vconsole-setup as fallback). - -- Start using the PlatformLang EFI variable to pre-select language in - systemd-firstboot. (At least HP laptops seem to make good use of/configure - the value) - - format-table: introduce the concept of a "title" for a table, which remains closely associated with the table. in most cases where want to output multiple tables from the same tool we want to separate things with a title, hence we might as well associate the title with the table itself, and streamline a few things. -- report: add filtering by metric prefix, so that we reports don't have to - include everything, and we have a way to have "small" and "big" reports, that - have different number of fields. - - allow metrics to indicate which values mean "nothing"/"invalid"/"zero"/"please-suppress". Then use that to reduce noise in systemd-report output. @@ -912,7 +900,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later 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 @@ -3038,8 +3025,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later 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. From 5833182dc105f08faedceb0ffe7139bdcf8d4ab3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 20 May 2026 15:51:17 +0100 Subject: [PATCH 2031/2155] Revert "meson: shrink developer-mode build artifacts" This reverts commit 68910161491cdd161bff29a32032e52301831164. --- meson.build | 6 ------ .../build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot | 6 ------ 2 files changed, 12 deletions(-) diff --git a/meson.build b/meson.build index 9529ea7b0648a..2f5bece79ded8 100644 --- a/meson.build +++ b/meson.build @@ -501,12 +501,6 @@ possible_cc_flags = [ if get_option('mode') == 'developer' possible_cc_flags += '-fno-omit-frame-pointer' - # Let -Wl,--gc-sections drop unused functions/data instead of whole - # .o files; biggest win on statically-linked NSS/PAM modules. - possible_cc_flags += ['-ffunction-sections', '-fdata-sections'] - # Compress and dedupe DWARF; transparent to GDB / readelf. - possible_cc_flags += ['-gz=zstd', '-fdebug-types-section'] - possible_link_flags += '-Wl,--compress-debug-sections=zstd' endif add_project_arguments( diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot index 3db60289037ac..02d4c3e5cb9cf 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot @@ -40,12 +40,6 @@ if [[ -d "$SRCDIR/debian/patches" ]]; then mount --bind /tmp/patches "$SRCDIR/debian/patches" fi -# mode=developer enables -Wl,--compress-debug-sections=zstd in meson.build, which dwz cannot process. -cp "$SRCDIR/debian/rules" /tmp/rules -chmod +x /tmp/rules -printf '\noverride_dh_dwz:\n' >>/tmp/rules -mount --bind /tmp/rules "$SRCDIR/debian/rules" - # While the build directory can be specified through DH_OPTIONS, the default one is hardcoded everywhere so # we have to use that. Because it is architecture dependent, we query it using dpkg-architecture first. DEB_HOST_GNU_TYPE="$(dpkg-architecture --query DEB_HOST_GNU_TYPE)" From 12836f982a29215ee0fa1a608ec7935c7ee903c4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 12:14:52 +0000 Subject: [PATCH 2032/2155] ptyfwd: Imply PTY_FORWARD_READ_ONLY if stdin isn't readable if stdin is connected to a closed pipe or similar, imply PTY_FORWARD_READ_ONLY so we don't even try to read from it in the first place. Otherwise we'll immediately get a hangup which will cause the forwarder to call sd_event_exit() and shut down the event loop. Debugged-by: Christian Brauner --- src/shared/ptyfwd.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 88cfd596d0508..fcc1500dc5c8b 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -934,6 +934,24 @@ int pty_forward_new( assert(master >= 0); assert(ret); + if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) { + /* If stdin isn't actually opened for reading, or refers to the read end of a pipe (or + * socket) whose peer has already hung up, then there's nothing for us to forward — imply + * read-only mode. */ + r = RET_NERRNO(fcntl(STDIN_FILENO, F_GETFL)); + if (r < 0 && r != -EBADF) + return log_debug_errno(errno, "Failed to query stdin flags: %m"); + if (r == -EBADF || (r & O_ACCMODE_STRICT) == O_WRONLY) + flags |= PTY_FORWARD_READ_ONLY; + else { + r = pipe_eof(STDIN_FILENO); + if (r < 0) + log_debug_errno(r, "Failed to check whether stdin is at EOF, ignoring: %m"); + else if (r > 0) + flags |= PTY_FORWARD_READ_ONLY; + } + } + f = new(PTYForward, 1); if (!f) return -ENOMEM; From e44f92ec40a5294beba3c5b466fcd9336ba22cc1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 21:07:10 +0000 Subject: [PATCH 2033/2155] mkosi: update mkosi ref to be746d51bc90568b196951a60095ba87bf51ca8b * be746d51bc Make full $PATH available when building tools tree --- .github/workflows/coverage.yml | 2 +- .github/workflows/linter.yml | 2 +- .github/workflows/mkosi.yml | 2 +- mkosi/mkosi.conf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7a968ac95bbb3..5143ea73e16c5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 36c0bc9e3384c..edc970de5b5ba 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,7 +40,7 @@ jobs: GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d - name: Check that tabs are not used in Python code run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 5ed4907665828..de6673c9575c3 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -169,7 +169,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index b807fdb3c50b5..a6966de2058fb 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later [Config] -MinimumVersion=commit:77fce77807a9a92bc37edc8f1c967102e6236d94 +MinimumVersion=commit:444d247d1a1328bcfb84a945e84959bd4bd0e02d Dependencies= minimal-base minimal-0 From b01dcb3a810fcbf7513490c5167024ce4c6b1e59 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 08:51:56 +0000 Subject: [PATCH 2034/2155] test-ukify: Skip kernel images we can't access --- src/ukify/test/test_ukify.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 0fe27601f15b4..11f843ddb6015 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -456,6 +456,9 @@ def kernel_initrd(): items = sorted(glob.glob('/lib/modules/*/vmlinuz')) if not items: items = sorted(glob.glob('/boot/vmlinuz*')) + # Drop entries we cannot read (e.g. /boot/vmlinuz.old with mode 0600 on + # GitHub-hosted runners), the test opens the file later and would fail. + items = [p for p in items if os.access(p, os.R_OK)] if not items: return None From fde1cccf31521b02f89566449110be7ef98dfee9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 07:18:41 +0000 Subject: [PATCH 2035/2155] meson: Skip coccinelle suite by default It's not fully passing yet so disable it by default until it is. clang-tidy follows the same model. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2f5bece79ded8..1d2d59e4fa536 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ project('systemd', 'c', add_test_setup( 'default', - exclude_suites : ['clang-tidy', 'unused-symbols', 'integration-tests'], + exclude_suites : ['clang-tidy', 'coccinelle', 'unused-symbols', 'integration-tests'], exe_wrapper : find_program('tools/test-crash-trace.sh'), is_default : true, ) From eb69a3c59dcd0dac67dc758429bfbe5980209d0e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 08:52:21 +0000 Subject: [PATCH 2036/2155] test-path: Fail earlier on start-limit-hit --- src/test/test-path.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/test-path.c b/src/test/test-path.c index 512eb96ead60f..d9e11fd029450 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -118,6 +118,17 @@ static int _check_states(unsigned line, exit(EXIT_TEST_SKIP); } + /* SERVICE_FAILURE_START_LIMIT_HIT is terminal: the unit won't recover without an explicit + * reset, so further looping is pointless. Abort the subtest rather than burning the 30s + * timeout. */ + if (service->state == SERVICE_FAILED && + service->result == SERVICE_FAILURE_START_LIMIT_HIT) + return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), + "Failed to start service %s, aborting test: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + if (n >= end) { log_error("Test timeout when testing %s", UNIT(path)->id); exit(EXIT_FAILURE); From fc7a32df38dd4797ce7266df6ece6bd9b9842783 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 13:30:28 +0000 Subject: [PATCH 2037/2155] test-path: Migrate to test framework and macros - Also clean up the logging in check_states() to only log on state changes so it's less noisy. --- src/test/test-path.c | 398 ++++++++++++++++++++----------------------- 1 file changed, 181 insertions(+), 217 deletions(-) diff --git a/src/test/test-path.c b/src/test/test-path.c index d9e11fd029450..b02f7e2e7f7d6 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -16,15 +16,15 @@ #include "tests.h" #include "unit.h" -typedef void (*test_function_t)(Manager *m); +static char *runtime_dir = NULL; static int setup_test(Manager **m) { char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit", "directorynotempty", "makedirectory"); - Manager *tmp = NULL; + _cleanup_(manager_freep) Manager *tmp = NULL; int r; - assert_se(m); + ASSERT_NOT_NULL(m); r = enter_cgroup_subroot(NULL); if (r == -ENOMEDIUM) @@ -33,396 +33,360 @@ static int setup_test(Manager **m) { r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &tmp); if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); - assert_se(r >= 0); - assert_se(manager_startup(tmp, NULL, NULL, NULL, NULL) >= 0); + ASSERT_OK(r); + ASSERT_OK(manager_startup(tmp, NULL, NULL, NULL, NULL)); STRV_FOREACH(test_path, tests_path) { _cleanup_free_ char *p = NULL; p = strjoin("/tmp/test-path_", *test_path); - assert_se(p); + ASSERT_NOT_NULL(p); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } - *m = tmp; - + *m = TAKE_PTR(tmp); return 0; } -static void shutdown_test(Manager *m) { - assert_se(m); - - manager_free(m); -} - static Service *service_for_path(Manager *m, Path *path, const char *service_name) { _cleanup_free_ char *tmp = NULL; Unit *service_unit = NULL; - assert_se(m); - assert_se(path); + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(path); if (!service_name) { - assert_se(tmp = strreplace(UNIT(path)->id, ".path", ".service")); + tmp = ASSERT_NOT_NULL(strreplace(UNIT(path)->id, ".path", ".service")); service_unit = manager_get_unit(m, tmp); } else service_unit = manager_get_unit(m, service_name); - assert_se(service_unit); + ASSERT_NOT_NULL(service_unit); return SERVICE(service_unit); } -static int _check_states(unsigned line, - Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) { - assert_se(m); - assert_se(service); +/* Define a test that gets a freshly-initialized Manager passed as `m`. The body returns 0 on + * success or EXIT_TEST_SKIP to skip; the wrapper hands the value back to the test framework. */ +#define PATH_TEST(name) \ + static int test_##name##_body(Manager *m); \ + TEST_RET(name) { \ + _cleanup_(manager_freep) Manager *m = NULL; \ + int r = setup_test(&m); \ + if (r != 0) \ + return r; \ + return test_##name##_body(m); \ + } \ + static int test_##name##_body(Manager *m) + +static int _check_states( + unsigned line, + Manager *m, + Path *path, + Service *service, + PathState path_state, + ServiceState service_state) { + + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(service); usec_t end = usec_add(now(CLOCK_MONOTONIC), 30 * USEC_PER_SEC); + PathState last_path_state = _PATH_STATE_INVALID; + PathResult last_path_result = _PATH_RESULT_INVALID; + ServiceState last_service_state = _SERVICE_STATE_INVALID; + ServiceResult last_service_result = _SERVICE_RESULT_INVALID; while (path->state != path_state || service->state != service_state || path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { - assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0); + ASSERT_OK(sd_event_run(m->event, 100 * USEC_PER_MSEC)); usec_t n = now(CLOCK_MONOTONIC); - log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", - line, - UNIT(path)->id, - path_state_to_string(path->state), - path_result_to_string(path->result), - (int64_t) (end - n)); - log_info("line %u: %s: state = %s; result = %s", - line, - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); + if (path->state != last_path_state || path->result != last_path_result || + service->state != last_service_state || service->result != last_service_result) { + log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", + line, + UNIT(path)->id, + path_state_to_string(path->state), + path_result_to_string(path->result), + (int64_t) (end - n)); + log_info("line %u: %s: state = %s; result = %s", + line, + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + last_path_state = path->state; + last_path_result = path->result; + last_service_state = service->state; + last_service_result = service->result; + } if (service->state == SERVICE_FAILED && (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) { - const char *ci = ci_environment(); - /* On a general purpose system we may fail to start the service for reasons which are * not under our control: permission limits, resource exhaustion, etc. Let's skip the - * test in those cases. On developer machines we require proper setup. */ - if (!ci) - return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), - "Failed to start service %s, aborting test: %s/%s", - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); - - /* On Salsa we can't setup cgroups so the unit always fails. The test checks if it - * can but continues if it cannot at the beginning, but on Salsa it fails here. */ - if (streq(ci, "salsa-ci")) - exit(EXIT_TEST_SKIP); + * test in those cases. On CI we expect a properly configured environment, except for + * Salsa where we can't set up cgroups so the unit always fails. */ + const char *ci = ci_environment(); + if (!ci || streq(ci, "salsa-ci")) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); } /* SERVICE_FAILURE_START_LIMIT_HIT is terminal: the unit won't recover without an explicit - * reset, so further looping is pointless. Abort the subtest rather than burning the 30s - * timeout. */ + * reset, so further looping is pointless. Skip the test rather than burning the 30s timeout. */ if (service->state == SERVICE_FAILED && service->result == SERVICE_FAILURE_START_LIMIT_HIT) - return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), - "Failed to start service %s, aborting test: %s/%s", - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); - - if (n >= end) { - log_error("Test timeout when testing %s", UNIT(path)->id); - exit(EXIT_FAILURE); - } + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + + if (n >= end) + log_test_failed("Test timeout when testing %s", UNIT(path)->id); } return 0; } -#define check_states(...) _check_states(__LINE__, __VA_ARGS__) -static void test_path_exists(Manager *m) { +#define check_states(...) \ + do { \ + int _r = _check_states(__LINE__, __VA_ARGS__); \ + if (_r != 0) \ + return _r; \ + } while (0) + +PATH_TEST(path_exists) { const char *test_path = "/tmp/test-path_exists"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_existsglob(Manager *m) { +PATH_TEST(path_existsglob) { const char *test_path = "/tmp/test-path_existsglobFOOBAR"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_changed(Manager *m) { +PATH_TEST(path_changed) { const char *test_path = "/tmp/test-path_changed"; FILE *f; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); + f = ASSERT_NOT_NULL(fopen(test_path, "w")); fclose(f); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_modified(Manager *m) { +PATH_TEST(path_modified) { _cleanup_fclose_ FILE *f = NULL; const char *test_path = "/tmp/test-path_modified"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); + f = ASSERT_NOT_NULL(fopen(test_path, "w")); fputs("test", f); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_unit(Manager *m) { +PATH_TEST(path_unit) { const char *test_path = "/tmp/test-path_unit"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, "path-mycustomunit.service"); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_directorynotempty(Manager *m) { +PATH_TEST(path_directorynotempty) { const char *test_file, *test_path = "/tmp/test-path_directorynotempty/"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); /* MakeDirectory default to no */ - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(mkdir_p(test_path, 0755) >= 0); + ASSERT_OK(mkdir_p(test_path, 0755)); test_file = strjoina(test_path, "test_file"); - assert_se(touch(test_file) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_file)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if directory is still not empty */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_makedirectory_directorymode(Manager *m) { +PATH_TEST(path_makedirectory_directorymode) { const char *test_path = "/tmp/test-path_makedirectory/"; Unit *unit = NULL; struct stat s; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit)); - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(unit_start(unit, NULL) >= 0); + ASSERT_OK(unit_start(unit, NULL)); /* Check if the directory has been created */ - assert_se(access(test_path, F_OK) >= 0); + ASSERT_OK_ERRNO(access(test_path, F_OK)); /* Check the mode we specified with DirectoryMode=0744 */ - assert_se(stat(test_path, &s) >= 0); - assert_se((s.st_mode & S_IRWXU) == 0700); - assert_se((s.st_mode & S_IRWXG) == 0040); - assert_se((s.st_mode & S_IRWXO) == 0004); + ASSERT_OK_ERRNO(stat(test_path, &s)); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXU), (mode_t) 0700); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXG), (mode_t) 0040); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXO), (mode_t) 0004); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + return 0; } -int main(int argc, char *argv[]) { - static const test_function_t tests[] = { - test_path_exists, - test_path_existsglob, - test_path_changed, - test_path_modified, - test_path_unit, - test_path_directorynotempty, - test_path_makedirectory_directorymode, - NULL, - }; - +static int intro(void) { _cleanup_free_ char *test_path = NULL; - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; umask(022); - test_setup_logging(LOG_INFO); - ASSERT_OK(get_testdata_dir("test-path", &test_path)); ASSERT_OK(setenv_unit_path(test_path)); - assert_se(runtime_dir = setup_fake_runtime_dir()); - - for (const test_function_t *test = tests; *test; test++) { - Manager *m = NULL; - int r; - - /* We create a clean environment for each test */ - r = setup_test(&m); - if (r != 0) - return r; - - (*test)(m); + ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); - shutdown_test(m); - } + return EXIT_SUCCESS; +} - return 0; +static int outro(void) { + runtime_dir = rm_rf_physical_and_free(runtime_dir); + return EXIT_SUCCESS; } + +DEFINE_TEST_MAIN_FULL(LOG_INFO, intro, outro); From 70b6dc763146459ffa3608d93350ef2004b95a2b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 20 May 2026 17:30:31 +0100 Subject: [PATCH 2038/2155] meson: reorder core sources array To hint that sources should be added to the lowest common denominator first --- src/core/meson.build | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/core/meson.build b/src/core/meson.build index a56df224abcd2..74871444a3913 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -1,5 +1,26 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +systemd_sources = files( + 'main.c', + 'crash-handler.c', + 'clock-warp.c', + 'kmod-setup.c', + 'apparmor-setup.c', + 'ima-setup.c', + 'ipe-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', +) + libcore_sources = files( 'audit-fd.c', 'automount.c', @@ -168,27 +189,6 @@ libcore = shared_library( install : true, install_dir : pkglibdir) -systemd_sources = files( - 'main.c', - 'crash-handler.c', - 'clock-warp.c', - 'kmod-setup.c', - 'apparmor-setup.c', - 'ima-setup.c', - 'ipe-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', -) - executor_libs = get_option('link-executor-shared') ? \ [ libcore, From fd16d4ac5426ea3b77f9b176cd8ed5fbd355eac2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 20 May 2026 17:32:08 +0100 Subject: [PATCH 2039/2155] meson: reorder core sources alphabetically --- src/core/meson.build | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/meson.build b/src/core/meson.build index 74871444a3913..83f7ba12f4d6c 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -1,15 +1,15 @@ # SPDX-License-Identifier: LGPL-2.1-or-later systemd_sources = files( - 'main.c', - 'crash-handler.c', - 'clock-warp.c', - 'kmod-setup.c', 'apparmor-setup.c', + 'clock-warp.c', + 'crash-handler.c', + 'efi-random.c', 'ima-setup.c', 'ipe-setup.c', + 'kmod-setup.c', + 'main.c', 'smack-setup.c', - 'efi-random.c', ) if conf.get('HAVE_SELINUX') == 1 @@ -24,6 +24,7 @@ systemd_executor_sources = files( libcore_sources = files( 'audit-fd.c', 'automount.c', + 'bpf-bind-iface.c', 'bpf-devices.c', 'bpf-firewall.c', 'bpf-foreign.c', @@ -31,7 +32,6 @@ libcore_sources = files( 'bpf-restrict-fsaccess.c', 'bpf-restrict-ifaces.c', 'bpf-socket-bind.c', - 'bpf-bind-iface.c', 'cgroup.c', 'dbus-automount.c', 'dbus-cgroup.c', From e13e69de7c379dd10f5b489b82acbb7a73ec637c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 20 May 2026 18:15:52 +0100 Subject: [PATCH 2040/2155] meson: move one source file from libcore to systemd array --- src/core/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/meson.build b/src/core/meson.build index 83f7ba12f4d6c..e1f315dda622d 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -6,6 +6,7 @@ systemd_sources = files( 'crash-handler.c', 'efi-random.c', 'ima-setup.c', + 'import-creds.c', 'ipe-setup.c', 'kmod-setup.c', 'main.c', @@ -59,7 +60,6 @@ libcore_sources = files( 'execute.c', 'execute-serialize.c', 'generator-setup.c', - 'import-creds.c', 'job.c', 'kill.c', 'load-dropin.c', From 65f076f4b05fb47ec286925baf94a6fd37ed45c1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 17:21:13 +0000 Subject: [PATCH 2041/2155] test-path: Skip test when we can't create a cgroup Instead of having CI runner specific checks, let's just skip the test if we get EXIT_CGROUP which is what we get when we can't create a cgroup. This makes the check work independently of CI runner, and specifically also on github actions. --- src/test/test-path.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/test/test-path.c b/src/test/test-path.c index b02f7e2e7f7d6..52b9d75f6013f 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -121,19 +121,16 @@ static int _check_states( last_service_result = service->result; } + /* We may fail to start the service for reasons which are not under our control: cgroup + * setup denied, permission limits, resource exhaustion, etc. RESOURCES is terminal here + * for path units that don't auto-retry (PathChanged, PathModified) — they'd just sit in + * the failure state until the test timeout. Skip rather than wait. */ if (service->state == SERVICE_FAILED && - (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) { - /* On a general purpose system we may fail to start the service for reasons which are - * not under our control: permission limits, resource exhaustion, etc. Let's skip the - * test in those cases. On CI we expect a properly configured environment, except for - * Salsa where we can't set up cgroups so the unit always fails. */ - const char *ci = ci_environment(); - if (!ci || streq(ci, "salsa-ci")) - return log_tests_skipped("Failed to start service %s: %s/%s", - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); - } + (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); /* SERVICE_FAILURE_START_LIMIT_HIT is terminal: the unit won't recover without an explicit * reset, so further looping is pointless. Skip the test rather than burning the 30s timeout. */ From c7113f6b3cd9932ac2ddba507138bc9d7df49d4d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 19 May 2026 10:36:01 +0000 Subject: [PATCH 2042/2155] ci: run the musl build & test under mkosi with a postmarketOS tools tree Drop the standalone Unit-tests (musl) workflow that ran on an Alpine sandbox spun up by jirutka/setup-alpine, and merge it into unit-tests.yml as a new build-musl job that provisions a postmarketOS tools tree via mkosi and runs the meson build + test suite through 'mkosi box'. postmarketOS is musl-native, so the musl-gcc / -idirafter /usr/include wrappers the Fedora tools tree needed are gone; the linter.yml's own musl build step also goes away since the unit-tests workflow now covers it (and tests it). postmarketOS doesn't ship a downstream systemd packaging spec, so the new tools tree config in mkosi.tools.conf/mkosi.conf.d/postmarketos.conf does not set PrepareScripts and lists build deps manually. mkosi.sync now early-exits when PKG_SUBDIR is unset so the missing pkgenv entry doesn't trip set -u. Co-developed-by: Claude Opus 4.7 --- .github/workflows/linter.yml | 11 -- .github/workflows/unit-tests-musl.sh | 59 --------- .github/workflows/unit-tests-musl.yml | 113 ------------------ .github/workflows/unit-tests.yml | 52 ++++++++ mkosi/mkosi.sync | 6 + .../mkosi.conf.d/postmarketos.conf | 86 +++++++++++++ tools/fetch-mkosi.py | 4 +- 7 files changed, 147 insertions(+), 184 deletions(-) delete mode 100755 .github/workflows/unit-tests-musl.sh delete mode 100644 .github/workflows/unit-tests-musl.yml create mode 100644 mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index edc970de5b5ba..96ed7dec965ed 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -87,14 +87,3 @@ jobs: - name: Run coccinelle checks run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit - - - name: Build with musl - run: | - mkosi box -- \ - env \ - CC=musl-gcc \ - CXX=musl-gcc \ - CFLAGS="-idirafter /usr/include" \ - CXXFLAGS="-idirafter /usr/include" \ - meson setup -Dlibc=musl -Ddbus-interfaces-dir=no musl - mkosi box -- ninja -C musl diff --git a/.github/workflows/unit-tests-musl.sh b/.github/workflows/unit-tests-musl.sh deleted file mode 100755 index 5a9e68710a294..0000000000000 --- a/.github/workflows/unit-tests-musl.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later - -# shellcheck disable=SC2206 -PHASES=(${@:-SETUP BUILD RUN CLEANUP}) - -function info() { - echo -e "\033[33;1m$1\033[0m" -} - -function run_meson() { - if ! meson "$@"; then - find . -type f -name meson-log.txt -exec cat '{}' + - return 1 - fi -} - -set -ex - -for phase in "${PHASES[@]}"; do - case $phase in - SETUP) - info "Setup phase" - - # Alpine still uses split-usr. - for i in /bin/* /sbin/*; do - ln -rs "$i" "/usr/$i"; - done - ;; - BUILD) - info "Build systemd phase" - - run_meson setup --werror -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true -Dlibc=musl build - ninja -v -C build - ;; - RUN) - info "Run phase" - - # Create dummy machine ID. - echo '052e58f661f94bd080e258b96aea3f7b' >/etc/machine-id - - # Start dbus for several unit tests. - mkdir -p /var/run/dbus - /usr/bin/dbus-daemon --system || : - - # Here, we explicitly set SYSTEMD_IN_CHROOT=yes as unfortunately runnin_in_chroot() does not - # correctly detect the environment. - env \ - SYSTEMD_IN_CHROOT=yes \ - meson test -C build --print-errorlogs --no-stdsplit --quiet - ;; - CLEANUP) - info "Cleanup phase" - ;; - *) - echo >&2 "Unknown phase '$phase'" - exit 1 - esac -done diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml deleted file mode 100644 index e2a7908875f37..0000000000000 --- a/.github/workflows/unit-tests-musl.yml +++ /dev/null @@ -1,113 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -name: Unit tests (musl) -on: - pull_request: - paths: - - '**/meson.build' - - '.github/workflows/**' - - 'meson_options.txt' - - 'src/**' - - 'test/fuzz/**' - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Repository checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - - name: Install build dependencies - uses: jirutka/setup-alpine@v1 - with: - arch: x86_64 - branch: edge - packages: > - acl - acl-dev - audit-dev - bash - bash-completion-dev - bpftool - build-base - bzip2-dev - coreutils - cryptsetup-dev - curl-dev - dbus - dbus-dev - elfutils-dev - gettext-dev - git - glib-dev - gnutls-dev - gperf - grep - iproute2 - iptables-dev - kbd - kmod - kmod-dev - libapparmor-dev - libarchive-dev - libbpf-dev - libcap-dev - libcap-utils - libfido2-dev - libgcrypt-dev - libidn2-dev - libmicrohttpd-dev - libpwquality-dev - libqrencode-dev - libseccomp-dev - libselinux-dev - libxkbcommon-dev - linux-pam-dev - lz4-dev - meson - openssl - openssl-dev - p11-kit-dev - pcre2-dev - pkgconf - polkit-dev - py3-elftools - py3-jinja2 - py3-pefile - py3-pytest - py3-lxml - quota-tools - rsync - sfdisk - tpm2-tss-dev - tpm2-tss-esys - tpm2-tss-rc - tpm2-tss-tcti-device - tzdata - util-linux-dev - util-linux-misc - utmps-dev - valgrind-dev - xen-dev - zlib-dev - zstd-dev - - - name: Setup - run: .github/workflows/unit-tests-musl.sh SETUP - shell: alpine.sh --root {0} - - name: Build - run: .github/workflows/unit-tests-musl.sh BUILD - shell: alpine.sh {0} - - name: Run - run: .github/workflows/unit-tests-musl.sh RUN - shell: alpine.sh --root {0} - - name: Cleanup - run: .github/workflows/unit-tests-musl.sh CLEANUP - shell: alpine.sh --root {0} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 799506401781c..af265e33ac44f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -53,3 +53,55 @@ jobs: sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh SETUP - name: Build & test run: sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh RUN_${{ matrix.run_phase }} + + build-musl: + name: Build & test (musl, postmarketOS) + runs-on: ubuntu-24.04 + concurrency: + group: ${{ github.workflow }}-musl-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Repository checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d + + - name: Install apk + # ubuntu-24.04 doesn't package apk, so fetch the official statically-linked binary from + # upstream so mkosi can use it to populate the postmarketOS tools tree. + run: | + sudo curl -fsSL -o /usr/local/bin/apk \ + https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v3.0.6/x86_64/apk.static + echo 'f1489e05bace7d7dd0a687fcd38d50b585ac660af4231668b123649bef3718c4 /usr/local/bin/apk' | sha256sum --check + sudo chmod +x /usr/local/bin/apk + + - name: Build tools tree + run: | + tee mkosi/mkosi.local.conf </dev/null)" == "$GIT_COMMIT" ]]; then exit 0 diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000000..845a85c760465 --- /dev/null +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +# postmarketOS is musl-based natively, so it can host the musl build directly without needing +# the musl-gcc wrapper that the Fedora tools tree uses. We don't run a PrepareScripts to pull +# systemd build deps from a downstream packaging spec — postmarketOS doesn't ship one — so the +# Packages= list below is the canonical set of build deps for the musl CI build in +# .github/workflows/unit-tests.yml (build-musl job). +[Content] +Packages= + acl + acl-dev + audit-dev + bash + bash-completion-dev + bpftool + build-base + bzip2-dev + coccinelle + coreutils + cryptsetup-dev + curl-dev + dbus + dbus-dev + elfutils-dev + gettext-dev + git + github-cli + glib-dev + gnutls-dev + gperf + grep + iproute2 + iptables-dev + kbd + kmod + kmod-dev + lcov + libapparmor-dev + libarchive-dev + libbpf-dev + libcap-dev + libcap-utils + libfido2-dev + libgcrypt-dev + libgpg-error-dev + libidn2-dev + libmicrohttpd-dev + libpwquality-dev + libqrencode-dev + libseccomp-dev + libselinux-dev + libucontext-dev + libxkbcommon-dev + linux-pam-dev + lz4-dev + openssl + openssl-dev + p11-kit-dev + pcre2-dev + pkgconf + polkit-dev + py3-elftools + py3-jinja2 + py3-lxml + py3-pefile + py3-pytest + quota-tools + ruff + rsync + sfdisk + shellcheck + tpm2-tss-dev + tpm2-tss-esys + tpm2-tss-rc + tpm2-tss-tcti-device + tzdata + util-linux-dev + util-linux-misc + utmps-dev + valgrind-dev + xen-dev + zlib-dev + zstd-dev diff --git a/tools/fetch-mkosi.py b/tools/fetch-mkosi.py index 481f607d40fcf..11e0a6aafa73d 100755 --- a/tools/fetch-mkosi.py +++ b/tools/fetch-mkosi.py @@ -15,7 +15,9 @@ URL = 'https://github.com/systemd/mkosi' BRANCH = 'main' # We only want to ever use commits on upstream 'main' branch CONFIG = Path('mkosi/mkosi.conf') -WORKFLOWS = [Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml']] +WORKFLOWS = [ + Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml', 'unit-tests.yml'] +] def parse_args(): From 5904cee941715bf985cfd35dd272a84f9819e87f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 00:54:26 +0900 Subject: [PATCH 2043/2155] include: move several missing definitions to musl Those moved ones have been defined in glibc <= 2.34, and only necessary when built with musl. Follow-up for c8c1bcf1941047d1fe43d9827ad4826b4620297a. --- src/include/musl/signal.h | 28 ++++++++++++++++++++++++++++ src/include/override/signal.h | 24 ------------------------ 2 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 src/include/musl/signal.h diff --git a/src/include/musl/signal.h b/src/include/musl/signal.h new file mode 100644 index 0000000000000..c69a3b285641f --- /dev/null +++ b/src/include/musl/signal.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +#ifndef ILL_BADIADDR +#define ILL_BADIADDR 9 +#endif + +#ifndef FPE_FLTUNK +#define FPE_FLTUNK 14 +#endif + +#ifndef FPE_CONDTRAP +#define FPE_CONDTRAP 15 +#endif + +#ifndef SEGV_ACCADI +#define SEGV_ACCADI 5 +#endif + +#ifndef SEGV_ADIDERR +#define SEGV_ADIDERR 6 +#endif + +#ifndef SEGV_ADIPERR +#define SEGV_ADIPERR 7 +#endif diff --git a/src/include/override/signal.h b/src/include/override/signal.h index 64ddacc4c9dc0..b6a6a9ce31a9f 100644 --- a/src/include/override/signal.h +++ b/src/include/override/signal.h @@ -3,30 +3,6 @@ #include_next /* IWYU pragma: export */ -#ifndef ILL_BADIADDR -#define ILL_BADIADDR 9 -#endif - -#ifndef FPE_FLTUNK -#define FPE_FLTUNK 14 -#endif - -#ifndef FPE_CONDTRAP -#define FPE_CONDTRAP 15 -#endif - -#ifndef SEGV_ACCADI -#define SEGV_ACCADI 5 -#endif - -#ifndef SEGV_ADIDERR -#define SEGV_ADIDERR 6 -#endif - -#ifndef SEGV_ADIPERR -#define SEGV_ADIPERR 7 -#endif - /* Defined since glibc-2.39. */ #ifndef SEGV_CPERR #define SEGV_CPERR 10 From 8c0f9073c7da808461acfc016cc291e4aba9c1a2 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 20 May 2026 17:15:00 +0100 Subject: [PATCH 2044/2155] updatectl: Add a --no-ask-password argument While commit 83c1e8ff5f9 added support for interactive polkit authentication in `updatectl`, some users might want to disable that for some use cases; so add the standard `--no-ask-password` argument. Signed-off-by: Philip Withnall Helps: https://github.com/systemd/systemd/issues/37412 --- man/updatectl.xml | 1 + src/sysupdate/updatectl.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/man/updatectl.xml b/man/updatectl.xml index c7b0dbd3096c0..a6da35b52f0ee 100644 --- a/man/updatectl.xml +++ b/man/updatectl.xml @@ -119,6 +119,7 @@ + diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index cf574a86e9063..bafc41c6ae0c7 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -35,6 +35,7 @@ static bool arg_legend = true; static bool arg_reboot = false; static bool arg_offline = false; static bool arg_now = false; +static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; @@ -1723,6 +1724,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_GROUP("Verbs"): {} + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + OPTION_COMMON_HELP: return help(); @@ -1755,7 +1760,7 @@ static int run(int argc, char *argv[]) { if (arg_transport == BUS_TRANSPORT_LOCAL) polkit_agent_open(); - (void) sd_bus_set_allow_interactive_authorization(bus, true); + (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); return dispatch_verb(args, bus); } From f405aa53c3f07f4d144bc88aa549f97e06f12c03 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 01:30:43 +0900 Subject: [PATCH 2045/2155] README: bump required minimum version of musl to 1.2.6 musl v1.2.6 has been released on 2026-03-20: https://git.musl-libc.org/cgit/musl/commit/?id=9fa28ece75d8a2191de7c5bb53bed224c5947417 alpine/postmarketos already have musl v1.2.6 in the edge repository: https://pkgs.alpinelinux.org/package/edge/main/x86_64/musl Also, Yocto Project 6.0 (wrynose) already has v1.2.6. Let's bump the requirement now. --- README | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README b/README index 1cae26ba3a5f6..a198637d133ec 100644 --- a/README +++ b/README @@ -205,8 +205,7 @@ REQUIREMENTS: CONFIG_MEMCG glibc >= 2.34 - musl >= 1.2.5 with fde29c04adbab9d5b081bf6717b5458188647f1c - (required when building systemd with -Dlibc=musl) + musl >= 1.2.6 (required when building systemd with -Dlibc=musl) libxcrypt >= 4.4.0 (optional) libmount >= 2.30 (from util-linux) (util-linux *must* be built without --enable-libmount-support-mtab) From bc40f6cc328803cd512569dc2d9a1ceea7c84396 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 01:08:24 +0900 Subject: [PATCH 2046/2155] musl: drop renameat2() wrapper musl provides renameat2() since v1.2.6: https://git.musl-libc.org/cgit/musl/commit/?id=05ce67fea99ca09cd4b6625cff7aec9cc222dd5a --- src/include/musl/stdio.h | 9 --------- src/libc/musl/stdio.c | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/include/musl/stdio.h b/src/include/musl/stdio.h index 54ad9ed110432..d6a3b54027a3d 100644 --- a/src/include/musl/stdio.h +++ b/src/include/musl/stdio.h @@ -3,15 +3,6 @@ #include_next -#ifndef RENAME_NOREPLACE -# define RENAME_NOREPLACE (1 << 0) -# define RENAME_EXCHANGE (1 << 1) -# define RENAME_WHITEOUT (1 << 2) -#endif - -int renameat2_shim(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags); -#define renameat2 renameat2_shim - /* When a stream is opened read-only under glibc, fputs() and friends fail with EBADF. However, they * succeed under musl. We rely on the glibc behavior in the code base. The following _check_writable() * functions first check if the passed stream is writable, and refuse to write with EBADF if not. */ diff --git a/src/libc/musl/stdio.c b/src/libc/musl/stdio.c index f4e8d9e4c18e7..cfe35b09f9908 100644 --- a/src/libc/musl/stdio.c +++ b/src/libc/musl/stdio.c @@ -4,15 +4,6 @@ #include #include -#include "../libc-shim.h" - -DEFINE_SYSCALL_SHIM(renameat2, int, - int, __oldfd, - const char *, __old, - int, __newfd, - const char *, __new, - unsigned, __flags) - #define DEFINE_PUT(func) \ int func##_check_writable(int c, FILE *stream) { \ if (!__fwritable(stream)) { \ From 12b9760800852a2b773586fba7531ff60cff85bc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 01:14:59 +0900 Subject: [PATCH 2047/2155] musl: drop several statx definitions Those dropped definitions have been provided since musl v1.2.6. --- src/include/musl/sys/stat.h | 55 ------------------------------------- 1 file changed, 55 deletions(-) diff --git a/src/include/musl/sys/stat.h b/src/include/musl/sys/stat.h index 610dd3e69958a..385e16f3f8757 100644 --- a/src/include/musl/sys/stat.h +++ b/src/include/musl/sys/stat.h @@ -3,64 +3,9 @@ #include_next -#include -#include - /* musl's sys/stat.h does not include linux/stat.h, and unfortunately they conflict with each other. * Hence, some relatively new macros need to be explicitly defined here. */ -/* Before 23ab04a8630225371455d5f4538fd078665bb646, statx.stx_mnt_id is not defined. */ -#ifndef STATX_MNT_ID -static_assert(offsetof(struct statx, __pad1) == offsetof(struct statx, stx_dev_minor) + sizeof(uint32_t), ""); -#define stx_mnt_id __pad1[0] -#endif - -#ifndef STATX_MNT_ID -#define STATX_MNT_ID 0x00001000U -#endif -#ifndef STATX_DIOALIGN -#define STATX_DIOALIGN 0x00002000U -#endif -#ifndef STATX_MNT_ID_UNIQUE -#define STATX_MNT_ID_UNIQUE 0x00004000U -#endif -#ifndef STATX_SUBVOL -#define STATX_SUBVOL 0x00008000U -#endif -#ifndef STATX_WRITE_ATOMIC -#define STATX_WRITE_ATOMIC 0x00010000U -#endif #ifndef STATX_DIO_READ_ALIGN #define STATX_DIO_READ_ALIGN 0x00020000U #endif - -#ifndef STATX_ATTR_COMPRESSED -#define STATX_ATTR_COMPRESSED 0x00000004 -#endif -#ifndef STATX_ATTR_IMMUTABLE -#define STATX_ATTR_IMMUTABLE 0x00000010 -#endif -#ifndef STATX_ATTR_APPEND -#define STATX_ATTR_APPEND 0x00000020 -#endif -#ifndef STATX_ATTR_NODUMP -#define STATX_ATTR_NODUMP 0x00000040 -#endif -#ifndef STATX_ATTR_ENCRYPTED -#define STATX_ATTR_ENCRYPTED 0x00000800 -#endif -#ifndef STATX_ATTR_AUTOMOUNT -#define STATX_ATTR_AUTOMOUNT 0x00001000 -#endif -#ifndef STATX_ATTR_MOUNT_ROOT -#define STATX_ATTR_MOUNT_ROOT 0x00002000 -#endif -#ifndef STATX_ATTR_VERITY -#define STATX_ATTR_VERITY 0x00100000 -#endif -#ifndef STATX_ATTR_DAX -#define STATX_ATTR_DAX 0x00200000 -#endif -#ifndef STATX_ATTR_WRITE_ATOMIC -#define STATX_ATTR_WRITE_ATOMIC 0x00400000 -#endif From 6a35ae68cb075c88bbfc6d970e29f6464f951688 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 04:07:36 +0900 Subject: [PATCH 2048/2155] NEWS: mention bump of musl requirement --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 05c7fef47c34a..386b803cf6b03 100644 --- a/NEWS +++ b/NEWS @@ -73,6 +73,9 @@ CHANGES WITH 261 in spe: effectively means live upgrades from versions older than v247 are not supported anymore. + * Required version of musl (when built with -Dlibc=musl) has been raised + from 1.2.5 to 1.2.6. + Changes in the system and service manager: * PID1 now supports the kernel's Live Update Orchestration (LUO) / From 1a0b17df32c7f8de692f7d30a753b7753f8eb0f8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:45:57 +0200 Subject: [PATCH 2049/2155] vmspawn: move image related knobs close to --image= (Also, arrange the order if the image options group in the man page to the same as in the --help text) --- man/systemd-vmspawn.xml | 56 ++++++++++++++++++++--------------------- src/vmspawn/vmspawn.c | 34 ++++++++++++------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 20be0c099b16d..2c07bad93c2dc 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -104,6 +104,18 @@ + + + + + If specified, the VM is run with a temporary snapshot of its file system that is removed + immediately when the VM terminates. Only works with currently. + + Note that will not work with . + + + + @@ -139,15 +151,25 @@ - - + - If specified, the VM is run with a temporary snapshot of its file system that is removed - immediately when the VM terminates. Only works with currently. + Controls whether qemu processes discard requests from the VM. + This prevents long running VMs from using more disk space than required. + This is enabled by default. - Note that will not work with . + + - + + + + + Grows the image file specified by to the specified size + in bytes if it is smaller. Executes no operation if no image file is used or the image file is + already as large or larger than requested. The specified size accepts the usual K, M, G suffixes + (to the base of 1024). Specified values are rounded up to multiples of 4096. + + @@ -363,16 +385,6 @@ - - - - Controls whether qemu processes discard requests from the VM. - This prevents long running VMs from using more disk space than required. - This is enabled by default. - - - - @@ -409,18 +421,6 @@ - - - - - Grows the image file specified by to the specified size - in bytes if it is smaller. Executes no operation if no image file is used or the image file is - already as large or larger than requested. The specified size accepts the usual K, M, G suffixes - (to the base of 1024). Specified values are rounded up to multiples of 4096. - - - - diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 6cb77dfadd920..4ced5d6896a76 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -394,6 +394,23 @@ static int parse_argv(int argc, char *argv[]) { "Invalid image disk type: %s", opts.arg); break; + OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): + r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk); + if (r < 0) + return r; + break; + + OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): + if (isempty(opts.arg)) { + arg_grow_image = 0; + break; + } + + r = parse_size(opts.arg, 1024, &arg_grow_image); + if (r < 0) + return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg); + break; + OPTION_GROUP("Host Configuration"): {} OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {} @@ -646,23 +663,6 @@ static int parse_argv(int argc, char *argv[]) { break; } - OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): - r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk); - if (r < 0) - return r; - break; - - OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): - if (isempty(opts.arg)) { - arg_grow_image = 0; - break; - } - - r = parse_size(opts.arg, 1024, &arg_grow_image); - if (r < 0) - return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg); - break; - OPTION_GROUP("Execution"): {} OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): From f9a40f7aa310ac974831560d5c8e3e54a2785ed4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:48:44 +0200 Subject: [PATCH 2050/2155] vmspawn: split 'Integration' section into 'Logging' and 'SSH' Each of the two has multiple options, hence let's split them out. --- man/systemd-vmspawn.xml | 9 ++++++++- src/vmspawn/vmspawn.c | 13 +++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 2c07bad93c2dc..f31d1a3df2731 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -708,7 +708,7 @@ - Integration Options + Logging Options @@ -740,6 +740,13 @@ + + + + + SSH Options + + diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 4ced5d6896a76..0c3c6a8109f1b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -237,7 +237,8 @@ static int help(void) { "Properties", "User Namespacing", "Mounts", - "Integration", + "Logging", + "SSH", "Input/Output", "Credentials", }; @@ -251,8 +252,10 @@ static int help(void) { 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]); + (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]); help_cmdline("[OPTIONS...] [ARGUMENTS...]"); help_abstract("Spawn a command or OS in a virtual machine."); @@ -839,7 +842,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; - OPTION_GROUP("Integration"): {} + OPTION_GROUP("Logging"): {} OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"): r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_forward_journal); @@ -871,6 +874,8 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", opts.arg); break; + OPTION_GROUP("SSH"): {} + OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): r = parse_boolean_argument("--pass-ssh-key=", opts.arg, &arg_pass_ssh_key); if (r < 0) From 183ec272eff1b86aa76948f83dacc8e0cd32386b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:50:32 +0200 Subject: [PATCH 2051/2155] vmspawn: split out networking related options into its own "Networking" section in --help --- man/systemd-vmspawn.xml | 60 ++++++++++++++++++++++------------------- src/vmspawn/vmspawn.c | 21 ++++++++------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index f31d1a3df2731..c70b76c4980b8 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -322,33 +322,6 @@ - - - - - - Create a TAP device to network with the virtual machine. - - Note: root privileges are required to use TAP networking. - Additionally, - systemd-networkd8 - must be running and correctly set up on the host to provision the host interface. The relevant - .network file can be found at - /usr/lib/systemd/network/80-vm-vt.network. - - - - - - - - - - Use user mode networking. - - - - @@ -454,6 +427,39 @@ + + Networking Options + + + + + + + + Create a TAP device to network with the virtual machine. + + Note: root privileges are required to use TAP networking. + Additionally, + systemd-networkd8 + must be running and correctly set up on the host to provision the host interface. The relevant + .network file can be found at + /usr/lib/systemd/network/80-vm-vt.network. + + + + + + + + + + Use user mode networking. + + + + + + System Identity Options diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 0c3c6a8109f1b..cc26309317b95 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -232,6 +232,7 @@ static int help(void) { NULL, "Image", "Host Configuration", + "Networking", "Execution", "System Identity", "Properties", @@ -255,7 +256,7 @@ static int help(void) { (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[11], tables[12]); help_cmdline("[OPTIONS...] [ARGUMENTS...]"); help_abstract("Spawn a command or OS in a virtual machine."); @@ -540,14 +541,6 @@ static int parse_argv(int argc, char *argv[]) { break; } - 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; - OPTION_LONG("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { int b; @@ -666,6 +659,16 @@ static int parse_argv(int argc, char *argv[]) { break; } + OPTION_GROUP("Networking"): {} + + 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; + OPTION_GROUP("Execution"): {} OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): From b12a3658663e7d6642b6c227e41bb6377ec1a498 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:52:57 +0200 Subject: [PATCH 2052/2155] vmspawn: move --kernel=/--initrd= under the "Execution" section This has little to do with host configuration (where it was so far), and a lot with what being executed, let's move it over. Note that --help and man page so far differed here quite a bit: the former had the "Execution" section, the latter didn't. This creates it in the man page, to bring the two back in sync. --- man/systemd-vmspawn.xml | 130 +++++++++++++++++++++------------------- src/vmspawn/vmspawn.c | 36 +++++------ 2 files changed, 86 insertions(+), 80 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index c70b76c4980b8..74feca4050870 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -291,37 +291,6 @@ - - - - - Set the linux kernel image to use for direct kernel boot. - If a directory type image is used and was omitted, vmspawn will search for boot loader entries - according to the - UAPI.1 Boot Loader Specification assuming - XBOOTLDR to be located at /boot and ESP to be /efi respectively. - If no kernel was installed into the image then the image will fail to boot. - - - - - - - - - - Set the initrd to use for direct kernel boot. - If the supplied is a - UAPI.1 Boot Loader Specification - Type #2 entry, then this argument is not required. - If no initrd was installed into the image then the image will fail to boot. - - can be specified multiple times and vmspawn will merge them together. - - - - - @@ -393,37 +362,6 @@ - - - - - - Passes the specified string into the VM as an SMBIOS Type #11 vendor string. This - is useful to parameterize the invoked VM in various ways. See - smbios-type-117 - for details. - - - - - - - - Configures support for notifications from the VM's init process to - systemd-vmspawn. If true, systemd-vmspawn will consider the - machine as ready only when it has received a READY=1 message from the init - process in the VM. If false, systemd-vmspawn will consider the machine as ready - immediately after creation. In either case, systemd-vmspawn sends its own - readiness notification to its manager after the spawned VM is ready. For more details about - notifications see - sd_notify3. - - Defaults to true. (Note that this is unlike the option of the same name to - systemd-nspawn1 - that defaults to false.) - - - @@ -460,6 +398,74 @@ + + Execution Options + + + + + + + Set the linux kernel image to use for direct kernel boot. + If a directory type image is used and was omitted, vmspawn will search for boot loader entries + according to the + UAPI.1 Boot Loader Specification assuming + XBOOTLDR to be located at /boot and ESP to be /efi respectively. + If no kernel was installed into the image then the image will fail to boot. + + + + + + + + + + Set the initrd to use for direct kernel boot. + If the supplied is a + UAPI.1 Boot Loader Specification + Type #2 entry, then this argument is not required. + If no initrd was installed into the image then the image will fail to boot. + + can be specified multiple times and vmspawn will merge them together. + + + + + + + + + + Passes the specified string into the VM as an SMBIOS Type #11 vendor string. This + is useful to parameterize the invoked VM in various ways. See + smbios-type-117 + for details. + + + + + + + + Configures support for notifications from the VM's init process to + systemd-vmspawn. If true, systemd-vmspawn will consider the + machine as ready only when it has received a READY=1 message from the init + process in the VM. If false, systemd-vmspawn will consider the machine as ready + immediately after creation. In either case, systemd-vmspawn sends its own + readiness notification to its manager after the spawned VM is ready. For more details about + notifications see + sd_notify3. + + Defaults to true. (Note that this is unlike the option of the same name to + systemd-nspawn1 + that defaults to false.) + + + + + + System Identity Options diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index cc26309317b95..0797676bf94bd 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -523,24 +523,6 @@ static int parse_argv(int argc, char *argv[]) { arg_efi_nvram_state_mode = STATE_PATH; break; - OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): - r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_linux); - if (r < 0) - return r; - break; - - OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): { - _cleanup_free_ char *initrd_path = NULL; - r = parse_path_argument(opts.arg, /* 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("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { int b; @@ -671,6 +653,24 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Execution"): {} + OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_linux); + if (r < 0) + return r; + break; + + OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): { + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(opts.arg, /* 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('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): if (isempty(opts.arg)) { arg_smbios11 = strv_free(arg_smbios11); From 95fe2d07ed9e080e63f7e9d10531ac21429b8315 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 16:23:36 +0200 Subject: [PATCH 2053/2155] man: order --secure-boot= to the same location in man page as in --help for vmspawn --- man/systemd-vmspawn.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 74feca4050870..3a84196b627db 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -291,6 +291,18 @@ + + + + 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. + + + + @@ -327,18 +339,6 @@ - - - - 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. - - - - From 8b675d315badd677778ac59bf84d47610714d8b5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 20 May 2026 21:18:35 +0100 Subject: [PATCH 2054/2155] mkosi: update debian commit reference to 3e1930512d1efee7e11b619a5f493b3229594a51 * 3e1930512d Downgrade dependency on dbus to recommends in sd-container * 61d6ecf0a0 Conflict with sysuser-helper * fbc4646437 autopkgtest: add dependency on procps --- 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 8449b296c2f97..73bcf958d69f0 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=8b9ea8981eee267a2fa493435f2869f7b2479350 + GIT_COMMIT=3e1930512d1efee7e11b619a5f493b3229594a51 PKG_SUBDIR=debian From 52421acf17adb40eed968c845e7d29d74f777b10 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 04:33:01 +0900 Subject: [PATCH 2055/2155] test-network: use networkctl_status() helper function at one more place --- test/test-network/systemd-networkd-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index fabb76fcbf200..9a8b38a88fa89 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -8153,7 +8153,7 @@ def test_dhcp_server(self): self.wait_online('veth-peer:routable') for _ in range(10): - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env) + output = networkctl_status('veth-peer') if 'Offered DHCP leases: 192.168.5.' in output: break time.sleep(0.2) From ba13cd1245d5ed60cc393e02bb21e5f78e40ab81 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 04:57:21 +0900 Subject: [PATCH 2056/2155] include: update kernel headers from v7.1-rc4 It seems there is no notable changes to us. --- src/include/uapi/linux/bpf.h | 4 +- src/include/uapi/linux/btrfs_tree.h | 3 +- src/include/uapi/linux/const.h | 18 ++ src/include/uapi/linux/ethtool.h | 2 +- src/include/uapi/linux/fs.h | 12 + src/include/uapi/linux/if_link.h | 47 ++++ src/include/uapi/linux/input-event-codes.h | 4 + src/include/uapi/linux/liveupdate.h | 6 +- src/include/uapi/linux/mount.h | 1 + src/include/uapi/linux/netfilter/nf_tables.h | 3 +- src/include/uapi/linux/nl80211.h | 272 ++++++++++++++++++- src/include/uapi/linux/nsfs.h | 6 - src/include/uapi/linux/prctl.h | 37 +-- 13 files changed, 377 insertions(+), 38 deletions(-) diff --git a/src/include/uapi/linux/bpf.h b/src/include/uapi/linux/bpf.h index 14c80da5df87f..2ae9d05cb4e15 100644 --- a/src/include/uapi/linux/bpf.h +++ b/src/include/uapi/linux/bpf.h @@ -4645,7 +4645,9 @@ union bpf_attr { * Description * Discard reserved ring buffer sample, pointed to by *data*. * If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification - * of new data availability is sent. + * of new data availability is sent. Discarded records remain in + * the ring buffer until consumed by user space, so a later submit + * using adaptive wakeup might not wake up the consumer. * If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification * of new data availability is sent unconditionally. * If **0** is specified in *flags*, an adaptive notification diff --git a/src/include/uapi/linux/btrfs_tree.h b/src/include/uapi/linux/btrfs_tree.h index 0507f9309523b..e7b75f9206ecd 100644 --- a/src/include/uapi/linux/btrfs_tree.h +++ b/src/include/uapi/linux/btrfs_tree.h @@ -1241,7 +1241,8 @@ struct btrfs_free_space_info { __le32 flags; } __attribute__ ((__packed__)); -#define BTRFS_FREE_SPACE_USING_BITMAPS (1ULL << 0) +#define BTRFS_FREE_SPACE_USING_BITMAPS (1UL << 0) +#define BTRFS_FREE_SPACE_FLAGS_MASK (BTRFS_FREE_SPACE_USING_BITMAPS) #define BTRFS_QGROUP_LEVEL_SHIFT 48 static __inline__ __u16 btrfs_qgroup_level(__u64 qgroupid) diff --git a/src/include/uapi/linux/const.h b/src/include/uapi/linux/const.h index 95ede23342040..c6a9d0c9835ca 100644 --- a/src/include/uapi/linux/const.h +++ b/src/include/uapi/linux/const.h @@ -50,4 +50,22 @@ #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +/* + * Divide positive or negative dividend by positive or negative divisor + * and round to closest integer. Result is undefined for negative + * divisors if the dividend variable type is unsigned and for negative + * dividends if the divisor variable type is unsigned. + */ +#define __KERNEL_DIV_ROUND_CLOSEST(x, divisor) \ +({ \ + __typeof__(x) __x = x; \ + __typeof__(divisor) __d = divisor; \ + \ + (((__typeof__(x))-1) > 0 || \ + ((__typeof__(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +}) + #endif /* _LINUX_CONST_H */ diff --git a/src/include/uapi/linux/ethtool.h b/src/include/uapi/linux/ethtool.h index 5cfcf2b858ee7..fc439ea218f86 100644 --- a/src/include/uapi/linux/ethtool.h +++ b/src/include/uapi/linux/ethtool.h @@ -225,7 +225,7 @@ enum tunable_id { ETHTOOL_ID_UNSPEC, ETHTOOL_RX_COPYBREAK, ETHTOOL_TX_COPYBREAK, - ETHTOOL_PFC_PREVENTION_TOUT, /* timeout in msecs */ + ETHTOOL_PFC_PREVENTION_TOUT, /* both pause and pfc, see man ethtool */ ETHTOOL_TX_COPYBREAK_BUF_SIZE, /* * Add your fresh new tunable attribute above and remember to update diff --git a/src/include/uapi/linux/fs.h b/src/include/uapi/linux/fs.h index 19d04b50e6625..6e9a44969f229 100644 --- a/src/include/uapi/linux/fs.h +++ b/src/include/uapi/linux/fs.h @@ -653,4 +653,16 @@ struct procmap_query { __u64 build_id_addr; /* in */ }; +/* + * Shutdown the filesystem. + */ +#define FS_IOC_SHUTDOWN _IOR('X', 125, __u32) + +/* + * Flags for FS_IOC_SHUTDOWN + */ +#define FS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#define FS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 /* flush log but not data*/ +#define FS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 /* don't flush log nor data */ + #endif /* _LINUX_FS_H */ diff --git a/src/include/uapi/linux/if_link.h b/src/include/uapi/linux/if_link.h index 2037afbc4b330..a8c64be26f2ca 100644 --- a/src/include/uapi/linux/if_link.h +++ b/src/include/uapi/linux/if_link.h @@ -742,6 +742,11 @@ enum in6_addr_gen_mode { * @IFLA_BR_FDB_MAX_LEARNED * Set the number of max dynamically learned FDB entries for the current * bridge. + * + * @IFLA_BR_STP_MODE + * Set the STP mode for the bridge, which controls how the bridge + * selects between userspace and kernel STP. The valid values are + * documented below in the ``BR_STP_MODE_*`` constants. */ enum { IFLA_BR_UNSPEC, @@ -794,11 +799,45 @@ enum { IFLA_BR_MCAST_QUERIER_STATE, IFLA_BR_FDB_N_LEARNED, IFLA_BR_FDB_MAX_LEARNED, + IFLA_BR_STP_MODE, __IFLA_BR_MAX, }; #define IFLA_BR_MAX (__IFLA_BR_MAX - 1) +/** + * DOC: Bridge STP mode values + * + * @BR_STP_MODE_AUTO + * Default. The kernel invokes the ``/sbin/bridge-stp`` helper to hand + * the bridge to a userspace STP daemon (e.g. mstpd). Only attempted in + * the initial network namespace; in other namespaces this falls back to + * kernel STP. + * + * @BR_STP_MODE_USER + * Directly enable userspace STP (``BR_USER_STP``) without invoking the + * ``/sbin/bridge-stp`` helper. Works in any network namespace. + * Userspace is responsible for ensuring an STP daemon manages the + * bridge. + * + * @BR_STP_MODE_KERNEL + * Directly enable kernel STP (``BR_KERNEL_STP``) without invoking the + * helper. + * + * The mode controls how the bridge selects between userspace and kernel + * STP when STP is enabled via ``IFLA_BR_STP_STATE``. It can only be + * changed while STP is disabled (``IFLA_BR_STP_STATE`` == 0), returns + * ``-EBUSY`` otherwise. The default value is ``BR_STP_MODE_AUTO``. + */ +enum br_stp_mode { + BR_STP_MODE_AUTO, + BR_STP_MODE_USER, + BR_STP_MODE_KERNEL, + __BR_STP_MODE_MAX +}; + +#define BR_STP_MODE_MAX (__BR_STP_MODE_MAX - 1) + struct ifla_bridge_id { __u8 prio[2]; __u8 addr[6]; /* ETH_ALEN */ @@ -1294,6 +1333,11 @@ enum netkit_mode { NETKIT_L3, }; +enum netkit_pairing { + NETKIT_DEVICE_PAIR, + NETKIT_DEVICE_SINGLE, +}; + /* NETKIT_SCRUB_NONE leaves clearing skb->{mark,priority} up to * the BPF program if attached. This also means the latter can * consume the two fields if they were populated earlier. @@ -1318,6 +1362,7 @@ enum { IFLA_NETKIT_PEER_SCRUB, IFLA_NETKIT_HEADROOM, IFLA_NETKIT_TAILROOM, + IFLA_NETKIT_PAIRING, __IFLA_NETKIT_MAX, }; #define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1) @@ -1566,6 +1611,8 @@ enum { IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, IFLA_BOND_SLAVE_PRIO, IFLA_BOND_SLAVE_ACTOR_PORT_PRIO, + IFLA_BOND_SLAVE_AD_CHURN_ACTOR_STATE, + IFLA_BOND_SLAVE_AD_CHURN_PARTNER_STATE, __IFLA_BOND_SLAVE_MAX, }; diff --git a/src/include/uapi/linux/input-event-codes.h b/src/include/uapi/linux/input-event-codes.h index 32bc46b55f611..593fb257ce54b 100644 --- a/src/include/uapi/linux/input-event-codes.h +++ b/src/include/uapi/linux/input-event-codes.h @@ -643,6 +643,10 @@ #define KEY_EPRIVACY_SCREEN_ON 0x252 #define KEY_EPRIVACY_SCREEN_OFF 0x253 +#define KEY_ACTION_ON_SELECTION 0x254 /* AL Action on Selection (HUTRR119) */ +#define KEY_CONTEXTUAL_INSERT 0x255 /* AL Contextual Insertion (HUTRR119) */ +#define KEY_CONTEXTUAL_QUERY 0x256 /* AL Contextual Query (HUTRR119) */ + #define KEY_KBDINPUTASSIST_PREV 0x260 #define KEY_KBDINPUTASSIST_NEXT 0x261 #define KEY_KBDINPUTASSIST_PREVGROUP 0x262 diff --git a/src/include/uapi/linux/liveupdate.h b/src/include/uapi/linux/liveupdate.h index 30bc66ee9436a..bef962adbf3ef 100644 --- a/src/include/uapi/linux/liveupdate.h +++ b/src/include/uapi/linux/liveupdate.h @@ -8,8 +8,8 @@ * Pasha Tatashin */ -#ifndef _UAPI_LIVEUPDATE_H -#define _UAPI_LIVEUPDATE_H +#ifndef _LIVEUPDATE_H +#define _LIVEUPDATE_H #include #include @@ -213,4 +213,4 @@ struct liveupdate_session_finish { #define LIVEUPDATE_SESSION_FINISH \ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FINISH) -#endif /* _UAPI_LIVEUPDATE_H */ +#endif /* _LIVEUPDATE_H */ diff --git a/src/include/uapi/linux/mount.h b/src/include/uapi/linux/mount.h index 96f2d20e78d64..719b46f6d5102 100644 --- a/src/include/uapi/linux/mount.h +++ b/src/include/uapi/linux/mount.h @@ -110,6 +110,7 @@ enum fsconfig_command { * fsmount() flags. */ #define FSMOUNT_CLOEXEC 0x00000001 +#define FSMOUNT_NAMESPACE 0x00000002 /* Create the mount in a new mount namespace */ /* * Mount attributes. diff --git a/src/include/uapi/linux/netfilter/nf_tables.h b/src/include/uapi/linux/netfilter/nf_tables.h index 45c71f7d21c25..1b915e32c9990 100644 --- a/src/include/uapi/linux/netfilter/nf_tables.h +++ b/src/include/uapi/linux/netfilter/nf_tables.h @@ -46,6 +46,7 @@ enum nft_registers { }; #define NFT_REG_MAX (__NFT_REG_MAX - 1) + #define NFT_REG_SIZE 16 #define NFT_REG32_SIZE 4 #define NFT_REG32_COUNT (NFT_REG32_15 - NFT_REG32_00 + 1) @@ -884,7 +885,7 @@ enum nft_exthdr_flags { * @NFT_EXTHDR_OP_TCPOPT: match against tcp options * @NFT_EXTHDR_OP_IPV4: match against ipv4 options * @NFT_EXTHDR_OP_SCTP: match against sctp chunks - * @NFT_EXTHDR_OP_DCCP: match against dccp otions + * @NFT_EXTHDR_OP_DCCP: match against dccp options */ enum nft_exthdr_op { NFT_EXTHDR_OP_IPV6, diff --git a/src/include/uapi/linux/nl80211.h b/src/include/uapi/linux/nl80211.h index b63f718509060..3d55bf4be36fe 100644 --- a/src/include/uapi/linux/nl80211.h +++ b/src/include/uapi/linux/nl80211.h @@ -906,8 +906,9 @@ * @NL80211_CMD_UNEXPECTED_FRAME: Used by an application controlling an AP * (or GO) interface (i.e. hostapd) to ask for unexpected frames to * implement sending deauth to stations that send unexpected class 3 - * frames. Also used as the event sent by the kernel when such a frame - * is received. + * frames. For NAN_DATA interfaces, this is used to report frames from + * unknown peers (A2 not assigned to any active NDP). + * Also used as the event sent by the kernel when such a frame is received. * For the event, the %NL80211_ATTR_MAC attribute carries the TA and * other attributes like the interface index are present. * If used as the command it must have an interface index and you can @@ -1361,6 +1362,59 @@ * user space that the NAN new cluster has been joined. The cluster ID is * indicated by %NL80211_ATTR_MAC. * + * @NL80211_CMD_INCUMBENT_SIGNAL_DETECT: Once any incumbent signal is detected + * on the operating channel in 6 GHz band, userspace is notified with the + * signal interference bitmap using + * %NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP. The current channel + * definition is also sent. + * + * @NL80211_CMD_NAN_SET_LOCAL_SCHED: Set the local NAN schedule. NAN must be + * operational (%NL80211_CMD_START_NAN was executed). Must contain + * %NL80211_ATTR_NAN_TIME_SLOTS and %NL80211_ATTR_NAN_AVAIL_BLOB, but + * %NL80211_ATTR_NAN_CHANNEL is optional (for example in case of a channel + * removal, that channel won't be provided). + * If %NL80211_ATTR_NAN_SCHED_DEFERRED is set, the command is a request + * from the device to perform an announced schedule update. See + * %NL80211_ATTR_NAN_SCHED_DEFERRED for more details. + * If not set, the schedule should be applied immediately. + * @NL80211_CMD_NAN_SCHED_UPDATE_DONE: Event sent to user space to notify that + * a deferred local NAN schedule update (requested with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED and %NL80211_ATTR_NAN_SCHED_DEFERRED) + * has been completed. The presence of %NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS + * indicates that the update was successful. + * @NL80211_CMD_NAN_SET_PEER_SCHED: Set the peer NAN schedule. NAN + * must be operational (%NL80211_CMD_START_NAN was executed). + * Required attributes: %NL80211_ATTR_MAC (peer NMI address) and + * %NL80211_ATTR_NAN_COMMITTED_DW. + * Optionally, the full schedule can be provided by including all of: + * %NL80211_ATTR_NAN_SEQ_ID, %NL80211_ATTR_NAN_CHANNEL (one or more), and + * %NL80211_ATTR_NAN_PEER_MAPS (see &enum nl80211_nan_peer_map_attrs). + * If any of these three optional attributes is provided, all three must + * be provided. + * Each peer channel must be compatible with at least one local channel + * set by %NL80211_CMD_SET_LOCAL_NAN_SCHED. Different maps must not + * contain compatible channels. + * For single-radio devices (n_radio <= 1), different maps must not + * schedule the same time slot, as the device cannot operate on multiple + * channels simultaneously. + * When updating an existing peer schedule, the full new schedule must be + * provided - partial updates are not supported. The new schedule will + * completely replace the previous one. + * The peer schedule is automatically removed when the NMI station is + * removed. + * @NL80211_CMD_NAN_ULW_UPDATE: Notification from the driver to user space + * with the updated ULW blob of the device. User space can use this blob + * to attach to frames sent to peers. This notification contains + * %NL80211_ATTR_NAN_ULW with the ULW blob. + * @NL80211_CMD_NAN_CHANNEL_EVAC: Notification to indicate that a NAN + * channel has been evacuated due to resource conflicts with other + * interfaces. This can happen when another interface sharing the channel + * resource with NAN needs to move to a different channel (e.g., channel + * switch or link switch on a BSS interface). + * The notification contains %NL80211_ATTR_NAN_CHANNEL attribute + * identifying the evacuated channel. + * User space may reconfigure the local schedule in response to this + * notification. * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1624,6 +1678,18 @@ enum nl80211_commands { NL80211_CMD_NAN_NEXT_DW_NOTIFICATION, NL80211_CMD_NAN_CLUSTER_JOINED, + NL80211_CMD_INCUMBENT_SIGNAL_DETECT, + + NL80211_CMD_NAN_SET_LOCAL_SCHED, + + NL80211_CMD_NAN_SCHED_UPDATE_DONE, + + NL80211_CMD_NAN_SET_PEER_SCHED, + + NL80211_CMD_NAN_ULW_UPDATE, + + NL80211_CMD_NAN_CHANNEL_EVAC, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -2651,7 +2717,8 @@ enum nl80211_commands { * a flow is assigned on each round of the DRR scheduler. * @NL80211_ATTR_HE_CAPABILITY: HE Capability information element (from * association request when used with NL80211_CMD_NEW_STATION). Can be set - * only if %NL80211_STA_FLAG_WME is set. + * only if %NL80211_STA_FLAG_WME is set (except for NAN, which uses WME + * anyway). * * @NL80211_ATTR_FTM_RESPONDER: nested attribute which user-space can include * in %NL80211_CMD_START_AP or %NL80211_CMD_SET_BEACON for fine timing @@ -2983,6 +3050,95 @@ enum nl80211_commands { * @NL80211_ATTR_DISABLE_UHR: Force UHR capable interfaces to disable * this feature during association. This is a flag attribute. * Currently only supported in mac80211 drivers. + * @NL80211_ATTR_NAN_CHANNEL: This is a nested attribute. There can be multiple + * attributes of this type, each one represents a channel definition and + * consists of top-level attributes like %NL80211_ATTR_WIPHY_FREQ. + * When used with %NL80211_CMD_NAN_SET_LOCAL_SCHED, it specifies + * the channel definitions on which the radio needs to operate during + * specific time slots. All of the channel definitions should be mutually + * incompatible. With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and + * %NL80211_ATTR_NAN_RX_NSS are mandatory. + * When used with %NL80211_CMD_NAN_SET_PEER_SCHED, it configures the + * peer NAN channels. In that case, the channel definitions can be + * compatible to each other, or even identical just with different RX NSS. + * With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and + * %NL80211_ATTR_NAN_RX_NSS are mandatory. + * The number of channels should fit the current configuration of channels + * and the possible interface combinations. + * If an existing NAN channel is changed but the chandef isn't, the + * channel entry must also remain unchanged. + * When used with %NL80211_CMD_NAN_CHANNEL_EVAC, this identifies the + * channels that were evacuated. + * @NL80211_ATTR_NAN_CHANNEL_ENTRY: a byte array of 6 bytes. contains the + * Channel Entry as defined in Wi-Fi Aware (TM) 4.0 specification Table + * 100 (Channel Entry format for the NAN Availability attribute). + * @NL80211_ATTR_NAN_RX_NSS: (u8) RX NSS used for a NAN channel. This is + * used with %NL80211_ATTR_NAN_CHANNEL when configuring NAN channels with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED or %NL80211_CMD_NAN_SET_PEER_SCHED. + * @NL80211_ATTR_NAN_TIME_SLOTS: an array of u8 values and 32 cells. each value + * maps a time slot to the chandef on which the radio should operate on in + * that time. %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled. + * The chandef is represented using its index, where the index is the + * sequential number of the %NL80211_ATTR_NAN_CHANNEL attribute within all + * the attributes of this type. + * Each slots spans over 16TUs, hence the entire schedule spans over + * 512TUs. Other slot durations and periods are currently not supported. + * @NL80211_ATTR_NAN_AVAIL_BLOB: (Binary) The NAN Availability attribute blob, + * including the attribute header, as defined in Wi-Fi Aware (TM) 4.0 + * specification Table 93 (NAN Availability attribute format). Required with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED to provide the raw NAN Availability + * attribute. Used by the device to publish Schedule Update NAFs. + * @NL80211_ATTR_NAN_SCHED_DEFERRED: Flag attribute used with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED. When present, the command is a + * request from the device to perform an announced schedule update. This + * means that it needs to send the updated NAN availability to the peers, + * and do the actual switch on the right time (i.e. at the end of the slot + * after the slot in which the updated NAN Availability was sent). Since + * the slots management is done in the device, the update to the peers + * needs to be sent by the device, so it knows the actual switch time. + * If the flag is not set, the schedule should be applied immediately. + * When this flag is set, the total number of NAN channels from both the + * old and new schedules must not exceed the allowed number of local NAN + * channels, because with deferred scheduling the old channels cannot be + * removed before adding the new ones to free up space. + * @NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS: flag attribute used with + * %NL80211_CMD_NAN_SCHED_UPDATE_DONE to indicate that the deferred + * schedule update completed successfully. If this flag is not present, + * the update failed. + * @NL80211_ATTR_NAN_NMI_MAC: The address of the NMI station to which this NDI + * station belongs. Used with %NL80211_CMD_NEW_STATION when adding an NDI + * station. + * @NL80211_ATTR_NAN_ULW: (Binary) The initial ULW(s) as published by the + * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 109 + * (Unaligned Schedule attribute format). Used to configure the device + * with the initial ULW(s) of a peer, before the device starts tracking it. + * @NL80211_ATTR_NAN_COMMITTED_DW: (u16) The committed DW as published by the + * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 80 + * (Committed DW Information field format). + * @NL80211_ATTR_NAN_SEQ_ID: (u8) The sequence ID of the peer schedule that + * %NL80211_CMD_NAN_SET_PEER_SCHED defines. The device follows the + * sequence ID in the frames to identify newer schedules. Once a schedule + * with a higher sequence ID is received, the device may stop communicating + * with that peer until a new peer schedule with a matching sequence ID is + * received. + * @NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME: (u16) The maximum channel switch + * time, in microseconds. + * @NL80211_ATTR_NAN_PEER_MAPS: Nested array of peer schedule maps. + * Used with %NL80211_CMD_NAN_SET_PEER_SCHED. Contains up to 2 entries, + * each containing nested attributes from &enum nl80211_nan_peer_map_attrs. + * + * @NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP: u32 attribute specifying + * the signal interference bitmap detected on the operating bandwidth for + * %NL80211_CMD_INCUMBENT_SIGNAL_DETECT. Each bit represents a 20 MHz + * segment, lowest bit corresponds to the lowest 20 MHz segment, in the + * operating bandwidth where the interference is detected. Punctured + * sub-channels are included in the bitmap structure; however, since + * interference detection is not performed on these sub-channels, their + * corresponding bits are consistently set to zero. + * + * @NL80211_ATTR_UHR_OPERATION: Full UHR Operation element, as it appears in + * association response etc., since it's abridged in the beacon. Used + * for START_AP etc. * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined @@ -3557,6 +3713,26 @@ enum nl80211_attrs { NL80211_ATTR_UHR_CAPABILITY, NL80211_ATTR_DISABLE_UHR, + NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP, + + NL80211_ATTR_UHR_OPERATION, + + NL80211_ATTR_NAN_CHANNEL, + NL80211_ATTR_NAN_CHANNEL_ENTRY, + NL80211_ATTR_NAN_TIME_SLOTS, + NL80211_ATTR_NAN_RX_NSS, + NL80211_ATTR_NAN_AVAIL_BLOB, + NL80211_ATTR_NAN_SCHED_DEFERRED, + NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS, + + NL80211_ATTR_NAN_NMI_MAC, + + NL80211_ATTR_NAN_ULW, + NL80211_ATTR_NAN_COMMITTED_DW, + NL80211_ATTR_NAN_SEQ_ID, + NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME, + NL80211_ATTR_NAN_PEER_MAPS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3650,6 +3826,9 @@ enum nl80211_attrs { * @NL80211_IFTYPE_OCB: Outside Context of a BSS * This mode corresponds to the MIB variable dot11OCBActivated=true * @NL80211_IFTYPE_NAN: NAN device interface type (not a netdev) + * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data + * interfaces can only be brought up (IFF_UP) when a NAN interface + * already exists and NAN has been started (using %NL80211_CMD_START_NAN). * @NL80211_IFTYPE_MAX: highest interface type number currently defined * @NUM_NL80211_IFTYPES: number of defined interface types * @@ -3671,6 +3850,7 @@ enum nl80211_iftype { NL80211_IFTYPE_P2P_DEVICE, NL80211_IFTYPE_OCB, NL80211_IFTYPE_NAN, + NL80211_IFTYPE_NAN_DATA, /* keep last */ NUM_NL80211_IFTYPES, @@ -4359,6 +4539,46 @@ enum nl80211_band_attr { #define NL80211_BAND_ATTR_HT_CAPA NL80211_BAND_ATTR_HT_CAPA +/** + * enum nl80211_nan_phy_cap_attr - NAN PHY capabilities attributes + * @__NL80211_NAN_PHY_CAP_ATTR_INVALID: attribute number 0 is reserved + * @NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET: 16-byte attribute containing HT MCS set + * @NL80211_NAN_PHY_CAP_ATTR_HT_CAPA: HT capabilities (u16) + * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR: HT A-MPDU factor (u8) + * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY: HT A-MPDU density (u8) + * @NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET: 8-byte attribute containing VHT MCS set + * @NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA: VHT capabilities (u32) + * @NL80211_NAN_PHY_CAP_ATTR_HE_MAC: HE MAC capabilities + * @NL80211_NAN_PHY_CAP_ATTR_HE_PHY: HE PHY capabilities + * @NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET: HE supported NSS/MCS combinations + * @NL80211_NAN_PHY_CAP_ATTR_HE_PPE: HE PPE thresholds + * @NL80211_NAN_PHY_CAP_ATTR_MAX: highest NAN PHY cap attribute number + * @__NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST: internal use + */ +enum nl80211_nan_phy_cap_attr { + __NL80211_NAN_PHY_CAP_ATTR_INVALID, + + /* HT capabilities */ + NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_HT_CAPA, + NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR, + NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY, + + /* VHT capabilities */ + NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA, + + /* HE capabilities */ + NL80211_NAN_PHY_CAP_ATTR_HE_MAC, + NL80211_NAN_PHY_CAP_ATTR_HE_PHY, + NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_HE_PPE, + + /* keep last */ + __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST, + NL80211_NAN_PHY_CAP_ATTR_MAX = __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST - 1 +}; + /** * enum nl80211_wmm_rule - regulatory wmm rule * @@ -4480,6 +4700,10 @@ enum nl80211_wmm_rule { * as a non-primary subchannel. Only applicable to S1G channels. * @NL80211_FREQUENCY_ATTR_NO_UHR: UHR operation is not allowed on this channel * in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_CAC_START_TIME: Channel Availability Check (CAC) + * start time (CLOCK_BOOTTIME, nanoseconds). Only present when CAC is + * currently in progress on this channel. + * @NL80211_FREQUENCY_ATTR_PAD: attribute used for padding for 64-bit alignment * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4530,6 +4754,8 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_16MHZ, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY, NL80211_FREQUENCY_ATTR_NO_UHR, + NL80211_FREQUENCY_ATTR_CAC_START_TIME, + NL80211_FREQUENCY_ATTR_PAD, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -5466,6 +5692,8 @@ enum nl80211_bss_status { * @NL80211_AUTHTYPE_FILS_SK_PFS: Fast Initial Link Setup shared key with PFS * @NL80211_AUTHTYPE_FILS_PK: Fast Initial Link Setup public key * @NL80211_AUTHTYPE_EPPKE: Enhanced Privacy Protection Key Exchange + * @NL80211_AUTHTYPE_IEEE8021X: IEEE 802.1X authentication utilizing + * Authentication frames * @__NL80211_AUTHTYPE_NUM: internal * @NL80211_AUTHTYPE_MAX: maximum valid auth algorithm * @NL80211_AUTHTYPE_AUTOMATIC: determine automatically (if necessary by @@ -5482,6 +5710,7 @@ enum nl80211_auth_type { NL80211_AUTHTYPE_FILS_SK_PFS, NL80211_AUTHTYPE_FILS_PK, NL80211_AUTHTYPE_EPPKE, + NL80211_AUTHTYPE_IEEE8021X, /* keep last */ __NL80211_AUTHTYPE_NUM, @@ -6795,6 +7024,11 @@ enum nl80211_feature_flags { * frames in both non‑AP STA and AP mode as specified in * "IEEE P802.11bi/D3.0, 12.16.6". * + * @NL80211_EXT_FEATURE_IEEE8021X_AUTH: Driver supports IEEE 802.1X + * authentication utilizing Authentication frames with user space SME + * (NL80211_CMD_AUTHENTICATE) in non-AP STA mode, as specified in + * "IEEE P802.11bi/D4.0, 12.16.5". + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6873,6 +7107,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_BEACON_RATE_EHT, NL80211_EXT_FEATURE_EPPKE, NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, + NL80211_EXT_FEATURE_IEEE8021X_AUTH, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, @@ -8517,6 +8752,8 @@ enum nl80211_s1g_short_beacon_attrs { * @NL80211_NAN_CAPA_CAPABILITIES: u8 attribute containing the * capabilities of the device as defined in Wi-Fi Aware (TM) * specification Table 79 (Capabilities field). + * @NL80211_NAN_CAPA_PHY: nested attribute containing band-agnostic + * capabilities for NAN data path. See &enum nl80211_nan_phy_cap_attr. * @__NL80211_NAN_CAPABILITIES_LAST: Internal * @NL80211_NAN_CAPABILITIES_MAX: Highest NAN capability attribute. */ @@ -8529,9 +8766,38 @@ enum nl80211_nan_capabilities { NL80211_NAN_CAPA_NUM_ANTENNAS, NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME, NL80211_NAN_CAPA_CAPABILITIES, + NL80211_NAN_CAPA_PHY, /* keep last */ __NL80211_NAN_CAPABILITIES_LAST, NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1, }; +/** + * enum nl80211_nan_peer_map_attrs - NAN peer schedule map attributes + * + * Nested attributes used within %NL80211_ATTR_NAN_PEER_MAPS to define + * individual peer schedule maps. + * + * @__NL80211_NAN_PEER_MAP_ATTR_INVALID: Invalid + * @NL80211_NAN_PEER_MAP_ATTR_MAP_ID: (u8) The map ID for this schedule map. + * @NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS: An array of u8 values with 32 cells. + * Each value maps a time slot to a channel index within the schedule's + * channel list (%NL80211_ATTR_NAN_CHANNEL attributes). + * %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled. + * @__NL80211_NAN_PEER_MAP_ATTR_LAST: Internal + * @NL80211_NAN_PEER_MAP_ATTR_MAX: Highest peer map attribute + */ +enum nl80211_nan_peer_map_attrs { + __NL80211_NAN_PEER_MAP_ATTR_INVALID, + + NL80211_NAN_PEER_MAP_ATTR_MAP_ID, + NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS, + + /* keep last */ + __NL80211_NAN_PEER_MAP_ATTR_LAST, + NL80211_NAN_PEER_MAP_ATTR_MAX = __NL80211_NAN_PEER_MAP_ATTR_LAST - 1, +}; + +#define NL80211_NAN_SCHED_NOT_AVAIL_SLOT 0xff + #endif /* __LINUX_NL80211_H */ diff --git a/src/include/uapi/linux/nsfs.h b/src/include/uapi/linux/nsfs.h index a25e38d1c8747..96bd591feb678 100644 --- a/src/include/uapi/linux/nsfs.h +++ b/src/include/uapi/linux/nsfs.h @@ -53,9 +53,6 @@ enum init_ns_ino { TIME_NS_INIT_INO = 0xEFFFFFFAU, NET_NS_INIT_INO = 0xEFFFFFF9U, MNT_NS_INIT_INO = 0xEFFFFFF8U, -#ifdef __KERNEL__ - MNT_NS_ANON_INO = 0xEFFFFFF7U, -#endif }; struct nsfs_file_handle { @@ -76,9 +73,6 @@ enum init_ns_id { TIME_NS_INIT_ID = 6ULL, NET_NS_INIT_ID = 7ULL, MNT_NS_INIT_ID = 8ULL, -#ifdef __KERNEL__ - NS_LAST_INIT_ID = MNT_NS_INIT_ID, -#endif }; enum ns_type { diff --git a/src/include/uapi/linux/prctl.h b/src/include/uapi/linux/prctl.h index 55b0446fff9d9..b6ec6f6937195 100644 --- a/src/include/uapi/linux/prctl.h +++ b/src/include/uapi/linux/prctl.h @@ -397,30 +397,23 @@ struct prctl_mm_map { # define PR_RSEQ_SLICE_EXT_ENABLE 0x01 /* - * Get the current indirect branch tracking configuration for the current - * thread, this will be the value configured via PR_SET_INDIR_BR_LP_STATUS. + * Get or set the control flow integrity (CFI) configuration for the + * current thread. + * + * Some per-thread control flow integrity settings are not yet + * controlled through this prctl(); see for example + * PR_{GET,SET,LOCK}_SHADOW_STACK_STATUS */ -#define PR_GET_INDIR_BR_LP_STATUS 80 - +#define PR_GET_CFI 80 +#define PR_SET_CFI 81 /* - * Set the indirect branch tracking configuration. PR_INDIR_BR_LP_ENABLE will - * enable cpu feature for user thread, to track all indirect branches and ensure - * they land on arch defined landing pad instruction. - * x86 - If enabled, an indirect branch must land on an ENDBRANCH instruction. - * arch64 - If enabled, an indirect branch must land on a BTI instruction. - * riscv - If enabled, an indirect branch must land on an lpad instruction. - * PR_INDIR_BR_LP_DISABLE will disable feature for user thread and indirect - * branches will no more be tracked by cpu to land on arch defined landing pad - * instruction. - */ -#define PR_SET_INDIR_BR_LP_STATUS 81 -# define PR_INDIR_BR_LP_ENABLE (1UL << 0) - -/* - * Prevent further changes to the specified indirect branch tracking - * configuration. All bits may be locked via this call, including - * undefined bits. + * Forward-edge CFI variants (excluding ARM64 BTI, which has its own + * prctl()s). */ -#define PR_LOCK_INDIR_BR_LP_STATUS 82 +#define PR_CFI_BRANCH_LANDING_PADS 0 +/* Return and control values for PR_{GET,SET}_CFI */ +# define PR_CFI_ENABLE _BITUL(0) +# define PR_CFI_DISABLE _BITUL(1) +# define PR_CFI_LOCK _BITUL(2) #endif /* _LINUX_PRCTL_H */ From 18f2e9bcfaa81223b47f9ec6a65998219902eaba Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 May 2026 23:40:51 +0900 Subject: [PATCH 2057/2155] test-network: try to stop test-modem-manager-mock.service only when necessary Otherwise, all test cases that does not create/start the service emits the following error: ``` Failed to stop test-modem-manager-mock.service: Unit test-modem-manager-mock.service not loaded. ``` Moreover, without this change, extra 'systemctl daemon-reload' is triggered after all test cases. That's super heavy, especially when the test is running on sanitizers. Follow-up for abe3d570f8006fca5138b2d5cfb4e8b530be02e5. --- test/test-network/systemd-networkd-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 9a8b38a88fa89..877075ec5cd39 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1210,7 +1210,6 @@ def tear_down_common(): stop_dnsmasq() stop_isc_dhcpd() stop_radvd() - stop_modem_manager_mock() # 2. remove modules call_quiet('rmmod netdevsim') @@ -10884,6 +10883,7 @@ def setUp(self): setup_common() def tearDown(self): + stop_modem_manager_mock() tear_down_common() def test_wwan_ipv4v6_static(self): From 0080566c922b27a028001268f8c71f37e2e73b74 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 00:28:38 +0900 Subject: [PATCH 2058/2155] test-network: add test cases for 'networkctl dhcp-lease' command Addresses the request: https://github.com/systemd/systemd/pull/42137#pullrequestreview-4305664376 --- test/test-network/systemd-networkd-tests.py | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 877075ec5cd39..8609a63916a39 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1174,6 +1174,10 @@ def networkctl_reload(): networkctl('reload') +def networkctl_dhcp_lease(*args): + return networkctl('dhcp-lease', *args) + + def resolvectl(*args): return check_output(*(resolvectl_cmd + list(args)), env=env) @@ -8768,6 +8772,43 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) + print('### networkctl dhcp-lease') + output = networkctl_dhcp_lease('veth99') + print(output) + # header + self.assertIn('Hardware Type: ETHER', output) + self.assertIn('Hardware Address: 12:34:56:78:9a:bc', output) + self.assertIn(f'Client Address: {address1}', output) + self.assertIn('Server Address: 192.168.5.1', output) + # options + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + self.assertRegex(output, r'3 router *192\.168\.5\.1') + self.assertRegex(output, r'6 domain name server *192\.168\.5\.6\n *192\.168\.5\.7') + self.assertRegex(output, r'26 MTU size *1492') + self.assertRegex(output, r'28 broadcast address *192\.168\.5\.255') + self.assertRegex(output, r'51 lease time *2min') + self.assertRegex(output, r'53 message type *5') + self.assertRegex(output, r'54 server identifier *192\.168\.5\.1') + self.assertRegex(output, r'119 domain search *example\.com') + self.assertRegex(output, r'120 SIP server *192\.168\.5\.21\n *192\.168\.5\.22') + # extra arguments + output = networkctl_dhcp_lease('veth99', '1') + print(output) + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + output = networkctl_dhcp_lease('veth99', '1:auto') + print(output) + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + output = networkctl_dhcp_lease('veth99', '1:hex') + print(output) + self.assertRegex(output, 'CODE *NAME *DATA') + self.assertRegex(output, r'1 subnet mask *ff:ff:ff:00') + output = networkctl_dhcp_lease('veth99', '1:hex', '--no-legend') + print(output) + self.assertNotIn('CODE', output) + self.assertNotIn('NAME', output) + self.assertNotIn('DATA', output) + self.assertRegex(output, r'1 subnet mask *ff:ff:ff:00') + # change address range, DNS servers, and Domains stop_dnsmasq() start_dnsmasq( @@ -8877,6 +8918,26 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) + print('### networkctl dhcp-lease') + output = networkctl_dhcp_lease('veth99') + print(output) + # header + self.assertIn('Hardware Type: ETHER', output) + self.assertIn('Hardware Address: 12:34:56:78:9a:bc', output) + self.assertIn(f'Client Address: {address2}', output) + self.assertIn('Server Address: 192.168.5.1', output) + # options + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + self.assertRegex(output, r'3 router *192\.168\.5\.1') + self.assertRegex(output, r'6 domain name server *192\.168\.5\.1\n *192\.168\.5\.7\n *192\.168\.5\.8') + self.assertRegex(output, r'26 MTU size *1492') + self.assertRegex(output, r'28 broadcast address *192\.168\.5\.255') + self.assertRegex(output, r'51 lease time *2min') + self.assertRegex(output, r'53 message type *5') + self.assertRegex(output, r'54 server identifier *192\.168\.5\.1') + self.assertRegex(output, r'119 domain search *foo\.example\.com') + self.assertRegex(output, r'120 SIP server *192\.168\.5\.23\n *192\.168\.5\.24') + self.check_netlabel('veth99', r'192\.168\.5\.0/24') self.check_nftset('addr4', r'192\.168\.5\.1') From d9b7fdf06a3fce33d3ae621b83cb2ae7e4996d82 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 07:34:43 +0900 Subject: [PATCH 2059/2155] test-network: fix test case for Neighbor Announcement message handling After 9142bd5a8e9ed94ecbb1e335305e24760b90ad2a, when NA without router flag is received, the corresponding redirect route and the default route is removed, but the other routes are kept. The corresponding test case was not updated by the commit, and the test case has been unfortunately skipped... This fixes the test case, and added more checks. --- test/test-network/systemd-networkd-tests.py | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 9a8b38a88fa89..5f5967439d657 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -7589,15 +7589,36 @@ def test_ndisc_redirect(self): timeout_sec=10, ) + print('### before sending NA without router flag') + output = check_output('ip -6 route show dev veth99') + print(output) + self.assertIn('2002:da8:1::/64 proto ra', output) + self.assertIn('2002:da8:2::/64 proto ra', output) + self.assertRegex(output, 'default .* proto ra') + self.assertRegex(output, '2002:da8:1:1:1a:2b:3c:4d .* proto redirect') + self.assertRegex(output, '2002:da8:1:2:1a:2b:3c:4d .* proto redirect') + self.assertIn('2002:da8:1:3:1a:2b:3c:4d proto redirect', output) + # Send Neighbor Advertisement without the router flag to announce the default router is not available # anymore. Then, verify that all redirect routes and the default route are dropped. output = check_output('ip -6 address show dev veth-peer scope link') veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group() print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}') check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') # fmt: skip - self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10) + self.wait_route_dropped('veth99', 'default .* proto ra', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10) + # Check if the non-default routes are unchanged, and others are actually dropped. + print('### after sending NA without router flag') + output = check_output('ip -6 route show dev veth99') + print(output) + self.assertIn('2002:da8:1::/64 proto ra', output) + self.assertIn('2002:da8:2::/64 proto ra', output) + self.assertNotRegex(output, 'default .* proto ra') + self.assertNotRegex(output, '2002:da8:1:1:1a:2b:3c:4d .* proto redirect') + self.assertNotRegex(output, '2002:da8:1:2:1a:2b:3c:4d .* proto redirect') + self.assertNotIn('2002:da8:1:3:1a:2b:3c:4d proto redirect', output) + # Check if sd-radv refuses RS from the same interface. # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306 since = datetime.datetime.now() From 2e1cf55dba673751f1c858560e0d8d65c670d4bd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 21 May 2026 07:47:37 +0900 Subject: [PATCH 2060/2155] Revert "network: fix max MTU check for IPv6 MTU adjustments" This reverts commit 32417c172383847ec78b672c537594e3efe8f0e0. IPv6 MTU cannot be larger than the current interface MTU. The previous behavior is correct. --- src/network/networkd-sysctl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index ba90d342c5a8b..c59cb2a5d708c 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -545,11 +545,11 @@ int link_set_ipv6_mtu(Link *link, int log_level) { if (mtu == 0) return 0; - if (mtu > link->max_mtu) { + if (mtu > link->mtu) { log_link_full(link, log_level, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, link->max_mtu); - mtu = link->max_mtu; + mtu, link->mtu); + mtu = link->mtu; } r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu, manager_get_sysctl_shadow(link->manager)); From dece52443cf2dab9fa6cb47a1102086997b6fc4a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 May 2026 23:44:49 +0900 Subject: [PATCH 2061/2155] test-network: fix path to test-ndisc-send Follow-up for 9dcdf16b25545d942b872cc0abdbb7c9a6b5f9f1. Previously, test cases that use test-ndisc-send executable did not tested in our mkosi CIs... --- test/test-network/systemd-networkd-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 5f5967439d657..05c11b6a4961d 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -11056,7 +11056,7 @@ def test_wwan_ipv4v6_static(self): if build_dir: test_ndisc_send = os.path.normpath(os.path.join(build_dir, 'test-ndisc-send')) else: - test_ndisc_send = '/usr/lib/tests/test-ndisc-send' + test_ndisc_send = '/usr/lib/systemd/tests/unit-tests/manual/test-ndisc-send' if build_dir: test_modem_manager_mock = os.path.normpath(os.path.join(build_dir, 'test-modem-manager-mock')) From 20ab4a8b154361e0b1a8bb6fccc8afa905825460 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 15:45:30 +0200 Subject: [PATCH 2062/2155] report: use new --help formatting calls --- src/report/report.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/report/report.c b/src/report/report.c index 9cb0e053e0706..a4d7a9ea56daa 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -4,17 +4,16 @@ #include "sd-varlink.h" #include "alloc-util.h" -#include "ansi-color.h" #include "build.h" #include "chase.h" #include "dirent-util.h" #include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "options.h" #include "parse-argument.h" #include "path-lookup.h" -#include "pretty-print.h" #include "recurse-dir.h" #include "report.h" #include "runtime-scope.h" @@ -735,14 +734,9 @@ 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(); - + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; r = verbs_get_help_table(&verbs); if (r < 0) return r; @@ -753,26 +747,21 @@ static int help(void) { (void) table_sync_column_widths(0, options, verbs); - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sAcquire metrics from local sources.%s\n" - "\n%sCommands:%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal()); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Acquire metrics from local sources."); + help_section("Commands"); + r = table_print_or_warn(verbs); if (r < 0) return r; - printf("\n%sOptions:%s\n", - ansi_underline(), - ansi_normal()); + 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-report", "1"); return 0; } From 85d47bfa92fa0bce2ca71d52b25f1276b938c6b8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 23:17:11 +0200 Subject: [PATCH 2063/2155] firstboot: make clear where the full screen console wizards end The three first boot screens are visually separated from the console output before them via the "chome" and welcome strings and sufficient whitespace. But so far they weren't from the output after them. This is sometimes a big confusing. Let's add a bit of a separator between the end and what comes next, too. Just cosmetics, nothing else. --- src/firstboot/firstboot.c | 17 ++++++++++++++--- src/home/homectl.c | 7 +++++++ src/sysinstall/sysinstall.c | 10 ++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 60ec63e2daed6..eac9e7f19bf83 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -103,9 +103,10 @@ STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +static bool welcome_done = false; + static void print_welcome(int rfd, sd_varlink **mute_console_link) { _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL; - static bool done = false; const char *pn, *ac; int r; @@ -122,7 +123,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { if (!arg_welcome) return; - if (done) { + if (welcome_done) { putchar('\n'); /* Add some breathing room between multiple prompts */ return; } @@ -158,7 +159,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { } printf("Please configure the system!\n\n"); - done = true; + welcome_done = true; } static int should_configure(int dir_fd, const char *filename) { @@ -1562,6 +1563,15 @@ static int reload_vconsole(sd_bus **bus) { return 0; } +static void end_marker(void) { + + if (!welcome_done) + return; + + printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; @@ -1627,6 +1637,7 @@ static int run(int argc, char *argv[]) { } LOG_SET_PREFIX(arg_image ?: arg_root); + DEFER_VOID_CALL(end_marker); DEFER_VOID_CALL(chrome_hide); /* We check these conditions here instead of in parse_argv() so that we can take the root directory diff --git a/src/home/homectl.c b/src/home/homectl.c index 6891475f3780f..a90b517416c0d 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -5,6 +5,7 @@ #include "sd-bus.h" #include "sd-varlink.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "bitfield.h" #include "build.h" @@ -2979,6 +2980,11 @@ static int username_is_ok(const char *name, void *userdata) { return false; } +static void end_marker(void) { + printf("\n%sExiting user account creation tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + static int create_interactively(void) { _cleanup_free_ char *username = NULL; int r; @@ -2999,6 +3005,7 @@ static int create_interactively(void) { if (arg_chrome) chrome_show("Create a User Account", /* bottom= */ NULL); + DEFER_VOID_CALL(end_marker); DEFER_VOID_CALL(chrome_hide); if (emoji_enabled()) { diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 0092f3f9c8c92..01e8cd048337d 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -1249,6 +1249,15 @@ static int settle_definitions(void) { return 0; } +static void end_marker(void) { + + if (!arg_welcome) + return; + + printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + static int run(int argc, char *argv[]) { int r; @@ -1275,6 +1284,7 @@ static int run(int argc, char *argv[]) { chrome_show("Operating System Installer", /* bottom= */ NULL); } + DEFER_VOID_CALL(end_marker); DEFER_VOID_CALL(chrome_hide); _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL; From 74d392ed1bab578e901699ee272faa0c8b922128 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 18 May 2026 20:29:04 +0000 Subject: [PATCH 2064/2155] tree-wide: standardize header names across src/fundamental, src/basic and src/shared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the -fundamental suffix from src/fundamental/ headers in favor of names that match their src/basic/ or src/shared/ counterparts (e.g. macro-fundamental.h -> macro.h, assert-fundamental.h -> assert-util.h, cleanup-fundamental.h -> cleanup-util.h). Rename src/basic/{btrfs,label}.{c,h} to use the -util suffix to match the existing shared/btrfs-util and shared/label-util siblings. Rename src/shared/mkdir-label.{c,h} to mkdir.{c,h} and src/shared/tmpfile-util-label.{c,h} to tmpfile-util.{c,h} to match the corresponding src/basic names. This saves us from having to come up with separate names for files that do the same thing across tiers, and it makes it easier to move stuff between src/fundamental, src/basic and src/shared: consumers just #include "foo.h" and pick up whichever tier their -I path resolves to first, so call sites don't need to be updated when an API moves between layers. Where a higher-tier wrapper exists (e.g. src/basic/macro.h wrapping src/fundamental/macro.h), the wrapper uses an explicit "../fundamental/foo.h" or "../basic/foo.h" relative include for the lower-tier header. We can't use GCC's #include_next directive for this — when the wrapper is reachable both via same-dir-as-source lookup and via -I (e.g. -Isrc/shared) for the directory it lives in, #include_next advances by exactly one slot in libcpp's internal directory chain and lands on the same physical directory it was already in, never reaching the lower-tier sibling (see make_cpp_dir() in gcc/libcpp/files.cc:1986). To make sure the right headers are always picked up, the include directories are reordered so that e.g. src/shared always takes priority over src/basic and similar for the other directories. Co-developed-by: Claude Opus 4.7 --- .clangd | 2 +- LICENSES/README.md | 4 ++-- coccinelle/parsing_hacks.h | 2 +- docs/CODING_STYLE.md | 8 ++++---- meson.build | 6 +++--- src/analyze/analyze-chid.c | 4 ++-- src/basic/assert-util.h | 2 +- src/basic/basic-forward.h | 3 ++- src/basic/{btrfs.c => btrfs-util.c} | 2 +- src/basic/{btrfs.h => btrfs-util.h} | 0 src/basic/cleanup-util.h | 4 ++-- src/basic/confidential-virt.c | 1 - src/basic/confidential-virt.h | 2 ++ src/basic/efivars.h | 2 +- src/basic/env-file.c | 2 +- src/basic/fileio.c | 2 +- src/basic/fs-util.c | 4 ++-- src/basic/iovec-util.h | 3 ++- src/basic/{label.c => label-util.c} | 2 +- src/basic/{label.h => label-util.h} | 0 src/basic/macro.h | 2 +- src/basic/memory-util.h | 3 ++- src/basic/meson.build | 4 ++-- src/basic/mkdir.c | 2 +- src/basic/sha256.h | 3 ++- src/basic/string-util.h | 3 ++- src/basic/strv.h | 3 ++- src/basic/unaligned.h | 3 ++- src/boot/boot-secret.c | 2 +- src/boot/boot.c | 10 +++++----- src/boot/chid.c | 1 - src/boot/chid.h | 3 ++- src/boot/console.c | 2 +- src/boot/cpio.c | 4 ++-- src/boot/device-path-util.c | 2 +- src/boot/drivers.c | 2 +- src/boot/edid.h | 3 ++- src/boot/efi-efivars.h | 2 +- src/boot/efi-string-table.h | 2 +- src/boot/efi-string.c | 2 +- src/boot/efi.h | 11 ++++++----- src/boot/initrd.c | 2 +- src/boot/initrd.h | 2 +- src/boot/line-edit.c | 2 +- src/boot/linux.h | 2 +- src/boot/linux_x86.c | 4 ++-- src/boot/meson.build | 4 ++-- src/boot/part-discovery.c | 2 +- src/boot/random-seed.c | 4 ++-- src/boot/secure-boot.h | 2 +- src/boot/splash.c | 2 +- src/boot/stub.c | 4 ++-- src/boot/util.c | 4 ++-- src/boot/util.h | 2 +- src/boot/vmm.c | 2 +- src/bootctl/bootctl-install.c | 2 +- src/core/automount.c | 2 +- src/core/exec-credential.c | 2 +- src/core/exec-invoke.c | 2 +- src/core/generator-setup.c | 2 +- src/core/import-creds.c | 2 +- src/core/main.c | 2 +- src/core/manager.c | 2 +- src/core/meson.build | 2 +- src/core/mount.c | 2 +- src/core/namespace.c | 2 +- src/core/path.c | 2 +- src/core/socket.c | 2 +- src/core/unit.c | 2 +- src/coredump/coredump-submit.c | 2 +- src/firstboot/firstboot.c | 2 +- .../{assert-fundamental.h => assert-util.h} | 2 +- .../{bootspec-fundamental.c => bootspec.c} | 2 +- .../{bootspec-fundamental.h => bootspec.h} | 2 +- src/fundamental/{chid-fundamental.c => chid.c} | 8 ++++---- src/fundamental/{chid-fundamental.h => chid.h} | 8 +++----- .../{cleanup-fundamental.h => cleanup-util.h} | 2 +- ...-virt-fundamental.h => confidential-virt.h} | 0 src/fundamental/{edid-fundamental.c => edid.c} | 4 ++-- src/fundamental/{edid-fundamental.h => edid.h} | 2 +- src/fundamental/{efi-fundamental.h => efi.h} | 0 .../{efivars-fundamental.c => efivars.c} | 2 +- .../{efivars-fundamental.h => efivars.h} | 2 +- .../{iovec-util-fundamental.h => iovec-util.h} | 4 ++-- .../{macro-fundamental.h => macro.h} | 0 ...memory-util-fundamental.c => memory-util.c} | 2 +- ...memory-util-fundamental.h => memory-util.h} | 4 ++-- src/fundamental/meson.build | 18 +++++++++--------- src/fundamental/{sha1-fundamental.c => sha1.c} | 4 ++-- src/fundamental/{sha1-fundamental.h => sha1.h} | 0 .../{sha256-fundamental.c => sha256.c} | 8 ++++---- .../{sha256-fundamental.h => sha256.h} | 0 ...ring-table-fundamental.h => string-table.h} | 2 +- ...string-util-fundamental.c => string-util.c} | 4 ++-- ...string-util-fundamental.h => string-util.h} | 4 ++-- src/fundamental/{strv-fundamental.h => strv.h} | 4 ++-- .../{unaligned-fundamental.h => unaligned.h} | 0 src/import/import-fs.c | 2 +- src/import/import-raw.c | 2 +- src/import/import-tar.c | 2 +- src/import/pull-oci.c | 4 ++-- src/import/pull-raw.c | 2 +- src/import/pull-tar.c | 2 +- src/include/override/sys/param.h | 2 +- src/libsystemd-network/meson.build | 2 +- src/libsystemd/sd-bus/test-bus-marshal.c | 2 +- src/libudev/meson.build | 2 +- src/locale/localed-util.c | 2 +- src/login/logind-dbus.c | 2 +- src/login/logind-inhibit.c | 2 +- src/login/logind-seat.c | 2 +- src/login/logind-session.c | 2 +- src/login/logind-user.c | 2 +- src/login/logind.c | 2 +- src/login/user-runtime-dir.c | 2 +- src/machine/machine.c | 2 +- src/machine/machined.c | 2 +- src/network/meson.build | 2 +- src/network/networkctl-config-file.c | 2 +- src/network/networkd.c | 2 +- src/nspawn/meson.build | 2 +- src/nspawn/nspawn-mount.c | 2 +- src/nss-resolve/meson.build | 4 ++-- src/pcrlock/pcrlock.c | 2 +- src/resolve/resolved-resolv-conf.c | 2 +- src/resolve/resolved-socket-graveyard.c | 2 +- src/resolve/resolved.c | 2 +- src/sbsign/sbsign.c | 2 +- src/shared/bootspec.c | 1 - src/shared/bootspec.h | 2 ++ src/shared/btrfs-util.h | 3 ++- src/shared/copy.c | 2 +- src/shared/creds-util.c | 2 +- src/shared/dev-setup.c | 2 +- src/shared/dissect-image.c | 2 +- src/shared/edit-util.c | 4 ++-- src/shared/efi-api.c | 2 +- src/shared/ethtool-util.c | 2 +- src/shared/generator.c | 2 +- src/shared/hwdb-util.c | 2 +- src/shared/install.c | 2 +- src/shared/label-util.h | 3 ++- src/shared/meson.build | 4 ++-- src/shared/{mkdir-label.c => mkdir.c} | 2 +- src/shared/{mkdir-label.h => mkdir.h} | 3 ++- src/shared/mount-setup.c | 2 +- src/shared/mount-util.c | 2 +- src/shared/resize-fs.c | 2 +- src/shared/selinux-util.c | 1 - src/shared/smack-util.c | 1 - src/shared/socket-label.c | 2 +- src/shared/storage-util.h | 1 - src/shared/switch-root.c | 2 +- .../{tmpfile-util-label.c => tmpfile-util.c} | 1 - .../{tmpfile-util-label.h => tmpfile-util.h} | 8 ++++---- src/shared/tpm2-util.h | 2 +- src/systemctl/systemctl-enable.c | 1 - src/sysusers/sysusers.c | 2 +- src/test/test-chid.c | 2 +- src/test/test-label.c | 2 +- src/test/test-sha1.c | 2 +- src/tmpfiles/tmpfiles.c | 2 +- .../tty-ask-password-agent.c | 2 +- src/udev/meson.build | 2 +- src/udev/test-udev-rule-runner.c | 2 +- src/udev/udev-node.c | 2 +- 166 files changed, 219 insertions(+), 212 deletions(-) rename src/basic/{btrfs.c => btrfs-util.c} (99%) rename src/basic/{btrfs.h => btrfs-util.h} (100%) rename src/basic/{label.c => label-util.c} (96%) rename src/basic/{label.h => label-util.h} (100%) rename src/fundamental/{assert-fundamental.h => assert-util.h} (99%) rename src/fundamental/{bootspec-fundamental.c => bootspec.c} (98%) rename src/fundamental/{bootspec-fundamental.h => bootspec.h} (94%) rename src/fundamental/{chid-fundamental.c => chid.c} (98%) rename src/fundamental/{chid-fundamental.h => chid.h} (94%) rename src/fundamental/{cleanup-fundamental.h => cleanup-util.h} (99%) rename src/fundamental/{confidential-virt-fundamental.h => confidential-virt.h} (100%) rename src/fundamental/{edid-fundamental.c => edid.c} (97%) rename src/fundamental/{edid-fundamental.h => edid.h} (97%) rename src/fundamental/{efi-fundamental.h => efi.h} (100%) rename src/fundamental/{efivars-fundamental.c => efivars.c} (98%) rename src/fundamental/{efivars-fundamental.h => efivars.h} (98%) rename src/fundamental/{iovec-util-fundamental.h => iovec-util.h} (96%) rename src/fundamental/{macro-fundamental.h => macro.h} (100%) rename src/fundamental/{memory-util-fundamental.c => memory-util.c} (95%) rename src/fundamental/{memory-util-fundamental.h => memory-util.h} (98%) rename src/fundamental/{sha1-fundamental.c => sha1.c} (99%) rename src/fundamental/{sha1-fundamental.h => sha1.h} (100%) rename src/fundamental/{sha256-fundamental.c => sha256.c} (98%) rename src/fundamental/{sha256-fundamental.h => sha256.h} (100%) rename src/fundamental/{string-table-fundamental.h => string-table.h} (96%) rename src/fundamental/{string-util-fundamental.c => string-util.c} (99%) rename src/fundamental/{string-util-fundamental.h => string-util.h} (97%) rename src/fundamental/{strv-fundamental.h => strv.h} (84%) rename src/fundamental/{unaligned-fundamental.h => unaligned.h} (100%) rename src/shared/{mkdir-label.c => mkdir.c} (98%) rename src/shared/{mkdir-label.h => mkdir.h} (93%) rename src/shared/{tmpfile-util-label.c => tmpfile-util.c} (95%) rename src/shared/{tmpfile-util-label.h => tmpfile-util.h} (50%) diff --git a/.clangd b/.clangd index 7ce59c6002f41..b0deab559c881 100644 --- a/.clangd +++ b/.clangd @@ -11,6 +11,6 @@ CompileFlags: Diagnostics: UnusedIncludes: Strict # __no_reorder__ is a GCC-only attribute (see _no_reorder_ in - # src/fundamental/macro-fundamental.h). Meson detects it during configure + # src/fundamental/macro.h). Meson detects it during configure # with GCC and enables it unconditionally, so clangd flags every use. Suppress: [unknown-attributes] diff --git a/LICENSES/README.md b/LICENSES/README.md index 9bd26baa0dc67..90550d85c78ac 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -67,8 +67,8 @@ The following exceptions apply: * the tools/chromiumos/gen_autosuspend_rules.py script is licensed under the **BSD-3-Clause** license. * the following sources are under **Public Domain** (LicenseRef-alg-sha1-public-domain): - - src/fundamental/sha1-fundamental.c - - src/fundamental/sha1-fundamental.h + - src/fundamental/sha1.c + - src/fundamental/sha1.h * the following files are licensed under **BSD-3-Clause** license: - src/boot/chid.c - src/boot/chid.h diff --git a/coccinelle/parsing_hacks.h b/coccinelle/parsing_hacks.h index bc84235557719..536a2d2670c04 100644 --- a/coccinelle/parsing_hacks.h +++ b/coccinelle/parsing_hacks.h @@ -101,7 +101,7 @@ /* 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. */ + * false positives from check-pointer-deref.cocci. See assert-util.h for the definition. */ #define SIZEOF(x) 8 /* Work around a bug in zlib.h parsing on Fedora (and possibly others) diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md index 085c97df5ce44..d6580bbbecd42 100644 --- a/docs/CODING_STYLE.md +++ b/docs/CODING_STYLE.md @@ -366,10 +366,10 @@ SPDX-License-Identifier: LGPL-2.1-or-later - `src/shared`: `shared-forward.h` Header files that extend other header files can include the original header - file. For example, `iovec-util.h` includes `iovec-fundamental.h` and - `sys/uio.h`. To identify headers that are exported from other headers, add a - `IWYU pragma: export` comment to the includes so that these exports are - recognized by clang static analysis tooling. + file. For example, `iovec-util.h` includes `sys/uio.h`. To identify headers + that are exported from other headers, add a `IWYU pragma: export` comment + to the includes so that these exports are recognized by clang static analysis + tooling. Bad: diff --git a/meson.build b/meson.build index 1d2d59e4fa536..c5af3b107c155 100644 --- a/meson.build +++ b/meson.build @@ -1715,7 +1715,7 @@ basic_includes = [ version_include, ] -libsystemd_includes = [basic_includes, include_directories( +libsystemd_includes = [include_directories( 'src/libsystemd/sd-bus', 'src/libsystemd/sd-common', 'src/libsystemd/sd-device', @@ -1728,14 +1728,14 @@ libsystemd_includes = [basic_includes, include_directories( 'src/libsystemd/sd-network', 'src/libsystemd/sd-path', 'src/libsystemd/sd-resolve', - 'src/libsystemd/sd-varlink')] + 'src/libsystemd/sd-varlink'), basic_includes] includes = [ - libsystemd_includes, include_directories( 'src/shared', 'src/bpf', ), + libsystemd_includes, ] subdir('po') diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c index 9fe68b2de45da..896c6e54b36a5 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -6,9 +6,9 @@ #include "analyze.h" #include "analyze-chid.h" #include "ansi-color.h" -#include "chid-fundamental.h" +#include "chid.h" #include "device-util.h" -#include "edid-fundamental.h" +#include "edid.h" #include "efi-api.h" #include "errno-util.h" #include "escape.h" diff --git a/src/basic/assert-util.h b/src/basic/assert-util.h index 899a4365601f7..8451b7b29916f 100644 --- a/src/basic/assert-util.h +++ b/src/basic/assert-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/assert-util.h" /* IWYU pragma: export */ /* Logging for various assertions */ diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 5edd494f783c6..0a245a1863196 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -17,7 +17,8 @@ #include "assert-util.h" /* IWYU pragma: export */ #include "cleanup-util.h" /* IWYU pragma: export */ #include "macro.h" /* IWYU pragma: export */ -#include "string-table-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/string-table.h" /* IWYU pragma: export */ /* Generic types */ diff --git a/src/basic/btrfs.c b/src/basic/btrfs-util.c similarity index 99% rename from src/basic/btrfs.c rename to src/basic/btrfs-util.c index 81c9839450734..941d66c93cf11 100644 --- a/src/basic/btrfs.c +++ b/src/basic/btrfs-util.c @@ -5,7 +5,7 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" diff --git a/src/basic/btrfs.h b/src/basic/btrfs-util.h similarity index 100% rename from src/basic/btrfs.h rename to src/basic/btrfs-util.h diff --git a/src/basic/cleanup-util.h b/src/basic/cleanup-util.h index e3a2ade4ed984..93f6a0de96d60 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-fundamental.h" -#include "cleanup-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/assert-util.h" +#include "../fundamental/cleanup-util.h" /* IWYU pragma: export */ typedef void (*free_func_t)(void *p); typedef void* (*mfree_func_t)(void *p); diff --git a/src/basic/confidential-virt.c b/src/basic/confidential-virt.c index a5996b244bd15..29d598a5988d8 100644 --- a/src/basic/confidential-virt.c +++ b/src/basic/confidential-virt.c @@ -8,7 +8,6 @@ #include #include "confidential-virt.h" -#include "confidential-virt-fundamental.h" #include "errno-util.h" /* IWYU pragma: keep */ #include "fd-util.h" #include "fileio.h" /* IWYU pragma: keep */ diff --git a/src/basic/confidential-virt.h b/src/basic/confidential-virt.h index 446080de3595f..5505f997e6f65 100644 --- a/src/basic/confidential-virt.h +++ b/src/basic/confidential-virt.h @@ -3,6 +3,8 @@ #include "basic-forward.h" +#include "../fundamental/confidential-virt.h" /* IWYU pragma: export */ + typedef enum ConfidentialVirtualization { CONFIDENTIAL_VIRTUALIZATION_NONE = 0, diff --git a/src/basic/efivars.h b/src/basic/efivars.h index 22e0eab9a0afa..368fe12a49acc 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -5,7 +5,7 @@ #include "sd-id128.h" -#include "efivars-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/efivars.h" /* IWYU pragma: export */ #define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) #define EFI_VENDOR_LOADER_STR SD_ID128_MAKE_UUID_STR(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) diff --git a/src/basic/env-file.c b/src/basic/env-file.c index 587618614e66d..7689c653a28b2 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -10,7 +10,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "label.h" +#include "label-util.h" #include "log.h" #include "string-util.h" #include "strv.h" diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 8149bb8b0dd44..de2ac6af726e7 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -16,7 +16,7 @@ #include "hexdecoct.h" #include "io-util.h" #include "iovec-util.h" -#include "label.h" +#include "label-util.h" #include "log.h" #include "mkdir.h" #include "nulstr-util.h" diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 84b76072d7c63..b51d630898239 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -7,14 +7,14 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chattr-util.h" #include "dirent-util.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" #include "hostname-util.h" -#include "label.h" +#include "label-util.h" #include "lock-util.h" #include "log.h" #include "mkdir.h" diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index a0a059550b7db..35cdce6a76ed1 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -4,7 +4,8 @@ #include /* IWYU pragma: export */ #include "basic-forward.h" -#include "iovec-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/iovec-util.h" /* IWYU pragma: export */ extern const struct iovec iovec_nul_byte; /* Points to a single NUL byte */ extern const struct iovec iovec_empty; /* Points to an empty, but valid (i.e. non-NULL) pointer */ diff --git a/src/basic/label.c b/src/basic/label-util.c similarity index 96% rename from src/basic/label.c rename to src/basic/label-util.c index cce3e75c7c1be..a6d616210afaf 100644 --- a/src/basic/label.c +++ b/src/basic/label-util.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "label.h" +#include "label-util.h" static const LabelOps *label_ops = NULL; diff --git a/src/basic/label.h b/src/basic/label-util.h similarity index 100% rename from src/basic/label.h rename to src/basic/label-util.h diff --git a/src/basic/macro.h b/src/basic/macro.h index 390a9fab38ca3..cb0719f15f220 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/macro.h" /* IWYU pragma: export */ #if !defined(HAS_FEATURE_MEMORY_SANITIZER) # if defined(__has_feature) diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index f16118fbb09c2..4caf58585a779 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -4,7 +4,8 @@ #include #include "basic-forward.h" -#include "memory-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/memory-util.h" /* IWYU pragma: export */ size_t page_size(void) _pure_; #define PAGE_ALIGN(l) ALIGN_TO(l, page_size()) diff --git a/src/basic/meson.build b/src/basic/meson.build index 7cc62c7b8bdac..6987c3cabb137 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -10,7 +10,7 @@ basic_sources = files( 'arphrd-util.c', 'assert-util.c', 'audit-util.c', - 'btrfs.c', + 'btrfs-util.c', 'build.c', 'build-path.c', 'bus-label.c', @@ -57,7 +57,7 @@ basic_sources = files( 'iovec-util.c', 'iovec-wrapper.c', 'keyring-util.c', - 'label.c', + 'label-util.c', 'limits-util.c', 'locale-util.c', 'lock-util.c', diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 7b353542e342f..fc79763bb95e3 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -3,7 +3,7 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chase.h" #include "errno-util.h" #include "fd-util.h" diff --git a/src/basic/sha256.h b/src/basic/sha256.h index 5016cb42b025a..74314a0cab853 100644 --- a/src/basic/sha256.h +++ b/src/basic/sha256.h @@ -3,7 +3,8 @@ #pragma once #include "basic-forward.h" -#include "sha256-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/sha256.h" /* IWYU pragma: export */ int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]); diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 8cddc051d5d3d..447f4f807e76b 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -5,7 +5,8 @@ #include "alloc-util.h" #include "basic-forward.h" -#include "string-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/string-util.h" /* IWYU pragma: export */ static inline char* strstr_ptr_internal(const char *haystack, const char *needle) { if (!haystack || !needle) diff --git a/src/basic/strv.h b/src/basic/strv.h index d7c4b2bcf08ed..cf6fa158b6a41 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -2,7 +2,8 @@ #pragma once #include "basic-forward.h" -#include "strv-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/strv.h" /* IWYU pragma: export */ char* strv_find(char * const *l, const char *name) _pure_; char* strv_find_case(char * const *l, const char *name) _pure_; diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h index b45fb3c3587d6..f54d7d8edd999 100644 --- a/src/basic/unaligned.h +++ b/src/basic/unaligned.h @@ -4,7 +4,8 @@ #include #include "basic-forward.h" -#include "unaligned-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/unaligned.h" /* IWYU pragma: export */ /* BE */ diff --git a/src/boot/boot-secret.c b/src/boot/boot-secret.c index 54078b51c23bd..7be0880d3b074 100644 --- a/src/boot/boot-secret.c +++ b/src/boot/boot-secret.c @@ -4,7 +4,7 @@ #include "efi-efivars.h" #include "efi-log.h" #include "random-seed.h" -#include "sha256-fundamental.h" +#include "sha256.h" #include "util.h" #define BOOT_SECRET_MIXIN_PATH u"\\loader\\boot-secret-mixin" diff --git a/src/boot/boot.c b/src/boot/boot.c index 8660814aaebd6..9f833f02b1b0f 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bcd.h" -#include "bootspec-fundamental.h" +#include "bootspec.h" #include "console.h" #include "cpio.h" #include "device-path-util.h" @@ -10,14 +10,14 @@ #include "efi-efivars.h" #include "efi-log.h" #include "efi-string-table.h" -#include "efivars-fundamental.h" +#include "efivars.h" #include "export-vars.h" #include "graphics.h" #include "initrd.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "line-edit.h" #include "measure.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "part-discovery.h" #include "pe.h" #include "proto/block-io.h" @@ -29,7 +29,7 @@ #include "secure-boot.h" #include "shim.h" #include "smbios.h" -#include "strv-fundamental.h" +#include "strv.h" #include "sysfail.h" #include "ticks.h" #include "tpm2-pcr.h" diff --git a/src/boot/chid.c b/src/boot/chid.c index 8abd9de47ceea..6cb11e8b1c1b2 100644 --- a/src/boot/chid.c +++ b/src/boot/chid.c @@ -15,7 +15,6 @@ */ #include "chid.h" -#include "chid-fundamental.h" #include "edid.h" #if SD_BOOT #include "efi-log.h" diff --git a/src/boot/chid.h b/src/boot/chid.h index f8299fde88888..8bf4b555a40a9 100644 --- a/src/boot/chid.h +++ b/src/boot/chid.h @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: BSD-3-Clause */ #pragma once -#include "chid-fundamental.h" /* IWYU pragma: export */ #include "efi.h" +#include "../fundamental/chid.h" /* IWYU pragma: export */ + /* A .hwids PE section consists of a series of 'Device' structures. A 'Device' structure binds a CHID to some * resource, for now only Devicetree blobs. Designed to be extensible to other types of resources, should the * need arise. The series of 'Device' structures is followed by some space for strings that can be referenced diff --git a/src/boot/console.c b/src/boot/console.c index 8dcd986aa4f15..1115eeaae0a2c 100644 --- a/src/boot/console.c +++ b/src/boot/console.c @@ -6,7 +6,7 @@ #include "efi-string.h" #include "proto/graphics-output.h" #include "proto/pci-io.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" #define SYSTEM_FONT_WIDTH 8 diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 36c536681cc12..6c1b3caedd178 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -2,9 +2,9 @@ #include "cpio.h" #include "efi-log.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "measure.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "tpm2-pcr.h" #include "util.h" diff --git a/src/boot/device-path-util.c b/src/boot/device-path-util.c index a40cb5e8a11e9..5243e81e5df18 100644 --- a/src/boot/device-path-util.c +++ b/src/boot/device-path-util.c @@ -2,7 +2,7 @@ #include "device-path-util.h" #include "efi-string.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static const EFI_DEVICE_PATH *device_path_find_end_node(const EFI_DEVICE_PATH *dp) { diff --git a/src/boot/drivers.c b/src/boot/drivers.c index d58b1e39da74c..215404d4b0398 100644 --- a/src/boot/drivers.c +++ b/src/boot/drivers.c @@ -3,7 +3,7 @@ #include "device-path-util.h" #include "drivers.h" #include "efi-log.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static EFI_STATUS load_one_driver( diff --git a/src/boot/edid.h b/src/boot/edid.h index 74649658d93ff..553684d8d1c77 100644 --- a/src/boot/edid.h +++ b/src/boot/edid.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "edid-fundamental.h" /* IWYU pragma: export */ #include "efi.h" +#include "../fundamental/edid.h" /* IWYU pragma: export */ + EFI_STATUS edid_get_discovered_panel_id(char16_t **ret_panel); diff --git a/src/boot/efi-efivars.h b/src/boot/efi-efivars.h index 6d88f56e71296..c9e2f0d9295c7 100644 --- a/src/boot/efi-efivars.h +++ b/src/boot/efi-efivars.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "efivars-fundamental.h" /* IWYU pragma: export */ +#include "efivars.h" /* IWYU pragma: export */ /* * Allocated random UUID, intended to be shared across tools that implement diff --git a/src/boot/efi-string-table.h b/src/boot/efi-string-table.h index 9e142aa6ccc13..7b755578b215d 100644 --- a/src/boot/efi-string-table.h +++ b/src/boot/efi-string-table.h @@ -2,7 +2,7 @@ #pragma once #include "efi-string.h" -#include "macro-fundamental.h" +#include "macro.h" #define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ scope const char* name##_to_string(type i) { \ diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index d0be7a1e160d3..306446386271a 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "efi-string.h" -#include "string-util-fundamental.h" +#include "string-util.h" #if SD_BOOT # include "proto/simple-text-io.h" diff --git a/src/boot/efi.h b/src/boot/efi.h index eddaad1eeeaaa..c518a0253ff8d 100644 --- a/src/boot/efi.h +++ b/src/boot/efi.h @@ -9,11 +9,12 @@ #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ -#include "assert-fundamental.h" /* IWYU pragma: export */ -#include "cleanup-fundamental.h" /* IWYU pragma: export */ -#include "efi-fundamental.h" /* IWYU pragma: export */ -#include "macro-fundamental.h" /* IWYU pragma: export */ -#include "string-table-fundamental.h" /* IWYU pragma: export */ +#include "assert-util.h" /* IWYU pragma: export */ +#include "cleanup-util.h" /* IWYU pragma: export */ +#include "macro.h" /* IWYU pragma: export */ +#include "string-table.h" /* IWYU pragma: export */ + +#include "../fundamental/efi.h" /* IWYU pragma: export */ #if SD_BOOT /* uchar.h/wchar.h are not suitable for freestanding environments. */ diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 044e011f60e89..eced9273fe0fa 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -2,7 +2,7 @@ #include "efi-log.h" #include "initrd.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "proto/device-path.h" #include "proto/load-file.h" #include "util.h" diff --git a/src/boot/initrd.h b/src/boot/initrd.h index cfcb8d1c6f59b..fb56d6919d97b 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "util.h" EFI_STATUS initrd_register( diff --git a/src/boot/line-edit.c b/src/boot/line-edit.c index 1e6128e1effaa..e37398d971956 100644 --- a/src/boot/line-edit.c +++ b/src/boot/line-edit.c @@ -2,7 +2,7 @@ #include "console.h" #include "line-edit.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static void cursor_left(size_t *cursor, size_t *first) { diff --git a/src/boot/linux.h b/src/boot/linux.h index 681d12c0c1cfa..f7eb834bca7f9 100644 --- a/src/boot/linux.h +++ b/src/boot/linux.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" EFI_STATUS linux_exec( EFI_HANDLE parent, diff --git a/src/boot/linux_x86.c b/src/boot/linux_x86.c index 349e3fb26c01b..2f3275b0654e6 100644 --- a/src/boot/linux_x86.c +++ b/src/boot/linux_x86.c @@ -12,8 +12,8 @@ #include "efi-log.h" #include "linux.h" -#include "macro-fundamental.h" -#include "memory-util-fundamental.h" +#include "macro.h" +#include "memory-util.h" #include "util.h" #define KERNEL_SECTOR_SIZE 512u diff --git a/src/boot/meson.build b/src/boot/meson.build index 1b8f94e58a247..2dc9c64256305 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -12,8 +12,8 @@ libefitest = static_library( ), build_by_default : false, include_directories : [ - basic_includes, include_directories('.'), + basic_includes, ], implicit_include_directories : false, dependencies : userspace) @@ -164,8 +164,8 @@ configure_file( ############################################################ efi_includes = [ - fundamental_include, include_directories('.'), + fundamental_include, version_include, ] diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 8f5fefad47c1d..ab553a6530bbc 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -6,7 +6,7 @@ #include "proto/block-io.h" #include "proto/device-path.h" #include "proto/disk-io.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" typedef struct { diff --git a/src/boot/random-seed.c b/src/boot/random-seed.c index a31215c3b0262..6d27a9255211c 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -2,11 +2,11 @@ #include "efi-efivars.h" #include "efi-log.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "proto/rng.h" #include "random-seed.h" #include "secure-boot.h" -#include "sha256-fundamental.h" +#include "sha256.h" #include "util.h" #define RANDOM_MAX_SIZE_MIN (32U) diff --git a/src/boot/secure-boot.h b/src/boot/secure-boot.h index 03bead27124d8..58cc02a9d277d 100644 --- a/src/boot/secure-boot.h +++ b/src/boot/secure-boot.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "efivars-fundamental.h" +#include "efivars.h" typedef enum { ENROLL_OFF, /* no Secure Boot key enrollment whatsoever, even manual entries are not generated */ diff --git a/src/boot/splash.c b/src/boot/splash.c index 487f212e65c1e..126fff954714b 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -4,7 +4,7 @@ #include "logarithm.h" #include "proto/graphics-output.h" #include "splash.h" -#include "unaligned-fundamental.h" +#include "unaligned.h" #include "util.h" struct bmp_file { diff --git a/src/boot/stub.c b/src/boot/stub.c index 52927e91ff077..6411102ec435e 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -11,10 +11,10 @@ #include "export-vars.h" #include "graphics.h" #include "initrd.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "linux.h" #include "measure.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "part-discovery.h" #include "pe.h" #include "proto/shell-parameters.h" diff --git a/src/boot/util.c b/src/boot/util.c index 22981bc00db1f..e0d2788c20233 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -4,10 +4,10 @@ #include "efi-log.h" #include "efi-string.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "proto/device-path.h" #include "proto/simple-text-io.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" #include "version.h" diff --git a/src/boot/util.h b/src/boot/util.h index fa552d6f46c08..89f1184762aaf 100644 --- a/src/boot/util.h +++ b/src/boot/util.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #if SD_BOOT diff --git a/src/boot/vmm.c b/src/boot/vmm.c index da17e4584aaec..902f61b41c2e4 100644 --- a/src/boot/vmm.c +++ b/src/boot/vmm.c @@ -4,7 +4,7 @@ # include #endif -#include "confidential-virt-fundamental.h" +#include "confidential-virt.h" #include "device-path-util.h" #include "drivers.h" #include "efi-string.h" diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 2ad76264fa390..77d03e72b6c5c 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -19,7 +19,7 @@ #include "crypto-util.h" #include "dirent-util.h" #include "efi-api.h" -#include "efi-fundamental.h" +#include "efi.h" #include "efivars.h" #include "env-file.h" #include "fd-util.h" diff --git a/src/core/automount.c b/src/core/automount.c index 5fd9c8f4ccbc2..a0529cda5f26d 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -20,7 +20,7 @@ #include "io-util.h" #include "label-util.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount.h" #include "mount-util.h" #include "mountpoint-util.h" diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index c1b2fcda85cde..13ecf72f49e5b 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -17,7 +17,7 @@ #include "label-util.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b82f7b995b638..9004ad88d0689 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -53,7 +53,7 @@ #include "libmount-util.h" #include "manager.h" #include "memfd-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" diff --git a/src/core/generator-setup.c b/src/core/generator-setup.c index d63a393d985d1..804eb11f1462b 100644 --- a/src/core/generator-setup.c +++ b/src/core/generator-setup.c @@ -4,7 +4,7 @@ #include "errno-util.h" #include "generator-setup.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-lookup.h" #include "rm-rf.h" diff --git a/src/core/import-creds.c b/src/core/import-creds.c index 89f0c557078c2..488d63eb41d81 100644 --- a/src/core/import-creds.c +++ b/src/core/import-creds.c @@ -17,7 +17,7 @@ #include "initrd-util.h" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "parse-util.h" diff --git a/src/core/main.c b/src/core/main.c index 0b88afae6242e..7815691133c66 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -78,7 +78,7 @@ #include "manager.h" #include "manager-dump.h" #include "manager-serialize.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-setup.h" #include "mount-util.h" #include "options.h" diff --git a/src/core/manager.c b/src/core/manager.c index 165914b2a53d0..bb8a735683ee5 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -61,7 +61,7 @@ #include "manager-dump.h" #include "manager-serialize.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "notify-recv.h" #include "parse-util.h" diff --git a/src/core/meson.build b/src/core/meson.build index e1f315dda622d..af50a2a4fa7d9 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -159,7 +159,7 @@ generated_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_d libcore_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc] libcore_build_dir = meson.current_build_dir() libcore_name = 'systemd-core-@0@'.format(shared_lib_tag) -core_includes = [includes, include_directories('.')] +core_includes = [include_directories('.'), includes] libcore_static = static_library( libcore_name, diff --git a/src/core/mount.c b/src/core/mount.c index 408274c3864be..4d5cd35fae60b 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -23,7 +23,7 @@ #include "libmount-util.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mount.h" #include "mount-setup.h" diff --git a/src/core/namespace.c b/src/core/namespace.c index b07b1082edef2..49635d3e9528a 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -31,7 +31,7 @@ #include "log.h" #include "loop-util.h" #include "loopback-setup.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" diff --git a/src/core/path.c b/src/core/path.c index 18a5e140f1442..93e702a481610 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -15,7 +15,7 @@ #include "glob-util.h" #include "inotify-util.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path.h" #include "path-util.h" #include "serialize.h" diff --git a/src/core/socket.c b/src/core/socket.c index 3c4742fba11eb..2cdb34c4be656 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -30,7 +30,7 @@ #include "ip-protocol-list.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "parse-util.h" #include "path-util.h" diff --git a/src/core/unit.c b/src/core/unit.c index f81083a70f753..d66f813a9a3de 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -40,7 +40,7 @@ #include "load-fragment.h" #include "log.h" #include "logarithm.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "manager.h" #include "mount-util.h" #include "mountpoint-util.h" diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index f0c961ded7e5f..a108fe3eb5fb7 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -33,7 +33,7 @@ #include "journal-send.h" #include "json-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "path-util.h" #include "process-util.h" diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index eac9e7f19bf83..e328c8550015c 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -55,7 +55,7 @@ #include "strv.h" #include "terminal-util.h" #include "time-util.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" #include "user-util.h" #include "vconsole-util.h" diff --git a/src/fundamental/assert-fundamental.h b/src/fundamental/assert-util.h similarity index 99% rename from src/fundamental/assert-fundamental.h rename to src/fundamental/assert-util.h index d3425cb54df0c..7f173fefb35f9 100644 --- a/src/fundamental/assert-fundamental.h +++ b/src/fundamental/assert-util.h @@ -5,7 +5,7 @@ # include #endif -#include "macro-fundamental.h" +#include "macro.h" #if SD_BOOT _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); diff --git a/src/fundamental/bootspec-fundamental.c b/src/fundamental/bootspec.c similarity index 98% rename from src/fundamental/bootspec-fundamental.c rename to src/fundamental/bootspec.c index 5ca66740294e2..6daf48479deb7 100644 --- a/src/fundamental/bootspec-fundamental.c +++ b/src/fundamental/bootspec.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bootspec-fundamental.h" +#include "bootspec.h" bool bootspec_pick_name_version_sort_key( const sd_char *os_pretty_name, diff --git a/src/fundamental/bootspec-fundamental.h b/src/fundamental/bootspec.h similarity index 94% rename from src/fundamental/bootspec-fundamental.h rename to src/fundamental/bootspec.h index 19b489ccf55ad..50722d2b34534 100644 --- a/src/fundamental/bootspec-fundamental.h +++ b/src/fundamental/bootspec.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "string-util-fundamental.h" +#include "string-util.h" bool bootspec_pick_name_version_sort_key( const sd_char *os_pretty_name, diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid.c similarity index 98% rename from src/fundamental/chid-fundamental.c rename to src/fundamental/chid.c index 474aab74840fa..28bc4572f06bf 100644 --- a/src/fundamental/chid-fundamental.c +++ b/src/fundamental/chid.c @@ -24,10 +24,10 @@ #define strlen16 char16_strlen #endif -#include "chid-fundamental.h" -#include "macro-fundamental.h" -#include "memory-util-fundamental.h" -#include "sha1-fundamental.h" +#include "chid.h" +#include "macro.h" +#include "memory-util.h" +#include "sha1.h" static void get_chid( const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid.h similarity index 94% rename from src/fundamental/chid-fundamental.h rename to src/fundamental/chid.h index a0e77dd9b02ce..1d5247bf12bbc 100644 --- a/src/fundamental/chid-fundamental.h +++ b/src/fundamental/chid.h @@ -2,14 +2,12 @@ #pragma once -#if SD_BOOT -# include "efi.h" -#else +#include "efi.h" + +#if !SD_BOOT # include #endif -#include "efi-fundamental.h" - #define CHID_TYPES_MAX 18 /* Any chids starting from EXTRA_CHID_BASE are non-standard and are subject to change and renumeration at any time */ #define EXTRA_CHID_BASE 15 diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-util.h similarity index 99% rename from src/fundamental/cleanup-fundamental.h rename to src/fundamental/cleanup-util.h index b9f9c0724546b..538469f255ac0 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-fundamental.h" +#include "assert-util.h" /* A wrapper for 'func' to return void. * Only useful when a void-returning function is required by some API. */ diff --git a/src/fundamental/confidential-virt-fundamental.h b/src/fundamental/confidential-virt.h similarity index 100% rename from src/fundamental/confidential-virt-fundamental.h rename to src/fundamental/confidential-virt.h diff --git a/src/fundamental/edid-fundamental.c b/src/fundamental/edid.c similarity index 97% rename from src/fundamental/edid-fundamental.c rename to src/fundamental/edid.c index bfd314e44452b..980a3989b7115 100644 --- a/src/fundamental/edid-fundamental.c +++ b/src/fundamental/edid.c @@ -4,8 +4,8 @@ #include #endif -#include "edid-fundamental.h" -#include "efivars-fundamental.h" +#include "edid.h" +#include "efivars.h" #define EDID_FIXED_HEADER_PATTERN "\x00\xFF\xFF\xFF\xFF\xFF\xFF" assert_cc(sizeof_field(EdidHeader, pattern) == sizeof(EDID_FIXED_HEADER_PATTERN)); diff --git a/src/fundamental/edid-fundamental.h b/src/fundamental/edid.h similarity index 97% rename from src/fundamental/edid-fundamental.h rename to src/fundamental/edid.h index 2a76c4524de2e..6c5930f4c6ac3 100644 --- a/src/fundamental/edid-fundamental.h +++ b/src/fundamental/edid.h @@ -10,7 +10,7 @@ # include #endif -#include "macro-fundamental.h" +#include "macro.h" /* EDID structure, version 1.4 */ typedef struct EdidHeader { diff --git a/src/fundamental/efi-fundamental.h b/src/fundamental/efi.h similarity index 100% rename from src/fundamental/efi-fundamental.h rename to src/fundamental/efi.h diff --git a/src/fundamental/efivars-fundamental.c b/src/fundamental/efivars.c similarity index 98% rename from src/fundamental/efivars-fundamental.c rename to src/fundamental/efivars.c index 611b0b682d9b7..87dc1fff8f4b4 100644 --- a/src/fundamental/efivars-fundamental.c +++ b/src/fundamental/efivars.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "efivars-fundamental.h" +#include "efivars.h" static const sd_char * const table[_SECURE_BOOT_MAX] = { [SECURE_BOOT_UNSUPPORTED] = STR_C("unsupported"), diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars.h similarity index 98% rename from src/fundamental/efivars-fundamental.h rename to src/fundamental/efivars.h index fea23fa29c182..eddda7c23a0c8 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars.h @@ -6,7 +6,7 @@ #else # include #endif -#include "string-util-fundamental.h" +#include "string-util.h" /* Features of the loader, i.e. systemd-boot */ #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0) diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util.h similarity index 96% rename from src/fundamental/iovec-util-fundamental.h rename to src/fundamental/iovec-util.h index 5b693742f3a7c..7d8f65db43d2b 100644 --- a/src/fundamental/iovec-util-fundamental.h +++ b/src/fundamental/iovec-util.h @@ -5,8 +5,8 @@ #include #endif -#include "assert-fundamental.h" /* IWYU pragma: keep */ -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.h" #if SD_BOOT /* struct iovec is a POSIX userspace construct. Let's introduce it also in EFI mode, it's just so useful */ diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro.h similarity index 100% rename from src/fundamental/macro-fundamental.h rename to src/fundamental/macro.h diff --git a/src/fundamental/memory-util-fundamental.c b/src/fundamental/memory-util.c similarity index 95% rename from src/fundamental/memory-util-fundamental.c rename to src/fundamental/memory-util.c index 1a64fbe514ff8..39d116cc8b65d 100644 --- a/src/fundamental/memory-util-fundamental.c +++ b/src/fundamental/memory-util.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "memory-util-fundamental.h" +#include "memory-util.h" bool memeqbyte(uint8_t byte, const void *data, size_t length) { assert(data || length == 0); diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util.h similarity index 98% rename from src/fundamental/memory-util-fundamental.h rename to src/fundamental/memory-util.h index 7c88264053ccd..55ed19e15ca8e 100644 --- a/src/fundamental/memory-util-fundamental.h +++ b/src/fundamental/memory-util.h @@ -9,8 +9,8 @@ # include #endif -#include "assert-fundamental.h" /* IWYU pragma: keep */ -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.h" #define memzero(x, l) \ ({ \ diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index 14d956ac07edf..93a332ec8a6a1 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -3,15 +3,15 @@ fundamental_include = include_directories('.') fundamental_sources = files( - 'bootspec-fundamental.c', - 'chid-fundamental.c', - 'edid-fundamental.c', - 'efivars-fundamental.c', - 'iovec-util-fundamental.h', - 'memory-util-fundamental.c', - 'sha1-fundamental.c', - 'sha256-fundamental.c', - 'string-util-fundamental.c', + 'bootspec.c', + 'chid.c', + 'edid.c', + 'efivars.c', + 'iovec-util.h', + 'memory-util.c', + 'sha1.c', + 'sha256.c', + 'string-util.c', 'uki.c', ) diff --git a/src/fundamental/sha1-fundamental.c b/src/fundamental/sha1.c similarity index 99% rename from src/fundamental/sha1-fundamental.c rename to src/fundamental/sha1.c index b2af1e98885ed..1d48d6874afd8 100644 --- a/src/fundamental/sha1-fundamental.c +++ b/src/fundamental/sha1.c @@ -82,8 +82,8 @@ modified for use with systemd # include #endif -#include "memory-util-fundamental.h" -#include "sha1-fundamental.h" +#include "memory-util.h" +#include "sha1.h" #define SHA1_DIGEST_SIZE 20 diff --git a/src/fundamental/sha1-fundamental.h b/src/fundamental/sha1.h similarity index 100% rename from src/fundamental/sha1-fundamental.h rename to src/fundamental/sha1.h diff --git a/src/fundamental/sha256-fundamental.c b/src/fundamental/sha256.c similarity index 98% rename from src/fundamental/sha256-fundamental.c rename to src/fundamental/sha256.c index 76951ccfad2e0..ac514eddabbc2 100644 --- a/src/fundamental/sha256-fundamental.c +++ b/src/fundamental/sha256.c @@ -27,10 +27,10 @@ # include #endif -#include "assert-fundamental.h" -#include "memory-util-fundamental.h" -#include "sha256-fundamental.h" -#include "unaligned-fundamental.h" +#include "assert-util.h" +#include "memory-util.h" +#include "sha256.h" +#include "unaligned.h" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define SWAP(n) \ diff --git a/src/fundamental/sha256-fundamental.h b/src/fundamental/sha256.h similarity index 100% rename from src/fundamental/sha256-fundamental.h rename to src/fundamental/sha256.h diff --git a/src/fundamental/string-table-fundamental.h b/src/fundamental/string-table.h similarity index 96% rename from src/fundamental/string-table-fundamental.h rename to src/fundamental/string-table.h index d40251506845b..41c8158689be4 100644 --- a/src/fundamental/string-table-fundamental.h +++ b/src/fundamental/string-table.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" +#include "macro.h" #define DECLARE_STRING_TABLE_LOOKUP_TO_STRING(name, type) \ const char* name##_to_string(type i) _const_ diff --git a/src/fundamental/string-util-fundamental.c b/src/fundamental/string-util.c similarity index 99% rename from src/fundamental/string-util-fundamental.c rename to src/fundamental/string-util.c index 6877dd439a4bd..bd6b81ea14da9 100644 --- a/src/fundamental/string-util-fundamental.c +++ b/src/fundamental/string-util.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "macro-fundamental.h" -#include "string-util-fundamental.h" +#include "macro.h" +#include "string-util.h" sd_char *startswith_internal(const sd_char *s, const sd_char *prefix) { size_t l; diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util.h similarity index 97% rename from src/fundamental/string-util-fundamental.h rename to src/fundamental/string-util.h index 83b90e4e9e543..c068c1904a876 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util.h @@ -8,8 +8,8 @@ # include #endif -#include "assert-fundamental.h" /* IWYU pragma: keep */ -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.h" /* What is interpreted as whitespace? */ #define WHITESPACE " \t\n\r" diff --git a/src/fundamental/strv-fundamental.h b/src/fundamental/strv.h similarity index 84% rename from src/fundamental/strv-fundamental.h rename to src/fundamental/strv.h index 7e7e34822f515..0ede9c3b796a1 100644 --- a/src/fundamental/strv-fundamental.h +++ b/src/fundamental/strv.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" -#include "string-util-fundamental.h" +#include "macro.h" +#include "string-util.h" #define _STRV_FOREACH(s, l, i) \ for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) diff --git a/src/fundamental/unaligned-fundamental.h b/src/fundamental/unaligned.h similarity index 100% rename from src/fundamental/unaligned-fundamental.h rename to src/fundamental/unaligned.h diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 1e6d8beff274c..dcb0f8c511a3d 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -17,7 +17,7 @@ #include "install-file.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "options.h" #include "parse-argument.h" #include "path-util.h" diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 2095a0dde57af..62aaf6bf86833 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -18,7 +18,7 @@ #include "install-file.h" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "pretty-print.h" #include "qcow2-util.h" #include "ratelimit.h" diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 06ee56bd78f90..25e16f671a786 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -20,7 +20,7 @@ #include "install-file.h" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index c1c76fc898017..b6fa71c902965 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -21,7 +21,7 @@ #include "install-file.h" #include "io-util.h" #include "json-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "oci-util.h" #include "ordered-set.h" #include "path-util.h" @@ -31,7 +31,7 @@ #include "pull-oci.h" #include "rm-rf.h" #include "set.h" -#include "sha256-fundamental.h" +#include "sha256.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 c63a453177cff..c069160cb43cf 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -15,7 +15,7 @@ #include "install-file.h" #include "iovec-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "pull-common.h" #include "pull-job.h" #include "pull-raw.h" diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 453ad1187cf7b..bf62b2e780d3b 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -16,7 +16,7 @@ #include "fs-util.h" #include "install-file.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" diff --git a/src/include/override/sys/param.h b/src/include/override/sys/param.h index 88cc47edf4fe0..2a4410f526d19 100644 --- a/src/include/override/sys/param.h +++ b/src/include/override/sys/param.h @@ -3,6 +3,6 @@ /* sys/param.h from glibc unconditionally overrides the MIN() and MAX() macros which interferes with our own * MAX() macro. It also includes a bunch of other headers transitively so we don't want to include - * sys/param.h in macro-fundamental.h unconditionally. We'd like to make including this file an error but + * sys/param.h in macro.h unconditionally. We'd like to make including this file an error but * unfortunately includes it. However, doesn't actually make use of anything from * sys/param.h, so we override it with an empty file so it can't mess with our macros. */ diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index ab1ac35409cb8..b9b8c7062ef3a 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -53,7 +53,7 @@ libsystemd_network = static_library( dependencies : userspace, build_by_default : false) -libsystemd_network_includes = [includes, include_directories('.')] +libsystemd_network_includes = [include_directories('.'), includes] ############################################################ diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 9bf8889f4a35a..05074fd92b2f6 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -2,7 +2,7 @@ /* We make an exception here to our usual "include system headers first" rule because we need one of these * macros to disable a warning triggered by the glib headers. */ -#include "macro-fundamental.h" +#include "macro.h" #if HAVE_GLIB DISABLE_WARNING_FORMAT_NONLITERAL diff --git a/src/libudev/meson.build b/src/libudev/meson.build index eb9dda79ae2fc..854628aed42ce 100644 --- a/src/libudev/meson.build +++ b/src/libudev/meson.build @@ -15,7 +15,7 @@ sources += libudev_sources ############################################################ -libudev_includes = [includes, include_directories('.')] +libudev_includes = [include_directories('.'), includes] libudev_dir_path = meson.current_source_dir() diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 25c361d400409..ab2abd7daeae3 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -17,7 +17,7 @@ #include "kbd-util.h" #include "localed-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 14208bc1496e9..5ff5a1f656a9b 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -49,7 +49,7 @@ #include "logind-user.h" #include "logind-user-dbus.h" #include "logind-utmp.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "os-util.h" #include "parse-util.h" #include "path-util.h" diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index b78d39d02d777..30b43404b19e7 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -22,7 +22,7 @@ #include "logind.h" #include "logind-dbus.h" #include "logind-inhibit.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "string-table.h" diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 77f006a479d29..66c27d3d73c09 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -25,7 +25,7 @@ #include "logind-session-dbus.h" #include "logind-session-device.h" #include "logind-user.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "set.h" #include "stat-util.h" diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 72cad136650c2..420ec810080d9 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -39,7 +39,7 @@ #include "logind-user.h" #include "logind-user-dbus.h" #include "logind-varlink.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 49d51952e98e7..0595ce5bd2b05 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -24,7 +24,7 @@ #include "logind-seat.h" #include "logind-user.h" #include "logind-user-dbus.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "percent-util.h" #include "serialize.h" diff --git a/src/login/logind.c b/src/login/logind.c index 2b7984051e197..c19c1cb791a69 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -35,7 +35,7 @@ #include "logind-utmp.h" #include "logind-varlink.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "process-util.h" #include "service-util.h" diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index 6cf157ffada3f..cb623d98e5274 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -16,7 +16,7 @@ #include "limits-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "path-util.h" diff --git a/src/machine/machine.c b/src/machine/machine.c index 63fed79687e5d..56d93ae560b1c 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -26,7 +26,7 @@ #include "machine-dbus.h" #include "machined-resolve-hook.h" #include "machined.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "operation.h" #include "parse-util.h" diff --git a/src/machine/machined.c b/src/machine/machined.c index 7c1f57baac3de..c419571deed9e 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -26,7 +26,7 @@ #include "machined-dbus.h" #include "machined-varlink.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "operation.h" #include "path-lookup.h" #include "service-util.h" diff --git a/src/network/meson.build b/src/network/meson.build index 23f6f3a4fe587..9844229ebdc58 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -169,7 +169,7 @@ else libshared_static] endif -network_includes = [libsystemd_network_includes, include_directories(['.', 'netdev', 'tc'])] +network_includes = [include_directories(['.', 'netdev', 'tc']), libsystemd_network_includes] network_test_template = test_template + { 'conditions' : ['ENABLE_NETWORKD'], diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index 43729c37653db..f69c509023ab9 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -14,7 +14,7 @@ #include "extract-word.h" #include "fd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "netlink-util.h" #include "network-util.h" #include "networkctl.h" diff --git a/src/network/networkd.c b/src/network/networkd.c index 3e0666610bfe9..73e03c2a25d99 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -10,7 +10,7 @@ #include "capability-util.h" #include "daemon-util.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "networkd-conf.h" #include "networkd-manager.h" #include "networkd-manager-bus.h" diff --git a/src/nspawn/meson.build b/src/nspawn/meson.build index 815b74cb1572d..95bc461cc5a08 100644 --- a/src/nspawn/meson.build +++ b/src/nspawn/meson.build @@ -43,8 +43,8 @@ executables += [ 'sources' : nspawn_sources, 'extract' : nspawn_extract_sources, 'include_directories' : [ + include_directories('.'), executable_template['include_directories'], - include_directories('.') ], 'dependencies' : [ libmount_cflags, diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index a75582e2b4d6e..3b57a75070b82 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -14,7 +14,7 @@ #include "format-util.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" diff --git a/src/nss-resolve/meson.build b/src/nss-resolve/meson.build index 1fae4266364f0..63332913c72a2 100644 --- a/src/nss-resolve/meson.build +++ b/src/nss-resolve/meson.build @@ -6,7 +6,7 @@ modules += [ 'conditions' : ['ENABLE_NSS_RESOLVE'], 'sources' : files('nss-resolve.c'), 'version-script' : meson.current_source_dir() / 'nss-resolve.sym', - 'include_directories' : includes + - include_directories('../resolve'), + 'include_directories' : [include_directories('../resolve'), + includes], }, ] diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 9ce991262e0db..fa2ee54c5756d 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -36,7 +36,7 @@ #include "label-util.h" #include "list.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "options.h" #include "ordered-set.h" #include "parse-argument.h" diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 5052bb531e25d..cae546ff82a9f 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -21,7 +21,7 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" int manager_check_resolv_conf(const Manager *m) { struct stat st, own; diff --git a/src/resolve/resolved-socket-graveyard.c b/src/resolve/resolved-socket-graveyard.c index bfa3055dedee4..753570f7adc54 100644 --- a/src/resolve/resolved-socket-graveyard.c +++ b/src/resolve/resolved-socket-graveyard.c @@ -2,7 +2,7 @@ #include "sd-event.h" #include "alloc-util.h" -#include "assert-fundamental.h" +#include "assert-util.h" #include "log.h" #include "resolved-manager.h" #include "resolved-socket-graveyard.h" diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index aa165927a423f..aaf206ac2c7e9 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -12,7 +12,7 @@ #include "label-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "resolved-bus.h" #include "resolved-manager.h" #include "resolved-resolv-conf.h" diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index f845bcdf424b7..1d690220d1b51 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -9,7 +9,7 @@ #include "build.h" #include "copy.h" #include "crypto-util.h" -#include "efi-fundamental.h" +#include "efi.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index a167e19a90b58..77ee218996e3c 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -7,7 +7,6 @@ #include "alloc-util.h" #include "bootspec.h" -#include "bootspec-fundamental.h" #include "chase.h" #include "devnum-util.h" #include "dirent-util.h" diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 677667383c9de..0560bfb3a4ac2 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -4,6 +4,8 @@ #include "shared-forward.h" +#include "../fundamental/bootspec.h" /* IWYU pragma: export */ + typedef enum BootEntryType { BOOT_ENTRY_TYPE1, /* Boot Loader Specification Type #1 entries: *.conf files */ BOOT_ENTRY_TYPE2, /* Boot Loader Specification Type #2 entries: *.efi files (UKIs) */ diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index da7c89794542e..da7c0481a6ab9 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -3,9 +3,10 @@ #include "sd-id128.h" -#include "btrfs.h" /* IWYU pragma: export */ #include "shared-forward.h" +#include "../basic/btrfs-util.h" /* IWYU pragma: export */ + typedef struct BtrfsSubvolInfo { uint64_t subvol_id; usec_t otime; /* creation time */ diff --git a/src/shared/copy.c b/src/shared/copy.c index 5b68bb9a5ab97..fbcb8e2a30fd2 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -12,7 +12,7 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chattr-util.h" #include "copy.h" #include "dirent-util.h" diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index dd2a93844dbdd..6ed433735375b 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -28,7 +28,7 @@ #include "json-util.h" #include "log.h" #include "memory-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index e79db91ddee53..9424e22001564 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -9,7 +9,7 @@ #include "fs-util.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "nulstr-util.h" #include "path-util.h" #include "stat-util.h" diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 8483a16e9442e..17c14db3ffa19 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -47,7 +47,7 @@ #include "json-util.h" #include "libmount-util.h" #include "loop-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index d48c36c1d5c2b..4180d2a6f7df5 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -13,12 +13,12 @@ #include "fileio.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "process-util.h" #include "string-util.h" #include "strv.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" typedef struct EditFile { EditFileContext *context; diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index 8a3a40cbecba7..c205ffb9002b9 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -5,7 +5,7 @@ #include "alloc-util.h" #include "dirent-util.h" #include "efi-api.h" -#include "efi-fundamental.h" +#include "efi.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c index 74a979c184e9d..44e9c49e817c2 100644 --- a/src/shared/ethtool-util.c +++ b/src/shared/ethtool-util.c @@ -13,7 +13,7 @@ #include "extract-word.h" #include "fd-util.h" #include "log.h" -#include "macro-fundamental.h" +#include "macro.h" #include "memory-util.h" #include "parse-util.h" #include "socket-util.h" diff --git a/src/shared/generator.c b/src/shared/generator.c index 5e2f8f6e0e64f..36fdff5263be1 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -14,7 +14,7 @@ #include "generator.h" #include "initrd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index 46c3f26a9b807..b42681a289cc7 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -15,7 +15,7 @@ #include "hwdb-util.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "nulstr-util.h" #include "path-util.h" #include "sort-util.h" diff --git a/src/shared/install.c b/src/shared/install.c index d01906c205d9d..6fc43a8df4ee9 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -22,7 +22,7 @@ #include "install.h" #include "install-printf.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-lookup.h" #include "path-util.h" #include "rm-rf.h" diff --git a/src/shared/label-util.h b/src/shared/label-util.h index c7ed307dee25a..720bd06533628 100644 --- a/src/shared/label-util.h +++ b/src/shared/label-util.h @@ -2,7 +2,8 @@ #pragma once #include "shared-forward.h" -#include "label.h" /* IWYU pragma: export */ + +#include "../basic/label-util.h" /* IWYU pragma: export */ typedef enum LabelFixFlags { LABEL_IGNORE_ENOENT = 1 << 0, diff --git a/src/shared/meson.build b/src/shared/meson.build index 55cf8364d3748..4d11a2637f2cf 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -139,7 +139,7 @@ shared_sources = files( 'main-func.c', 'metrics.c', 'microhttpd-util.c', - 'mkdir-label.c', + 'mkdir.c', 'mkfs-util.c', 'module-util.c', 'mount-setup.c', @@ -204,7 +204,7 @@ shared_sources = files( 'switch-root.c', 'swtpm-util.c', 'tar-util.c', - 'tmpfile-util-label.c', + 'tmpfile-util.c', 'tomoyo-util.c', 'tpm2-util.c', 'tpm2-event-log.c', diff --git a/src/shared/mkdir-label.c b/src/shared/mkdir.c similarity index 98% rename from src/shared/mkdir-label.c rename to src/shared/mkdir.c index 36473b10669ad..1228bca19bb3e 100644 --- a/src/shared/mkdir-label.c +++ b/src/shared/mkdir.c @@ -4,7 +4,7 @@ #include "errno-util.h" #include "label-util.h" /* IWYU pragma: keep */ -#include "mkdir-label.h" +#include "mkdir.h" #include "selinux-util.h" #include "smack-util.h" diff --git a/src/shared/mkdir-label.h b/src/shared/mkdir.h similarity index 93% rename from src/shared/mkdir-label.h rename to src/shared/mkdir.h index c884190a0e25f..b02e02a8f083f 100644 --- a/src/shared/mkdir-label.h +++ b/src/shared/mkdir.h @@ -2,7 +2,8 @@ #pragma once #include "shared-forward.h" -#include "mkdir.h" + +#include "../basic/mkdir.h" /* IWYU pragma: export */ int mkdirat_label(int dirfd, const char *path, mode_t mode); diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 8c6cd2b12b69a..f402990cf747c 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -12,7 +12,7 @@ #include "fileio.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-setup.h" #include "mount-util.h" #include "mountpoint-util.h" diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 12d3da82e220e..cda0070b14e54 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -21,7 +21,7 @@ #include "hashmap.h" #include "libmount-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" diff --git a/src/shared/resize-fs.c b/src/shared/resize-fs.c index 147af7ec33da6..7a7854666486f 100644 --- a/src/shared/resize-fs.c +++ b/src/shared/resize-fs.c @@ -10,7 +10,7 @@ #include "resize-fs.h" #include "stat-util.h" #include "stdio-util.h" -#include "string-util-fundamental.h" +#include "string-util.h" int resize_fs(int fd, uint64_t sz, uint64_t *ret_size) { struct statfs sfs; diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 6c83f3fdd28d8..81ec4a9ba6d28 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -21,7 +21,6 @@ #include "alloc-util.h" #include "fd-util.h" -#include "label.h" #include "path-util.h" #include "string-util.h" #include "time-util.h" diff --git a/src/shared/smack-util.c b/src/shared/smack-util.c index e1dbf8686edda..6380a4e3af65e 100644 --- a/src/shared/smack-util.c +++ b/src/shared/smack-util.c @@ -13,7 +13,6 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" -#include "label.h" #include "label-util.h" #include "log.h" #include "path-util.h" diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c index 3f2ac99b9b0ec..ea1116983efff 100644 --- a/src/shared/socket-label.c +++ b/src/shared/socket-label.c @@ -7,7 +7,7 @@ #include "fd-util.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "selinux-util.h" #include "smack-util.h" diff --git a/src/shared/storage-util.h b/src/shared/storage-util.h index b37515bedbd43..fe78112d24b82 100644 --- a/src/shared/storage-util.h +++ b/src/shared/storage-util.h @@ -3,7 +3,6 @@ #include "sd-json.h" -#include "string-table-fundamental.h" #include "string-util.h" /* This closely follows the kernel's inode type naming, i.e. is supposed to be a subset of what diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 08710a8966400..3bb2611523510 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -11,7 +11,7 @@ #include "errno-util.h" #include "fd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "rm-rf.h" diff --git a/src/shared/tmpfile-util-label.c b/src/shared/tmpfile-util.c similarity index 95% rename from src/shared/tmpfile-util-label.c rename to src/shared/tmpfile-util.c index b928460f1bedb..9a5360d6d22df 100644 --- a/src/shared/tmpfile-util-label.c +++ b/src/shared/tmpfile-util.c @@ -4,7 +4,6 @@ #include "selinux-util.h" #include "tmpfile-util.h" -#include "tmpfile-util-label.h" int fopen_temporary_at_label( int dir_fd, diff --git a/src/shared/tmpfile-util-label.h b/src/shared/tmpfile-util.h similarity index 50% rename from src/shared/tmpfile-util-label.h rename to src/shared/tmpfile-util.h index d8920cc697f83..23db87cbb18af 100644 --- a/src/shared/tmpfile-util-label.h +++ b/src/shared/tmpfile-util.h @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" +#include "../basic/tmpfile-util.h" /* IWYU pragma: export */ -/* These functions are split out of tmpfile-util.h (and not for example just flags to the functions they - * wrap) in order to optimize linking: this way, -lselinux is needed only for the callers of these functions - * that need selinux, but not for all. */ +/* These functions extend the basic tmpfile-util.h API with shared-only functionality (selinux labelling). + * Targets that link libshared automatically pick up this version via -Isrc/shared; targets that only have + * src/basic on their include path fall through to the basic header. */ int fopen_temporary_at_label(int dir_fd, const char *target, const char *path, FILE **f, char **temp_path); static inline int fopen_temporary_label(const char *target, const char *path, FILE **f, char **temp_path) { diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index b5a8ec1c1eaf7..f2f63a94a43fc 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -4,7 +4,7 @@ #include "bitfield.h" #include "iovec-util.h" #include "shared-forward.h" -#include "sha256-fundamental.h" +#include "sha256.h" typedef enum TPM2Flags { TPM2_FLAGS_USE_PIN = 1 << 0, diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c index 68c349a195c67..5ad2066557284 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -13,7 +13,6 @@ #include "path-lookup.h" #include "path-util.h" #include "string-util.h" -#include "strv-fundamental.h" #include "strv.h" #include "systemctl.h" #include "systemctl-daemon-reload.h" diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 05a3e2db509e4..cdbc053f6b3d2 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -40,7 +40,7 @@ #include "strv.h" #include "sync-util.h" #include "time-util.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" #include "uid-classification.h" #include "uid-range.h" #include "user-util.h" diff --git a/src/test/test-chid.c b/src/test/test-chid.c index 86b748016aba0..84f7eefac38ab 100644 --- a/src/test/test-chid.c +++ b/src/test/test-chid.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "chid-fundamental.h" +#include "chid.h" #include "tests.h" static const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { diff --git a/src/test/test-label.c b/src/test/test-label.c index 5ce9988ce277d..3bd96a3a1b794 100644 --- a/src/test/test-label.c +++ b/src/test/test-label.c @@ -7,7 +7,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" -#include "label.h" +#include "label-util.h" #include "path-util.h" #include "string-util.h" #include "tests.h" diff --git a/src/test/test-sha1.c b/src/test/test-sha1.c index 3a5ff148df47e..6356400c62887 100644 --- a/src/test/test-sha1.c +++ b/src/test/test-sha1.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hexdecoct.h" -#include "sha1-fundamental.h" +#include "sha1.h" #include "tests.h" static void sha1_process_string(const char *key, struct sha1_ctx *ctx) { diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index b6007f6207ed6..8b7b676598702 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -42,7 +42,7 @@ #include "log.h" #include "loop-util.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "offline-passwd.h" 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 d675e4269ac16..c7b1fb3e1fbe8 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -28,7 +28,7 @@ #include "inotify-util.h" #include "io-util.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "options.h" #include "path-util.h" #include "pidref.h" diff --git a/src/udev/meson.build b/src/udev/meson.build index 7e2435d0fab68..375645e7593ed 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -150,8 +150,8 @@ udev_binaries_dict = [ keyboard_keys_from_name_inc, 'extract' : udevadm_extract_sources, 'include_directories' : [ - libexec_template['include_directories'], include_directories('.', 'net'), + libexec_template['include_directories'], ], 'dependencies' : udev_dependencies, 'link_with' : udev_link_with, diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 1acbe2f0909c8..dfb4b497227fe 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -12,7 +12,7 @@ #include "label-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "namespace-util.h" #include "parse-util.h" diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index 4d1ecf5d116a1..ba28a8b809e87 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -20,7 +20,7 @@ #include "hashmap.h" #include "hexdecoct.h" #include "label-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "selinux-util.h" From ebd70a53d4c7f66c415f505e3eec2f7fc0d67eea Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Thu, 21 May 2026 08:07:51 +0200 Subject: [PATCH 2065/2155] vmspawn: add shell completion for --coco Fixes 4f8215add24ac9018fb68399f0b957cf0eb3b0c6. Signed-off-by: Paul Meyer --- shell-completion/bash/systemd-vmspawn | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index 62fa5ab52065d..efb4b1059962c 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -41,6 +41,7 @@ _systemd_vmspawn() { [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 --bind-volume' [IMAGE_FORMAT]='--image-format' [IMAGE_DISK_TYPE]='--image-disk-type' + [COCO]='--coco' ) _init_completion || return @@ -68,6 +69,8 @@ _systemd_vmspawn() { comps='raw qcow2' elif __contains_word "$prev" ${OPTS[IMAGE_DISK_TYPE]}; then comps='virtio-blk virtio-scsi nvme' + elif __contains_word "$prev" ${OPTS[COCO]}; then + comps='no sev-snp' elif __contains_word "$prev" ${OPTS[ARG]}; then comps='' else From 9f25feb4ed18bb3d1e21de932279b2b1b3098846 Mon Sep 17 00:00:00 2001 From: Rocker Zhang Date: Sat, 16 May 2026 13:07:56 +0800 Subject: [PATCH 2066/2155] logind: keep lingering users at startup-time GC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit manager_startup() runs manager_gc(m, /* drop_not_started= */ false) before the user_start() loop. user_may_gc()'s linger guard requires user_unit_active() to be true to keep a user, but at this point the per-user units have not been started yet, so for any lingering user that ended up in the user_gc_queue the guard falls through and manager_gc frees the User struct before user_start() ever runs. This only manifests after `systemctl soft-reboot`, because /run is tmpfs and survives soft-reboot: /run/systemd/users/UID files persist, and manager_enumerate_users() in src/login/logind.c explicitly calls user_add_to_gc_queue() for every UID it loads from there. Cold boot is unaffected because /run is empty, so the linger users that come in via manager_enumerate_linger_users() never enter the GC queue at all and reach user_start() directly. Special-case the startup-time GC: if a linger file exists, keep the user regardless of unit state — user_start() is about to run and will queue the appropriate jobs. Steady-state GC (drop_not_started=true, in the event loop) still requires user_unit_active() so we don't hold on to records for lingering users whose units genuinely died. Fixes: https://github.com/systemd/systemd/issues/41789 Co-developed-by: Claude Opus 4.7 --- src/login/logind-user.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 0595ce5bd2b05..11ca04dc8de24 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -763,8 +763,14 @@ bool user_may_gc(User *u, bool drop_not_started) { /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check * if any of the three units that we maintain for this user is still around. If none of them is, - * there's no need to keep this user around even if lingering is enabled. */ - if (user_check_linger_file(u) > 0 && user_unit_active(u)) + * there's no need to keep this user around even if lingering is enabled. + * + * Exception: at startup-time GC (drop_not_started=false) the per-user units have not yet been + * started by manager_startup(), so requiring user_unit_active() here would unconditionally GC + * every lingering user before we get a chance to user_start() them. That breaks after a + * soft-reboot, where /run/systemd/users is preserved and feeds lingering users into the GC + * queue (cold boot is unaffected because /run is empty). See #41789. */ + if (user_check_linger_file(u) > 0 && (!drop_not_started || user_unit_active(u))) return false; /* Check if our job is still pending */ From 06ce0f90a57e35f9fbbbe4dff15ae5ae17381034 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 28 Apr 2026 13:27:43 +0900 Subject: [PATCH 2067/2155] sd-dhcp-relay: introduce new DHCP relay agent Previously, sd-dhcp-server can be run as a DHCP relay agent. But, DHCP server and DHCP relay agent behave completely differently, hence there is almost no code that can be shared between the two modes. Let's split out the DHCP relay agent feature from sd-dhcp-server. The new DHCP relay agent supports: - multiple upstream and downstream interfaces, - gateway address (giaddr field in DHCP message header) is configurable, - supports more DHCP relay agent information sub-options, - each interface has their own socket fd, and each socket is bound to the interface, so that we can enable/disable each interface safely without affecting other interfaces, and we can filter out any unexpected packets from unmanaged interfaces, networkd integration and test cases will be added later. --- .../dhcp-relay-downstream.c | 503 ++++++++++++++++++ src/libsystemd-network/dhcp-relay-interface.c | 334 ++++++++++++ src/libsystemd-network/dhcp-relay-internal.h | 114 ++++ src/libsystemd-network/dhcp-relay-upstream.c | 150 ++++++ src/libsystemd-network/meson.build | 4 + src/libsystemd-network/sd-dhcp-relay.c | 136 +++++ src/libsystemd/sd-common/sd-forward.h | 4 +- src/systemd/meson.build | 1 + src/systemd/sd-dhcp-relay.h | 71 +++ 9 files changed, 1316 insertions(+), 1 deletion(-) create mode 100644 src/libsystemd-network/dhcp-relay-downstream.c create mode 100644 src/libsystemd-network/dhcp-relay-interface.c create mode 100644 src/libsystemd-network/dhcp-relay-internal.h create mode 100644 src/libsystemd-network/dhcp-relay-upstream.c create mode 100644 src/libsystemd-network/sd-dhcp-relay.c create mode 100644 src/systemd/sd-dhcp-relay.h diff --git a/src/libsystemd-network/dhcp-relay-downstream.c b/src/libsystemd-network/dhcp-relay-downstream.c new file mode 100644 index 0000000000000..dd825d02a21fb --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-downstream.c @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "siphash24.h" +#include "socket-util.h" + +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->circuit_id, iov); +} + +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->vss, iov); +} + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options) { + assert(interface); + assert(!interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + return unref_and_replace_new_ref(interface->extra_options, options, tlv_ref, tlv_unref); +} + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + if (address) + interface->gateway_address = *address; + else + interface->gateway_address = (struct in_addr) {}; + + return 0; +} + +static void downstream_hash_func(const sd_dhcp_relay_interface *interface, struct siphash *state) { + int b; + + assert(interface); + assert(!interface->upstream); + assert(state); + + siphash24_compress_typesafe(interface->gateway_address, state); + + b = iovec_is_set(&interface->circuit_id); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->circuit_id, state); + + b = iovec_is_set(&interface->vss); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->vss, state); +} + +static int downstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + int r; + + assert(a); + assert(!a->upstream); + assert(b); + assert(!b->upstream); + + r = CMP(a->gateway_address.s_addr, b->gateway_address.s_addr); + if (r != 0) + return r; + + r = iovec_memcmp(&a->circuit_id, &b->circuit_id); + if (r != 0) + return r; + + return iovec_memcmp(&a->vss, &b->vss); +} + +DEFINE_PRIVATE_HASH_OPS( + downstream_hash_ops, + sd_dhcp_relay_interface, + downstream_hash_func, + downstream_compare_func); + +int downstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + /* Do not use a Set; otherwise, we cannot deduplicate entries. */ + return hashmap_ensure_put(&interface->relay->downstream_interfaces, &downstream_hash_ops, interface, interface); +} + +void downstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + + hashmap_remove_value(interface->relay->downstream_interfaces, interface, interface); +} + +void downstream_done(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(!interface->upstream); + + downstream_unregister(interface); + iovec_done(&interface->circuit_id); + iovec_done(&interface->vss); + interface->extra_options = tlv_unref(interface->extra_options); +} + +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret) { + int r; + + assert(relay); + assert(message); + + /* RFC 3046 section 2.2: + * DHCP servers claiming to support the Relay Agent Information option SHALL echo the entire contents + * of the Relay Agent Information option in all replies. + * + * So, first try to find the suitable downstream interface by the gateway address and circuit ID in + * the reply message. */ + sd_dhcp_relay_interface key = { + .gateway_address.s_addr = message->header.giaddr, + }; + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + return r; + + if (agent_info) { + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &key.circuit_id); + if (r < 0 && r != -ENODATA) + return r; + + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &key.vss); + if (r < 0 && r != -ENODATA) + return r; + } + + sd_dhcp_relay_interface *interface = hashmap_get(relay->downstream_interfaces, &key); + if (!interface) { + /* Some DHCP servers may not understand the Relay Agent Information option and may not echo + * it back. To support this case, we fall back to finding a suitable downstream interface + * using only the gateway address. Note that if the downstream network uses VRF or the Link + * Selection sub-option, multiple interfaces may share the same gateway address. In such + * cases, we cannot reliably determine the correct downstream interface, so we must drop the + * packet. */ + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, relay->downstream_interfaces) { + if (i->gateway_address.s_addr != message->header.giaddr) + continue; + + if (interface) + /* multiple interfaces have the same gateway address?? */ + return -ENOTUNIQ; + + interface = i; + } + } + if (!interface) + return -ENODEV; + + assert(!interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +static int downstream_append_relay_agent_information( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(message); + + _cleanup_(tlv_done) TLV tlv = TLV_INIT(TLV_DHCP4_SUBOPTION); + + /* First, set per-interface options. */ + if (iovec_is_set(&interface->circuit_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &interface->circuit_id); + if (r < 0) + return r; + } + + if (iovec_is_set(&interface->vss)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &interface->vss); + if (r < 0) + return r; + } + + if (!in4_addr_equal(&interface->address, &interface->gateway_address)) { + /* RFC 3527 section 3 + * The link-selection sub-option is used by any DHCP relay agent that desires to specify a + * subnet/link for a DHCP client request that it is relaying but needs the subnet/link + * specification to be different from the IP address the DHCP server should use when + * communicating with the relay agent. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_LINK_SELECTION, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->extra_options); + if (r < 0) + return r; + + /* Then, set agent-wide options. */ + if (iovec_is_set(&interface->relay->remote_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_REMOTE_ID, &interface->relay->remote_id); + if (r < 0) + return r; + } + + if (interface->relay->server_identifier_override) { + /* RFC 5107 section 1: + * This DHCP relay agent suboption, Server Identifier Override, allows the relay agent to + * tell the DHCP server what value to place into the Server Identifier option. Using this, + * the relay agent can force a host in RENEWING state to send DHCPREQUEST messages to the + * relay agent instead of directly to the server. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + + /* RFC 5107 section 4: + * DHCP relay agents implementing this suboption SHOULD also implement and use the DHCPv4 + * Relay Agent Flags Suboption in order to specify whether the DHCP relay agent received the + * original message as a broadcast or unicast. */ + uint8_t flags = 0; + SET_FLAG(flags, DHCP_RELAY_AGENT_FLAG_UNICAST, + pktinfo && + pktinfo->ipi_addr.s_addr != INADDR_BROADCAST && + pktinfo->ipi_addr.s_addr != interface->subnet_broadcast.s_addr); + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_FLAGS, sizeof(uint8_t), &flags); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->relay->extra_options); + if (r < 0) + return r; + + if (tlv_isempty(&tlv)) + return 0; + + return dhcp_message_append_option_sub_tlv(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &tlv); +} + +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(iov); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* RFC 1542 section 4.1.1: + * The relay agent MUST silently discard BOOTREQUEST messages whose 'hops' field exceeds the value 16. */ + if (message->header.hops >= 16) + return 0; + message->header.hops++; + + /* RFC 3046 section 2.1.1: + * Relay agents configured to add a Relay Agent option which receive a client DHCP packet with a + * nonzero giaddr SHALL discard the packet if the giaddr spoofs a giaddr address implemented by the + * local agent itself. */ + if (message->header.giaddr != INADDR_ANY) { + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, interface->relay->interfaces) { + if (i->upstream) + continue; + if (message->header.giaddr == i->address.s_addr || + message->header.giaddr == i->gateway_address.s_addr) + return -EBADMSG; + } + } + + /* RFC 1542 section 4.1.1: + * If the relay agent does decide to relay the request, it MUST examine the 'giaddr' ("gateway" IP + * address) field. If this field is zero, the relay agent MUST fill this field with the IP address of + * the interface on which the request was received. (snip) If the 'giaddr' field contains some + * non-zero value, the 'giaddr' field MUST NOT be modified. + * + * RFC 3046 section 2.1.1: + * the relay agent SHALL forward any received DHCP packet with a valid non-zero giaddr WITHOUT adding + * any relay agent options. Per RFC 2131, it shall also NOT modify the giaddr value. + * + * Therefore, we set giaddr and the Relay Agent Information option here only when the giaddr in the + * received message is zero. */ + if (message->header.giaddr == INADDR_ANY) { + message->header.giaddr = interface->gateway_address.s_addr; + + /* RFC 3046 section 2.1: + * Relay agents receiving a DHCP packet from an untrusted circuit with giaddr set to zero + * (indicating that they are the first-hop router) but with a Relay Agent Information option + * already present in the packet SHALL discard the packet and increment an error count. */ + if (dhcp_message_has_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)) + return -EBADMSG; + + r = downstream_append_relay_agent_information(interface, message, pktinfo); + if (r < 0) + return r; + } + + log_dhcp_relay_interface(interface, "Received BOOTREQUEST (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *upstream; + r = upstream_get(interface->relay, &upstream); + if (r < 0) + return r; + + return upstream_send_message(upstream, message); +} + +static int downstream_acquire_raw_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + assert(!interface->upstream); + + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it. */ + return interface->socket_fd; + + if (interface->raw_socket_fd >= 0) + /* Already opened. */ + return interface->raw_socket_fd; + + /* This is a send-only socket, hence it is opened with protocol=0, and do not call bind(). + * The interface binding will be done on send. */ + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, /* protocol= */ 0)); + if (fd < 0) + return fd; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + return interface->raw_socket_fd = TAKE_FD(fd); +} + +static int downstream_send_l2_unicast( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct hw_addr_data *hw_addr) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.yiaddr != INADDR_ANY); + assert(!hw_addr_is_null(hw_addr)); + + int fd = downstream_acquire_raw_socket(interface); + if (fd < 0) + return fd; + + r = dhcp_message_send_raw( + message, + fd, + interface->ifindex, + interface->address.s_addr, + interface->port, + hw_addr, + message->header.yiaddr, + DHCP_PORT_CLIENT, + interface->ip_service_type); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (L2 unicast).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = message->header.yiaddr })); + return 0; +} + +static int downstream_send_udp( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + be32_t address) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(address != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + address, + DHCP_PORT_CLIENT); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = address })); + return 0; +} + +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.op == BOOTREPLY); + + /* See RFC 2131 Section 4.1 + * + * (Note, we are a relay agent, hence conditions for giaddr in the statements are ignored.) */ + + uint8_t type; + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &type); + if (r < 0) + return r; + + /* the server broadcasts any DHCPNAK messages to 0xffffffff. */ + if (type == DHCP_NAK) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If (...) the ’ciaddr’ field is nonzero, then the server unicasts DHCPOFFER and DHCPACK messages + * to the address in ’ciaddr’. */ + if (message->header.ciaddr != INADDR_ANY) + return downstream_send_udp(interface, message, message->header.ciaddr); + + /* If (...) ’ciaddr’ is zero, and the broadcast bit is set, then the server broadcasts DHCPOFFER + * and DHCPACK messages to 0xffffffff. + * + * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. + * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, for other + * message types we do not support, also broadcast if 'yiaddr' is zero.) */ + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If the broadcast bit is not set (...) and ’ciaddr’ is zero, then the server unicasts DHCPOFFER + * and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ + return downstream_send_l2_unicast(interface, message, &hw_addr); +} diff --git a/src/libsystemd-network/dhcp-relay-interface.c b/src/libsystemd-network/dhcp-relay-interface.c new file mode 100644 index 0000000000000..218f56fc04c74 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-interface.c @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "socket-util.h" +#include "string-util.h" + +static sd_dhcp_relay_interface* dhcp_relay_interface_free(sd_dhcp_relay_interface *interface) { + if (!interface) + return NULL; + + assert(interface->relay); + + sd_event_source_disable_unref(interface->io_event_source); + safe_close(interface->socket_fd); + safe_close(interface->raw_socket_fd); + + if (interface->upstream) + upstream_done(interface); + else + downstream_done(interface); + + hashmap_remove_value(interface->relay->interfaces, INT_TO_PTR(interface->ifindex), interface); + sd_dhcp_relay_unref(interface->relay); + + free(interface->ifname); + return mfree(interface); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface, dhcp_relay_interface_free); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret) { + int r; + + assert_return(relay, -EINVAL); + assert_return(ifindex > 0 || (ifindex == DHCP_RELAY_IFINDEX_UNBOUND && !!is_upstream), -EINVAL); + assert_return(ret, -EINVAL); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = new(sd_dhcp_relay_interface, 1); + if (!interface) + return -ENOMEM; + + /* RFC 1542 section 5.4: + * The server SHOULD next check the 'giaddr' field. If this field is non-zero, the server SHOULD send + * the BOOTREPLY as an IP unicast to the IP address identified in the 'giaddr' field. The UDP + * destination port MUST be set to BOOTPS (67). + * + * Hence, the relay agent needs to use DHCP_PORT_SERVER (67) for both source and destination port. */ + *interface = (sd_dhcp_relay_interface) { + .n_ref = 1, + .relay = sd_dhcp_relay_ref(relay), + .upstream = !!is_upstream, + .ifindex = ifindex, + .port = DHCP_PORT_SERVER, + .socket_fd = -EBADF, + .raw_socket_fd = -EBADF, + .ip_service_type = IPTOS_CLASS_CS6, /* Defaults to CS6 (Internetwork Control). */ + }; + + r = hashmap_ensure_put(&relay->interfaces, NULL, INT_TO_PTR(interface->ifindex), interface); + if (r < 0) + return r; + + *ret = TAKE_PTR(interface); + return 0; +} + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname) { + assert_return(interface, -EINVAL); + + return free_and_strdup(&interface->ifname, ifname); +} + +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret) { + int r; + + assert_return(interface, -EINVAL); + + r = get_ifname(interface->ifindex, &interface->ifname); + if (r < 0) + return r; + + if (ret) + *ret = interface->ifname; + + return 0; +} + +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + assert_return(prefixlen <= sizeof(struct in_addr) * 8, -EINVAL); + + if (address) + interface->address = *address; + else + interface->address = (struct in_addr) {}; + + if (in4_addr_is_set(&interface->address)) { + interface->address_prefixlen = prefixlen; + + struct in_addr netmask; + in4_addr_prefixlen_to_netmask(&netmask, prefixlen); + interface->subnet_broadcast.s_addr = (interface->address.s_addr & netmask.s_addr) | ~netmask.s_addr; + } else { + interface->address_prefixlen = 0; + interface->subnet_broadcast = (struct in_addr) {}; + } + + return 0; +} + +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen) { + assert_return(interface, -EINVAL); + + if (ret_address) + *ret_address = interface->address; + if (ret_prefixlen) + *ret_prefixlen = interface->address_prefixlen; + + return in4_addr_is_set(&interface->address); +} + +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->port = port; + return 0; +} + +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->ip_service_type = type; + return 0; +} + +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface) { + return interface && sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0; +} + +static int interface_open_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + if (interface->ifindex > 0) { + r = socket_bind_to_ifindex(fd, interface->ifindex); + if (r < 0) + return r; + } + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, interface->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(interface->port), + .in.sin_addr.s_addr = INADDR_ANY, + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +static int interface_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay_interface *interface = ASSERT_PTR(userdata); + int r; + + assert(fd >= 0); + + ssize_t buflen = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + return 0; + if (buflen < 0) { + log_dhcp_relay_interface_errno( + interface, buflen, + "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) + return log_oom_debug(); + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + + ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); + if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) + return 0; + if (len < 0) { + log_dhcp_relay_interface_errno( + interface, len, + "Could not receive message, ignoring: %m"); + return 0; + } + + if (interface->upstream) + r = upstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + else + r = downstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + if (r < 0) + log_dhcp_relay_interface_errno( + interface, r, + "Could not process message, ignoring: %m"); + + return 0; +} + +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface) { + int r; + + assert_return(interface, -EINVAL); + assert_return(interface->relay, -ESTALE); + assert_return(interface->relay->event, -EINVAL); + + if (in4_addr_is_null(&interface->relay->server_address) || + (!interface->upstream && in4_addr_is_null(&interface->address)) || + (!interface->upstream && in4_addr_is_null(&interface->gateway_address))) + return -EADDRNOTAVAIL; + + if (sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0) + return 0; /* Already started. */ + + _cleanup_close_ int fd_close = -EBADF; + int fd; + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket + * even if we fail to set up the event source. */ + fd = interface->socket_fd; + else { + /* Otherwise, open a new socket. */ + fd = fd_close = interface_open_socket(interface); + if (fd < 0) + return fd; + } + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(interface->relay->event, &s, fd, EPOLLIN, + interface_receive_message, interface); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, interface->relay->event_priority); + if (r < 0) + return r; + + const char *name, *description; + if (sd_dhcp_relay_interface_get_ifname(interface, &name) >= 0) + description = strjoina("dhcp-relay-interface-io-event-source-", name); + else + description = "dhcp-relay-interface-io-event-source"; + (void) sd_event_source_set_description(s, description); + + if (fd_close >= 0) { + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + TAKE_FD(fd_close); + } + + /* This may potentially fail, in which case the event source should be discarded. */ + if (interface->upstream) + r = upstream_register(interface); + else + r = downstream_register(interface); + if (r < 0) + return r; + + sd_event_source_disable_unref(interface->io_event_source); + interface->io_event_source = TAKE_PTR(s); + return 0; +} + +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface) { + if (!interface) + return 0; + + interface->raw_socket_fd = safe_close(interface->raw_socket_fd); + interface->io_event_source = sd_event_source_disable_unref(interface->io_event_source); + + if (interface->upstream) + upstream_unregister(interface); + else + downstream_unregister(interface); + return 0; +} diff --git a/src/libsystemd-network/dhcp-relay-internal.h b/src/libsystemd-network/dhcp-relay-internal.h new file mode 100644 index 0000000000000..feb08a606cfdf --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-internal.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp-relay.h" + +#include "dhcp-message.h" +#include "ether-addr-util.h" +#include "network-common.h" +#include "sd-forward.h" +#include "tlv-util.h" + +#define DHCP_RELAY_IFINDEX_UNBOUND (-100) + +struct sd_dhcp_relay { + unsigned n_ref; + + sd_event *event; + int event_priority; + + Hashmap *interfaces; /* All interfaces by their ifindex. */ + Prioq *upstream_interfaces; /* Active upstream interfaces by their priorities. */ + Hashmap *downstream_interfaces; /* Active downstream interfaces by their gateway address, circuit ID, and VSS. */ + + struct in_addr server_address; + uint16_t server_port; + + /* Global Relay Agent Information option (82) */ + struct iovec remote_id; /* Agent Remote ID Sub-option (2) */ + bool server_identifier_override; /* Relay Agent Flags (10) and Server Identifier Override Sub-option (11) */ + TLV *extra_options; +}; + +struct sd_dhcp_relay_interface { + unsigned n_ref; + + sd_dhcp_relay *relay; + bool upstream; + + int ifindex; + char *ifname; + + /* The address used for: + * - the source IP of forwarded packets (both downstream and upstream), + * - the Server Identifier Override Sub-option (when sd_dhcp_relay.server_identifier_override is true), + * - the Link Selection Sub-option (when address != gateway_address). + * Typically, this is an address of the interface itself, but we can specify an address of another + * interface (e.g., for IP unnumbered setups). */ + struct in_addr address; + uint8_t address_prefixlen; + /* subnet-directed broadcast address, e.g. 192.0.2.255 when address is 192.0.2.1/24. */ + struct in_addr subnet_broadcast; + uint16_t port; + + uint8_t ip_service_type; /* a.k.a. TOS */ + int socket_fd; /* socket fd set externally, used by unit tests */ + int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */ + sd_event_source *io_event_source; + + /* Mutually exclusive fields depending on the 'upstream' boolean */ + union { + /* Upstream specific */ + struct { + int priority; + unsigned priority_idx; + }; + + /* Downstream specific */ + struct { + /* The address set in the giaddr field of the DHCP message header. Typically, it is + * the same as 'address' above, but we can specify a different address, and it does + * not need to be an address assigned to the interface. */ + struct in_addr gateway_address; + + /* Per-interface Relay Agent Information option (82) */ + struct iovec circuit_id; /* Agent Circuit ID Sub-option (1) */ + struct iovec vss; /* DHCPv4 Virtual Subnet Selection Sub-Option (151) */ + TLV *extra_options; + }; + }; +}; + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options); + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options); +int downstream_register(sd_dhcp_relay_interface *interface); +void downstream_unregister(sd_dhcp_relay_interface *interface); +void downstream_done(sd_dhcp_relay_interface *interface); +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret); +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +int upstream_register(sd_dhcp_relay_interface *interface); +void upstream_unregister(sd_dhcp_relay_interface *interface); +void upstream_done(sd_dhcp_relay_interface *interface); +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret); +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +#define log_dhcp_relay_interface_errno(interface, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + error, fmt, ##__VA_ARGS__) +#define log_dhcp_relay_interface(interface, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/dhcp-relay-upstream.c b/src/libsystemd-network/dhcp-relay-upstream.c new file mode 100644 index 0000000000000..f1b4189153b39 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-upstream.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "prioq.h" + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority) { + assert_return(interface, -EINVAL); + assert_return(interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->priority = priority; + return 0; +} + +static int upstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + assert(a); + assert(a->upstream); + assert(b); + assert(b->upstream); + + /* Higher priority first */ + return CMP(b->priority, a->priority); +} + +int upstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + interface->priority_idx = PRIOQ_IDX_NULL; + return prioq_ensure_put(&interface->relay->upstream_interfaces, upstream_compare_func, interface, &interface->priority_idx); +} + +void upstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + + (void) prioq_remove(interface->relay->upstream_interfaces, interface, &interface->priority_idx); +} + +void upstream_done(sd_dhcp_relay_interface *interface) { + upstream_unregister(interface); +} + +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret) { + sd_dhcp_relay_interface *interface = prioq_peek(relay->upstream_interfaces); + if (!interface) + return -ENETDOWN; + + assert(interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(iov); + + if (pktinfo && pktinfo->ipi_ifindex > 0 && + interface->ifindex < 0 && + hashmap_contains(interface->relay->interfaces, INT_TO_PTR(pktinfo->ipi_ifindex))) + return 0; /* This message is not for the catch-all interface. */ + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREPLY, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + if (message->header.giaddr == INADDR_ANY) + return 0; /* Not a relay message, so it is probably not for us. */ + + if (pktinfo && pktinfo->ipi_addr.s_addr != message->header.giaddr) + return -EBADMSG; + + log_dhcp_relay_interface(interface, "Received BOOTREPLY (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *downstream; + r = downstream_get(interface->relay, message, &downstream); + if (r < 0) + return r; + + /* RFC 3046 abstract: + * The DHCP Server echoes the option back verbatim to the relay agent in server-to-client + * replies, and the relay agent strips the option before forwarding the reply to the client. + * + * RFC 3046 section 2.1: + * The Relay Agent Information option echoed by a server MUST be removed by either the relay + * agent or the trusted downstream network element which added it when forwarding a + * server-to-client response back to the client. + * + * Here, we do not check the contents of the option, and unconditionally remove it. */ + dhcp_message_remove_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + return downstream_send_message(downstream, message); +} + +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(interface->upstream); + assert(message); + assert(message->header.op == BOOTREQUEST); + assert(message->header.giaddr != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + interface->relay->server_address.s_addr, + interface->relay->server_port); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREQUEST (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&interface->relay->server_address)); + return 0; +} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index b9b8c7062ef3a..5d84b9934aadc 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -9,6 +9,9 @@ libsystemd_network_sources = files( 'dhcp-option.c', 'dhcp-packet.c', 'dhcp-protocol.c', + 'dhcp-relay-downstream.c', + 'dhcp-relay-interface.c', + 'dhcp-relay-upstream.c', 'dhcp-route.c', 'dhcp6-network.c', 'dhcp6-option.c', @@ -25,6 +28,7 @@ libsystemd_network_sources = files( 'sd-dhcp-client.c', 'sd-dhcp-duid.c', 'sd-dhcp-lease.c', + 'sd-dhcp-relay.c', 'sd-dhcp-server-lease.c', 'sd-dhcp-server.c', 'sd-dhcp6-client.c', diff --git a/src/libsystemd-network/sd-dhcp-relay.c b/src/libsystemd-network/sd-dhcp-relay.c new file mode 100644 index 0000000000000..1442a43fda7d2 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-relay.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "prioq.h" + +static sd_dhcp_relay* dhcp_relay_free(sd_dhcp_relay *relay) { + if (!relay) + return NULL; + + assert(hashmap_isempty(relay->interfaces)); + hashmap_free(relay->interfaces); + assert(hashmap_isempty(relay->downstream_interfaces)); + hashmap_free(relay->downstream_interfaces); + assert(prioq_isempty(relay->upstream_interfaces)); + prioq_free(relay->upstream_interfaces); + + sd_event_unref(relay->event); + + iovec_done(&relay->remote_id); + tlv_unref(relay->extra_options); + return mfree(relay); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay, sd_dhcp_relay, dhcp_relay_free); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret) { + assert_return(ret, -EINVAL); + + sd_dhcp_relay *relay = new(sd_dhcp_relay, 1); + if (!relay) + return -ENOMEM; + + *relay = (sd_dhcp_relay) { + .n_ref = 1, + .server_port = DHCP_PORT_SERVER, + }; + + *ret = TAKE_PTR(relay); + return 0; +} + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay) { + if (!relay) + return false; + + return + !prioq_isempty(relay->upstream_interfaces) || + !hashmap_isempty(relay->downstream_interfaces); +} + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority) { + int r; + + assert_return(relay, -EINVAL); + assert_return(!relay->event, -EBUSY); + + if (event) + relay->event = sd_event_ref(event); + else { + r = sd_event_default(&relay->event); + if (r < 0) + return r; + } + + relay->event_priority = priority; + return 0; +} + +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->event = sd_event_unref(relay->event); + return 0; +} + +sd_event* sd_dhcp_relay_get_event(sd_dhcp_relay *relay) { + assert_return(relay, NULL); + + return relay->event; +} + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + if (address) + relay->server_address = *address; + else + relay->server_address = (struct in_addr) {}; + + return 0; +} + +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret) { + assert_return(relay, -EINVAL); + + if (ret) + *ret = relay->server_address; + + return in4_addr_is_set(&relay->server_address); +} + +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->server_port = port; + return 0; +} + +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov) { + assert_return(relay, -EINVAL); + + return iovec_done_and_memdup(&relay->remote_id, iov); +} + +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b) { + assert_return(relay, -EINVAL); + + relay->server_identifier_override = !!b; + return 0; +} + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options) { + assert(relay); + + return unref_and_replace_new_ref(relay->extra_options, options, tlv_ref, tlv_unref); +} diff --git a/src/libsystemd/sd-common/sd-forward.h b/src/libsystemd/sd-common/sd-forward.h index 8abe655209dec..ba800bbd0164d 100644 --- a/src/libsystemd/sd-common/sd-forward.h +++ b/src/libsystemd/sd-common/sd-forward.h @@ -79,8 +79,10 @@ typedef struct sd_ipv4ll sd_ipv4ll; typedef struct sd_dhcp_client sd_dhcp_client; typedef struct sd_dhcp_lease sd_dhcp_lease; typedef struct sd_dhcp_route sd_dhcp_route; -typedef struct sd_dns_resolver sd_dns_resolver; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; typedef struct sd_dhcp_server sd_dhcp_server; +typedef struct sd_dns_resolver sd_dns_resolver; typedef struct sd_ndisc sd_ndisc; typedef struct sd_radv sd_radv; typedef struct sd_dhcp6_client sd_dhcp6_client; diff --git a/src/systemd/meson.build b/src/systemd/meson.build index ad455d73b217b..ca5444310ff86 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -29,6 +29,7 @@ _not_installed_headers = [ 'sd-dhcp-duid.h', 'sd-dhcp-lease.h', 'sd-dhcp-protocol.h', + 'sd-dhcp-relay.h', 'sd-dhcp-server-lease.h', 'sd-dhcp-server.h', 'sd-dhcp6-client.h', diff --git a/src/systemd/sd-dhcp-relay.h b/src/systemd/sd-dhcp-relay.h new file mode 100644 index 0000000000000..47c42a21ddb75 --- /dev/null +++ b/src/systemd/sd-dhcp-relay.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcprelayhfoo +#define foosddhcprelayhfoo + +/*** + 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 +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_event sd_event; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; + +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay); +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret); + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay); + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority); +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay); +sd_event *sd_dhcp_relay_get_event(sd_dhcp_relay *relay); + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address); +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret); +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port); +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov); +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret); + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname); +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret); +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen); +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen); +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port); +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type); +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface); + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address); +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov); +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov); + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay, sd_dhcp_relay_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface_unref); + +_SD_END_DECLARATIONS; + +#endif From 0325022c15803458a580fe3ea6c14a835b540765 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 23:18:32 +0900 Subject: [PATCH 2068/2155] test: add unit test for DHCP relay agent --- src/libsystemd-network/meson.build | 3 + src/libsystemd-network/test-dhcp-relay.c | 383 +++++++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 src/libsystemd-network/test-dhcp-relay.c diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 5d84b9934aadc..7693687a13211 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -96,6 +96,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-option.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-relay.c'), + }, network_test_template + { 'sources' : files('test-dhcp-server.c'), }, diff --git a/src/libsystemd-network/test-dhcp-relay.c b/src/libsystemd-network/test-dhcp-relay.c new file mode 100644 index 0000000000000..dd4e7652d9f06 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-relay.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" + +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" /* IWYU pragma: keep */ +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "ip-util.h" +#include "socket-util.h" +#include "tests.h" + +static uint32_t xid = 12345; + +static const struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, +}; + +static unsigned fake_server_message_count = 0; +static unsigned fake_client_message_count = 0; + +TEST(sd_dhcp_relay_ref_unref) { + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL, *downstream = NULL; + + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_NOT_NULL(relay); + + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Each interface holds a reference to the sd_dhcp_relay object, so we can safely drop our reference. */ + relay = sd_dhcp_relay_unref(relay); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(downstream->relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Still upstream interface has the reference. */ + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(upstream->relay->interfaces, INT_TO_PTR(4343))); + + /* Everything should be freed with this. */ + upstream = sd_dhcp_relay_interface_unref(upstream); + + /* Let's check the inverse order. */ + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); + + upstream = sd_dhcp_relay_interface_unref(upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4242))); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); +} + +static void send_message(int fd, sd_dhcp_message *m) { + ASSERT_OK(dhcp_message_send_udp( + m, + fd, + /* src_addr= */ INADDR_ANY, + /* dst_addr= */ INADDR_ANY, + /* dst_port= */ 0)); +} + +static int fake_server_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay *relay = ASSERT_PTR(userdata); + + fake_server_message_count++; + log_debug("%s: count=%u", __func__, fake_server_message_count); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &IOVEC_MAKE(buf, len), + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m)); + + ASSERT_EQ(m->header.hops, 1u); + + sd_dhcp_relay_interface *downstream; + ASSERT_OK(downstream_get(relay, m, &downstream)); + ASSERT_FALSE(downstream->upstream); + + ASSERT_EQ(m->header.giaddr, downstream->gateway_address.s_addr); + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info)); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, agent_info->entries) { + uint32_t tag = PTR_TO_UINT32(key); + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(tlv_get_alloc(agent_info, tag, &iov)); + + switch (tag) { + case SD_DHCP_RELAY_AGENT_CIRCUIT_ID: + ASSERT_TRUE(iovec_equal(&iov, &downstream->circuit_id)); + break; + case SD_DHCP_RELAY_AGENT_REMOTE_ID: + ASSERT_TRUE(iovec_equal(&iov, &relay->remote_id)); + break; + case SD_DHCP_RELAY_AGENT_LINK_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_FLAGS: { + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_EQ(iov.iov_len, 1u); + uint8_t flags = *(uint8_t*) iov.iov_base; + /* In the unit test, we cannot detect if the message is broadcast or unicast because + * AF_UNIX is used; therefore, the unicast flag is not set. */ + ASSERT_FALSE(FLAGS_SET(flags, DHCP_RELAY_AGENT_FLAG_UNICAST)); + break; + } + case SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE: + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &downstream->vss)); + break; + default: + assert_not_reached(); + } + } + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + + switch (fake_server_message_count) { + case 1: + ASSERT_EQ(t, DHCP_DISCOVER); + break; + case 2: + ASSERT_EQ(t, DHCP_REQUEST); + break; + case 3: + ASSERT_EQ(t, DHCP_RELEASE); + + if (fake_client_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static void fake_client_verify(int fd, uint8_t type, bool raw) { + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) + ASSERT_OK(udp_packet_verify(&payload, DHCP_PORT_CLIENT, /* checksum= */ true, &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREPLY, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m)); + + ASSERT_EQ(m->header.hops, 0u); + ASSERT_FALSE(dhcp_message_has_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + ASSERT_EQ(t, type); +} + +static int fake_client_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + fake_client_message_count++; + log_debug("%s: count=%u", __func__, fake_client_message_count); + + switch (fake_client_message_count) { + case 1: + fake_client_verify(fd, DHCP_OFFER, /* raw= */ true); + break; + case 2: + fake_client_verify(fd, DHCP_ACK, /* raw= */ false); + break; + case 3: + fake_client_verify(fd, DHCP_NAK, /* raw= */ false); + + if (fake_server_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(forwarding) { + union in_addr_union a; + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + /* Setting up an upstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK_ZERO(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + ASSERT_OK_POSITIVE(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + /* IO event source for the server side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_server = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_server, upstream_fd[1], EPOLLIN, fake_server_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_server, SD_EVENT_PRIORITY_IMPORTANT)); + ASSERT_OK(sd_event_source_set_description(fake_server, "fake-server-io-event-source")); + + /* Setting up a downstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + /* IO event source for the client side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_client = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_client, downstream_fd[1], EPOLLIN, fake_client_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_client, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(sd_event_source_set_description(fake_client, "fake-client-io-event-source")); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* Test: downstream -> upstream */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 100)); + m->header.op = BOOTREPLY; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREQUEST; + + /* Invalid message (too large hops). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 101)); + m->header.hops = 16; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.hops = 0; + + /* Invalid message (invalid giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 102)); + m->header.giaddr = downstream->address.s_addr; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = INADDR_ANY; + + /* Invalid message (unexpected relay agent information). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 103)); + ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + dhcp_message_remove_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_RELEASE)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Test: upstream -> downstream */ + m->header.op = BOOTREPLY; + m->header.giaddr = downstream->gateway_address.s_addr; + m->header.yiaddr = 0x12345678; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_ACK)); + m->header.ciaddr = 0x12345678; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.ciaddr = 0; + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 200)); + m->header.op = BOOTREQUEST; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREPLY; + + /* Invalid message (NULL giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 201)); + m->header.giaddr = INADDR_ANY; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + /* Invalid message (unexpected giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 202)); + m->header.giaddr = 1234567; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_NAK)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(sd_event_loop(e)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 0ff6bace599c366b8f5b7d9f53c2a1e4b36909ab Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 23:32:33 +0900 Subject: [PATCH 2069/2155] fuzz: introduce fuzzer for DHCP relay agent --- src/libsystemd-network/fuzz-dhcp-relay.c | 61 +++++++++++++++++++++++ src/libsystemd-network/meson.build | 3 ++ test/fuzz/fuzz-dhcp-relay/discover | Bin 0 -> 300 bytes test/fuzz/fuzz-dhcp-relay/offer | Bin 0 -> 300 bytes 4 files changed, 64 insertions(+) create mode 100644 src/libsystemd-network/fuzz-dhcp-relay.c create mode 100644 test/fuzz/fuzz-dhcp-relay/discover create mode 100644 test/fuzz/fuzz-dhcp-relay/offer diff --git a/src/libsystemd-network/fuzz-dhcp-relay.c b/src/libsystemd-network/fuzz-dhcp-relay.c new file mode 100644 index 0000000000000..2c828b9548b8e --- /dev/null +++ b/src/libsystemd-network/fuzz-dhcp-relay.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-relay-internal.h" +#include "fd-util.h" +#include "fuzz.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "tests.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + union in_addr_union a; + + fuzz_setup_logging(); + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + (void) downstream_process_message(downstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + (void) upstream_process_message(upstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + + return 0; +} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 7693687a13211..5d2368cc9881a 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -136,6 +136,9 @@ executables += [ network_fuzz_template + { 'sources' : files('fuzz-dhcp-client.c'), }, + network_fuzz_template + { + 'sources' : files('fuzz-dhcp-relay.c'), + }, network_fuzz_template + { 'sources' : files('fuzz-dhcp6-client.c'), }, diff --git a/test/fuzz/fuzz-dhcp-relay/discover b/test/fuzz/fuzz-dhcp-relay/discover new file mode 100644 index 0000000000000000000000000000000000000000..4ffce3951fef0a04a199331a762fb2b62939a397 GIT binary patch literal 300 ncmZQ%WMhz75 Date: Sun, 3 May 2026 09:56:45 +0900 Subject: [PATCH 2070/2155] network: re-implement DHCP relay support with new sd-dhcp-relay --- src/network/meson.build | 1 + src/network/networkd-address.c | 7 + src/network/networkd-conf.c | 1 + src/network/networkd-dhcp-relay.c | 362 +++++++++++++++++++++++ src/network/networkd-dhcp-relay.h | 19 ++ src/network/networkd-gperf.gperf | 4 + src/network/networkd-link.c | 22 ++ src/network/networkd-link.h | 1 + src/network/networkd-manager.c | 7 + src/network/networkd-manager.h | 8 + src/network/networkd-network-gperf.gperf | 7 + src/network/networkd-network.c | 10 + src/network/networkd-network.h | 11 + src/network/networkd-queue.c | 1 + src/network/networkd-queue.h | 1 + 15 files changed, 462 insertions(+) create mode 100644 src/network/networkd-dhcp-relay.c create mode 100644 src/network/networkd-dhcp-relay.h diff --git a/src/network/meson.build b/src/network/meson.build index 9844229ebdc58..110af9511c11b 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -43,6 +43,7 @@ systemd_networkd_extract_sources = files( 'networkd-conf.c', 'networkd-dhcp-common.c', 'networkd-dhcp-prefix-delegation.c', + 'networkd-dhcp-relay.c', 'networkd-dhcp-server-bus.c', 'networkd-dhcp-server-static-lease.c', 'networkd-dhcp-server.c', diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 193c4a04b5f44..9737ae02881da 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -19,6 +19,7 @@ #include "networkd-address.h" #include "networkd-address-pool.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-link.h" @@ -245,6 +246,9 @@ static Address* address_detach_impl(Address *address) { if (address->network->dhcp_server_address == address) address->network->dhcp_server_address = NULL; + if (address->network->dhcp_relay_agent_address == address) + address->network->dhcp_relay_agent_address = NULL; + address->network = NULL; return address; } @@ -893,6 +897,8 @@ static int address_drop(Address *in, bool removed_by_us) { ipv4acd_detach(link, address); + (void) link_dhcp_relay_address_dropped(link, address); + address_detach(address); if (!removed_by_us) { @@ -2485,5 +2491,6 @@ int network_drop_invalid_addresses(Network *network) { if (r < 0) return r; + network_adjust_dhcp_relay(network); return 0; } diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index bce16284f84e9..ba3e2f5a43132 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -22,6 +22,7 @@ int manager_parse_config_file(Manager *m) { "DHCPv4\0" "DHCPv6\0" "DHCPServer\0" + "DHCPRelay\0" "DHCP\0", config_item_perf_lookup, networkd_gperf_lookup, CONFIG_PARSE_WARN, diff --git a/src/network/networkd-dhcp-relay.c b/src/network/networkd-dhcp-relay.c new file mode 100644 index 0000000000000..5e793a0a124d3 --- /dev/null +++ b/src/network/networkd-dhcp-relay.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" +#include "sd-id128.h" + +#include "conf-parser.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "networkd-address.h" +#include "networkd-dhcp-relay.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-queue.h" +#include "string-table.h" +#include "string-util.h" /* IWYU pragma: keep */ + +#define DHCP_RELAY_APP_REMOTE_ID SD_ID128_MAKE(85,bb,eb,d2,b8,56,47,0b,b0,86,4c,f3,d3,9b,c1,b5) + +void network_adjust_dhcp_relay(Network *network) { + assert(network); + assert(network->manager); + + if (network->dhcp_relay_interface_mode < 0) + return; + + if (network->bond) { + log_warning("%s: DHCPRelay= is enabled for bond slave. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (network->dhcp_server) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPServer= is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPv4 client is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (in4_addr_is_null(&network->manager->dhcp_relay_server_address)) { + log_warning("%s: DHCPRelay= is enabled, but [DHCPRelay] ServerAddress= in networkd.conf is not configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + Address *a; + ORDERED_HASHMAP_FOREACH(a, network->addresses_by_section) { + assert(!section_is_invalid(a->section)); + + if (a->family != AF_INET) + continue; + + if (in4_addr_is_set(&a->in_addr_peer.in)) + continue; + + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) { + if (!in4_addr_equal(&a->in_addr.in, &network->dhcp_relay_agent_address_in_addr)) + continue; + + } else { + if (in4_addr_is_localhost(&a->in_addr.in)) + continue; + + if (in4_addr_is_link_local(&a->in_addr.in)) + continue; + + if (a->scope != RT_SCOPE_UNIVERSE) + continue; + } + + network->dhcp_relay_agent_address = a; + break; + } + + if (!network->dhcp_relay_agent_address) { + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + log_warning("%s: Configured AgentAddress=%s not found among static addresses. Disabling DHCP relay agent.", + network->filename, IN4_ADDR_TO_STRING(&network->dhcp_relay_agent_address_in_addr)); + else + log_warning("%s: DHCPRelay= is enabled, but no suitable static address configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } +} + +static int manager_configure_dhcp_relay(Manager *manager) { + int r; + + assert(manager); + assert(manager->event); + + if (manager->dhcp_relay) + return 0; + + if (in4_addr_is_null(&manager->dhcp_relay_server_address)) + return -EADDRNOTAVAIL; + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + r = sd_dhcp_relay_new(&relay); + if (r < 0) + return r; + + r = sd_dhcp_relay_attach_event(relay, manager->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_server_address(relay, &manager->dhcp_relay_server_address); + if (r < 0) + return r; + + if (iovec_is_set(&manager->dhcp_relay_remote_id)) { + r = sd_dhcp_relay_set_remote_id(relay, &manager->dhcp_relay_remote_id); + if (r < 0) + return r; + } else { + sd_id128_t id; + r = sd_id128_get_machine_app_specific(DHCP_RELAY_APP_REMOTE_ID, &id); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING(SD_ID128_TO_STRING(id))); + if (r < 0) + return r; + } + + r = sd_dhcp_relay_set_server_identifier_override(relay, manager->dhcp_relay_override_server_id); + if (r < 0) + return r; + + r = dhcp_relay_set_extra_options(relay, &manager->dhcp_relay_extra_options); + if (r < 0) + return r; + + manager->dhcp_relay = TAKE_PTR(relay); + return 0; +} + +static int link_configure_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + assert(link->network); + assert(!link->dhcp_relay_interface); + assert(link->network->dhcp_relay_agent_address); + assert(link->network->dhcp_relay_interface_mode >= 0 && link->network->dhcp_relay_interface_mode < _DHCP_RELAY_INTERFACE_MAX); + + r = manager_configure_dhcp_relay(link->manager); + if (r < 0) + return r; + + bool upstream = link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_UPSTREAM; + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = NULL; + r = sd_dhcp_relay_add_interface(link->manager->dhcp_relay, link->ifindex, upstream, &interface); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_ifname(interface, link->ifname); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_address( + interface, + &link->network->dhcp_relay_agent_address->in_addr.in, + link->network->dhcp_relay_agent_address->prefixlen); + if (r < 0) + return r; + + if (upstream) { + r = sd_dhcp_relay_upstream_set_priority(interface, link->network->dhcp_relay_interface_priority); + if (r < 0) + return r; + } else { + if (in4_addr_is_set(&link->network->dhcp_relay_gateway_address)) + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_gateway_address); + else + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_agent_address->in_addr.in); + if (r < 0) + return r; + + if (iovec_is_set(&link->network->dhcp_relay_circuit_id)) + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &link->network->dhcp_relay_circuit_id); + else + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &IOVEC_MAKE_STRING(link->ifname)); + if (r < 0) + return r; + + r = sd_dhcp_relay_downstream_set_virtual_subnet_selection(interface, &link->network->dhcp_relay_vss); + if (r < 0) + return r; + + r = downstream_set_extra_options(interface, &link->network->dhcp_relay_extra_options); + if (r < 0) + return r; + } + + link->dhcp_relay_interface = TAKE_PTR(interface); + return 0; +} + +static bool dhcp_relay_is_ready_to_configure(Link *link) { + assert(link); + assert(link->network); + + if (!link_is_ready_to_configure(link, /* allow_unmanaged= */ false)) + return false; + + if (!link_has_carrier(link)) + return false; + + if (!link->static_addresses_configured) + return false; + + Address *a; + if (address_get(link, link->network->dhcp_relay_agent_address, &a) < 0) + return false; + + if (!address_is_ready(a)) + return false; + + return true; +} + +static int dhcp_relay_process_request(Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + if (!dhcp_relay_is_ready_to_configure(link)) + return 0; + + r = link_configure_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure DHCP relay agent: %m"); + + log_link_debug(link, "DHCP relay agent is configured."); + + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start DHCP relay agent: %m"); + + return 1; +} + +int link_request_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (link->network->dhcp_relay_interface_mode < 0) + return 0; + + if (link->dhcp_relay_interface) + return 0; + + r = link_queue_request(link, REQUEST_TYPE_DHCP_RELAY, dhcp_relay_process_request, NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request configuring of the DHCP relay agent: %m"); + + log_link_debug(link, "Requested configuring of the DHCP relay agent."); + return 0; +} + +int link_start_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_relay_interface) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface); + if (r < 0) + return r; + + log_link_debug(link, "Relaying DHCPv4 messages."); + return 0; +} + +int link_dhcp_relay_address_dropped(Link *link, const Address *address) { + int r; + + assert(link); + assert(link->manager); + assert(address); + + /* This is called when an address is removed from the interface. */ + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (!link->dhcp_relay_interface) + return 0; + + if (address->family != AF_INET) + return 0; + + struct in_addr a; + uint8_t prefixlen; + r = sd_dhcp_relay_interface_get_address(link->dhcp_relay_interface, &a, &prefixlen); + if (r <= 0) + return r; + + if (!in4_addr_equal(&address->in_addr.in, &a)) + return 0; + + if (address->prefixlen != prefixlen) + return 0; + + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + return r; + + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + /* The address may be reconfigured later. Let's reconfigure DHCP relay interface when the address comes back. */ + return link_request_dhcp_relay(link); +} + +static const char * const dhcp_relay_interface_mode_table[_DHCP_RELAY_INTERFACE_MAX] = { + [DHCP_RELAY_INTERFACE_UPSTREAM] = "upstream", + [DHCP_RELAY_INTERFACE_DOWNSTREAM] = "downstream", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_relay_interface_mode, DHCPRelayInterfaceMode); + +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT( + config_parse_dhcp_relay_interface_mode, + dhcp_relay_interface_mode, + DHCPRelayInterfaceMode, + _DHCP_RELAY_INTERFACE_INVALID); diff --git a/src/network/networkd-dhcp-relay.h b/src/network/networkd-dhcp-relay.h new file mode 100644 index 0000000000000..7d56740edeabb --- /dev/null +++ b/src/network/networkd-dhcp-relay.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-forward.h" + +typedef enum DHCPRelayInterfaceMode { + DHCP_RELAY_INTERFACE_UPSTREAM, + DHCP_RELAY_INTERFACE_DOWNSTREAM, + _DHCP_RELAY_INTERFACE_MAX, + _DHCP_RELAY_INTERFACE_INVALID = -EINVAL, +} DHCPRelayInterfaceMode; + +void network_adjust_dhcp_relay(Network *network); + +int link_request_dhcp_relay(Link *link); +int link_start_dhcp_relay(Link *link); +int link_dhcp_relay_address_dropped(Link *link, const Address *address); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_relay_interface_mode); diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 3684a696ff89e..242da72c27ee8 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -45,6 +45,10 @@ DHCPv6.UseDomains, config_parse_use_domains, DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid) DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Manager, dhcp_server_persist_leases) +DHCPRelay.ServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Manager, dhcp_relay_server_address) +DHCPRelay.OverrideServerIdentifier, config_parse_bool, 0, offsetof(Manager, dhcp_relay_override_server_id) +DHCPRelay.RemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Manager, dhcp_relay_remote_id) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Manager, dhcp_relay_extra_options) /* Deprecated */ DHCP.DUIDType, config_parse_manager_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0 diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a69a5e8979c3c..969818e9f7622 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -10,6 +10,7 @@ #include "sd-bus.h" #include "sd-dhcp-client.h" +#include "sd-dhcp-relay.h" #include "sd-dhcp-server.h" #include "sd-dhcp6-client.h" #include "sd-dhcp6-lease.h" @@ -41,6 +42,7 @@ #include "networkd-bridge-mdb.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -240,6 +242,7 @@ static void link_free_engines(Link *link) { if (!link) return; + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); @@ -424,6 +427,10 @@ int link_stop_engines(Link *link, bool may_keep_dynamic) { ndisc_flush(link); } + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent: %m")); + r = sd_dhcp_server_stop(link->dhcp_server); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); @@ -739,6 +746,10 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) { log_link_debug(link, "Acquiring IPv4 link-local address."); } + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not start DHCP relay agent: %m"); + r = link_start_dhcp4_server(link); if (r < 0) return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); @@ -1156,6 +1167,7 @@ static int link_drop_dynamic_config(Link *link, Network *network) { RET_GATHER(r, link_drop_dhcp4_config(link, network)); RET_GATHER(r, link_drop_dhcp6_config(link, network)); RET_GATHER(r, link_drop_dhcp_pd_config(link, network)); + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */ link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx); @@ -1274,6 +1286,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_request_dhcp_relay(link); + if (r < 0) + return r; + r = link_request_dhcp_server(link); if (r < 0) return r; @@ -2605,6 +2621,12 @@ static int link_update_name(Link *link, sd_netlink_message *message) { return log_link_debug_errno(link, r, "Failed to update interface name in NDisc: %m"); } + if (link->dhcp_relay_interface) { + r = sd_dhcp_relay_interface_set_ifname(link->dhcp_relay_interface, link->ifname); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to update interface name in DHCP relay interface: %m"); + } + if (link->dhcp_server) { r = sd_dhcp_server_set_ifname(link->dhcp_server, link->ifname); if (r < 0) diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 456ec99185624..4c3e53b93eabc 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -143,6 +143,7 @@ typedef struct Link { bool bridge_vlan_set:1; bool bearer_configured:1; + sd_dhcp_relay_interface *dhcp_relay_interface; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index f43709da356c9..63ba0c166fa83 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -5,6 +5,7 @@ #include #include "sd-bus.h" +#include "sd-dhcp-relay.h" #include "sd-event.h" #include "sd-netlink.h" #include "sd-resolve.h" @@ -25,6 +26,7 @@ #include "errno-util.h" #include "fd-util.h" #include "initrd-util.h" +#include "iovec-util.h" #include "mount-util.h" #include "netlink-internal.h" #include "netlink-util.h" @@ -701,6 +703,7 @@ int manager_new(Manager **ret, bool test_mode) { .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, .duid_product_uuid.type = DUID_TYPE_UUID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_server_persist_leases = DHCP_SERVER_PERSIST_LEASES_YES, .serialization_fd = -EBADF, .ip_forwarding = { -1, -1, }, @@ -760,6 +763,10 @@ Manager* manager_free(Manager *m) { sd_netlink_unref(m->nfnl); sd_resolve_unref(m->resolve); + iovec_done(&m->dhcp_relay_remote_id); + tlv_done(&m->dhcp_relay_extra_options); + sd_dhcp_relay_unref(m->dhcp_relay); + m->routes = set_free(m->routes); m->nexthops_by_id = hashmap_free(m->nexthops_by_id); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index bacf3df444474..2e1b6430907ee 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -3,6 +3,7 @@ #include "networkd-forward.h" #include "networkd-network.h" +#include "tlv-util.h" typedef enum ManagerState { MANAGER_RUNNING, @@ -74,6 +75,13 @@ typedef struct Manager { bool has_product_uuid; bool product_uuid_requested; + /* DHCP relay agent */ + sd_dhcp_relay *dhcp_relay; + struct in_addr dhcp_relay_server_address; + bool dhcp_relay_override_server_id; + struct iovec dhcp_relay_remote_id; + TLV dhcp_relay_extra_options; + char* dynamic_hostname; char* dynamic_timezone; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9e6eb7c799572..4b54c6d88de6f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -132,6 +132,7 @@ Network.VLAN, config_parse_stacked_netdev, Network.VXLAN, config_parse_stacked_netdev, NETDEV_KIND_VXLAN, offsetof(Network, stacked_netdev_names) Network.Xfrm, config_parse_stacked_netdev, NETDEV_KIND_XFRM, offsetof(Network, stacked_netdev_names) Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp) +Network.DHCPRelay, config_parse_dhcp_relay_interface_mode, 0, offsetof(Network, dhcp_relay_interface_mode) Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server) Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local) Network.IPv6LinkLocalAddressGenerationMode, config_parse_ipv6_link_local_address_gen_mode, 0, offsetof(Network, ipv6ll_address_gen_mode) @@ -360,6 +361,12 @@ IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens) IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel) IPv6AcceptRA.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, ndisc_nft_set_context) +DHCPRelay.AgentAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_agent_address_in_addr) +DHCPRelay.GatewayAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_gateway_address) +DHCPRelay.CircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) +DHCPRelay.VirtualSubnetSelection, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_vss) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_relay_extra_options) +DHCPRelay.InterfacePriority, config_parse_int, 0, offsetof(Network, dhcp_relay_interface_priority) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index dfe524d150d23..b1569866d10da 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -7,6 +7,7 @@ #include "conf-files.h" #include "conf-parser.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "net-condition.h" #include "netdev/macvlan.h" #include "netif-sriov.h" @@ -433,6 +434,9 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_pd_subnet_id = -1, .dhcp_pd_route_metric = DHCP6PD_ROUTE_METRIC, + .dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), + .dhcp_server_bind_to_interface = true, .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, @@ -547,6 +551,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "DHCPv6\0" "DHCPv6PrefixDelegation\0" /* compat */ "DHCPPrefixDelegation\0" + "DHCPRelay\0" "DHCPServer\0" "DHCPServerStaticLease\0" "IPv6AcceptRA\0" @@ -767,6 +772,11 @@ static Network *network_free(Network *network) { ordered_set_free(network->route_domains); set_free(network->dnssec_negative_trust_anchors); + /* DHCP relay agent */ + iovec_done(&network->dhcp_relay_circuit_id); + iovec_done(&network->dhcp_relay_vss); + tlv_done(&network->dhcp_relay_extra_options); + /* DHCP server */ free(network->dhcp_server_relay_agent_circuit_id); free(network->dhcp_server_relay_agent_remote_id); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 625b3b9a08c31..d9e516ff4ecaf 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -12,6 +12,7 @@ #include "network-util.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-common.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -208,6 +209,16 @@ typedef struct Network { uint32_t dhcp6_route_table; bool dhcp6_route_table_set; + /* DHCP Relay Agent Support */ + DHCPRelayInterfaceMode dhcp_relay_interface_mode; + Address *dhcp_relay_agent_address; + struct in_addr dhcp_relay_agent_address_in_addr; + struct in_addr dhcp_relay_gateway_address; + struct iovec dhcp_relay_circuit_id; + struct iovec dhcp_relay_vss; + TLV dhcp_relay_extra_options; + int dhcp_relay_interface_priority; + /* DHCP Server Support */ bool dhcp_server; bool dhcp_server_bind_to_interface; diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 0ab372b4b21e7..ea846327b0db9 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -365,6 +365,7 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { [REQUEST_TYPE_ADDRESS_LABEL] = "address label", [REQUEST_TYPE_BRIDGE_FDB] = "bridge FDB", [REQUEST_TYPE_BRIDGE_MDB] = "bridge MDB", + [REQUEST_TYPE_DHCP_RELAY] = "DHCP relay agent", [REQUEST_TYPE_DHCP_SERVER] = "DHCP server", [REQUEST_TYPE_DHCP4_CLIENT] = "DHCPv4 client", [REQUEST_TYPE_DHCP6_CLIENT] = "DHCPv6 client", diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index d656d7aa7aa8a..70c8b2ca9eeda 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -13,6 +13,7 @@ typedef enum RequestType { REQUEST_TYPE_ADDRESS_LABEL, REQUEST_TYPE_BRIDGE_FDB, REQUEST_TYPE_BRIDGE_MDB, + REQUEST_TYPE_DHCP_RELAY, REQUEST_TYPE_DHCP_SERVER, REQUEST_TYPE_DHCP4_CLIENT, REQUEST_TYPE_DHCP6_CLIENT, From 08b3ebf5f1d7a22f29b6e3dbe8ab6c9ac8407084 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 11 May 2026 07:48:27 +0900 Subject: [PATCH 2071/2155] test-network: add test case for DHCP relay --- .../conf/25-dhcp-relay-downstream.network | 14 +++ .../conf/25-dhcp-relay-upstream.network | 12 ++ test/test-network/conf/25-dhcp-relay.conf | 5 + test/test-network/systemd-networkd-tests.py | 110 ++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 test/test-network/conf/25-dhcp-relay-downstream.network create mode 100644 test/test-network/conf/25-dhcp-relay-upstream.network create mode 100644 test/test-network/conf/25-dhcp-relay.conf diff --git a/test/test-network/conf/25-dhcp-relay-downstream.network b/test/test-network/conf/25-dhcp-relay-downstream.network new file mode 100644 index 0000000000000..49ff765e95f52 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-downstream.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-down + +[Network] +Address=198.51.100.10/24 +IPv6AcceptRA=no +DHCPRelay=downstream + +[DHCPRelay] +AgentAddress=198.51.100.10 +GatewayAddress=198.51.100.10 +CircuitId=string:test-dhcp-relay-circuit-id +VirtualSubnetSelection=string:subnet-hoge diff --git a/test/test-network/conf/25-dhcp-relay-upstream.network b/test/test-network/conf/25-dhcp-relay-upstream.network new file mode 100644 index 0000000000000..d84f6a71fe6ed --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-upstream.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-up + +[Network] +Address=192.0.2.10/24 +IPv6AcceptRA=no +DHCPRelay=upstream + +[DHCPRelay] +AgentAddress=192.0.2.10 +InterfacePriority=100 diff --git a/test/test-network/conf/25-dhcp-relay.conf b/test/test-network/conf/25-dhcp-relay.conf new file mode 100644 index 0000000000000..5d6bcb2c4aeb6 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[DHCPRelay] +ServerAddress=192.0.2.1 +OverrideServerIdentifier=yes +RemoteId=string:test-dhcp-relay-remote-id diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8609a63916a39..28c418460c079 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1222,6 +1222,7 @@ 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-relay') call_quiet('ip netns del ns-server') # 4. remove links @@ -8393,6 +8394,115 @@ def test_relay_agent_on_bridge(self): # For issue #30763. self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') + def test_sd_dhcp_relay(self): + check_output('ip netns add ns-relay') + check_output('ip netns add ns-server') + + check_output('ip link add relay-down type veth peer client') + check_output('ip link set relay-down netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-down up') + + check_output('ip link add relay-up type veth peer server') + check_output('ip link set relay-up netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-up up') + + 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') + + with tempfile.TemporaryDirectory() as tmp: + conf_dir = pathlib.Path(tmp) / 'run/systemd/networkd.conf.d' + unit_dir = pathlib.Path(tmp) / 'run/systemd/network' + netif_dir = pathlib.Path(tmp) / 'run/systemd/netif' + mkdir_p(conf_dir) + mkdir_p(unit_dir) + mkdir_p(netif_dir) + + # Do not use shutil.chown(), as it fails when running with sanitizers. + check_output(f'chown systemd-network:systemd-network {netif_dir}') + + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay.conf', conf_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-downstream.network', unit_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-upstream.network', unit_dir) + + cmd = [ + 'systemd-run', + '--unit=networkd-test-dhcp-relay.service', + '--service-type=notify-reload', + '-p', 'User=systemd-network', + '-p', 'FileDescriptorStoreMax=512', + '-p', 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', 'CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', f'BindPaths={conf_dir}:/run/systemd/networkd.conf.d', + '-p', f'BindPaths={unit_dir}:/run/systemd/network', + '-p', f'BindPaths={netif_dir}:/run/systemd/netif', + '-p', 'TemporaryFileSystem=/run/dbus', + '-p', 'TemporaryFileSystem=/run/systemd/report', + '-p', 'TemporaryFileSystem=/run/systemd/resolve.hook', + '-p', 'TemporaryFileSystem=/var/lib/systemd/network', + '-p', 'TemporaryFileSystem=/etc/systemd/network', + '-p', 'TemporaryFileSystem=/usr/lib/systemd/network', + '-p', 'ReadOnlyPaths=/sys', + '-p', 'NetworkNamespacePath=/run/netns/ns-relay', + ] # fmt: skip + if enable_debug: + cmd += ['-p', 'Environment=SYSTEMD_LOG_LEVEL=debug'] + if asan_options: + cmd += ['-p', f'Environment=ASAN_OPTIONS={asan_options}'] + if lsan_options: + cmd += ['-p', f'Environment=LSAN_OPTIONS={lsan_options}'] + if ubsan_options: + cmd += ['-p', f'Environment=UBSAN_OPTIONS={ubsan_options}'] + if asan_options or lsan_options or ubsan_options: + cmd += ['-p', 'TimeoutStopFailureMode=abort'] + + cmd += [networkd_bin] + + try: + check_output(*cmd) + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + + 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'198\.51\.100\.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + # fmt: off + self.assertRegex(output, r'default via 198\.51\.100\.10 proto dhcp src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.0/24 proto kernel scope link src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.10 proto dhcp scope link src 198\.51\.100\.10[0-9]') + # fmt: on + + print('## dnsmasq log') + output = read_dnsmasq_log_file() + print(output) + # The option 82 is logged as agent-info since dnsmasq-2.92, otherwise logged as agent-id. + self.assertRegex(output, r'option: 82 (agent-id|agent-info)') + + print('## journal of networkd-test-dhcp-relay.service') + check_output('journalctl --sync') + output = check_output('journalctl --no-hostname --output=short-monotonic --unit=networkd-test-dhcp-relay.service -I') # fmt: skip + self.assertIn('relay-down: DHCPv4 relay: Received BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Forwarded BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Received BOOTREPLY', output) + self.assertIn('relay-down: DHCPv4 relay: Forwarded BOOTREPLY', output) + finally: + call_quiet('systemctl stop networkd-test-dhcp-relay.service') + call_quiet('systemctl reset-failed networkd-test-dhcp-relay.service') + class NetworkdDHCPClientTests(unittest.TestCase, Utilities): def setUp(self): From aa063e593ba0d34d86a638df8985632670b269a2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 11 May 2026 08:56:40 +0900 Subject: [PATCH 2072/2155] man: document DHCP relay configuration --- man/networkd.conf.xml | 61 +++++++++++++++++++++ man/systemd.network.xml | 118 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml index f611e244f5aaf..8dd1181508d39 100644 --- a/man/networkd.conf.xml +++ b/man/networkd.conf.xml @@ -393,6 +393,67 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + + [DHCPRelay] Section Options + The [DHCPRelay] section contains host-wide settings for the DHCP relay agent. + + + + + ServerAddress= + + Specifies the IPv4 address of the upstream DHCP server. This is required for the host to act + as a DHCP relay agent. + + + + + + + OverrideServerIdentifier= + + Takes a boolean value. When enabled, the Server Identifier Override sub-option and the Relay + Agent Flags sub-option in the Relay Agent Information option will be appended to the DHCP message + forwarded to the upstream DHCP server. Defaults to false. + + + + + + + RemoteId= + + Specifies the remote ID, which is typically used by DHCP servers to identify the DHCP relay + agent. Takes a data type and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + If unset, a UUID generated from the local machine ID will be used. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See RemoteId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. Defaults to unset, and no extra sub-option will be appended. + + + + + + + + [DHCPServer] Section Options diff --git a/man/systemd.network.xml b/man/systemd.network.xml index d62e4d62329ed..fcb700bff03aa 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -435,6 +435,26 @@ + + DHCPRelay= + + Takes one of upstream or downstream. When specified, + the host acts as a DHCP relay agent. When set to upstream, the interface behaves + as an upstream interface of the DHCP relay agent. When set to downstream, the + interface behaves as a downstream interface of the DHCP relay agent. To make the DHCP relay agent + work, at least one upstream interface and one downstream interface must be configured on the host. + This requires ServerAddress= in the [DHCPRelay] section to be configured in + networkd.conf. If ServerAddress= is not configured, this + setting is ignored. See + networkd.conf5 + for more details about ServerAddress= and other host-wide settings. + Also, further per-interface settings can be configured in the [DHCPRelay] section described below. + Defaults to unset, and the interface is not used for DHCP relay forwarding. + + + + + DHCPServer= @@ -3868,6 +3888,104 @@ Token=prefixstable:2002:da8:1:: + + [DHCPRelay] Section Options + The [DHCPRelay] section contains per-interface settings for the DHCP relay agent. The settings in + this section are only used when DHCPRelay= in the [Network] section is configured. + + + + + + AgentAddress= + + Takes an IPv4 address. The specified address is used as the source IP address of packets + forwarded to the upstream DHCP server. The address may also be used for the Server Identifier + Override sub-option and the Link Selection sub-option in the Relay Agent Information option + appended to DHCP messages forwarded to the upstream DHCP server. This setting applies to both + upstream and downstream interfaces. Defaults to unset. If unset, a statically configured IPv4 + address in the .network file is selected automatically. When multiple static IPv4 addresses are + configured, it is recommended to specify this setting explicitly. + + + + + + + GatewayAddress= + + Takes an IPv4 address. The specified address is set to the giaddr field of + the DHCP message header when the DHCP message is forwarded to the upstream DHCP server. This is + used only when DHCPRelay=downstream, and ignored otherwise. Defaults to unset, + and the same address specified with AgentAddress= will be used. + + + + + + + CircuitId= + + Specifies the circuit ID of the downstream interface. This is set as the Circuit ID + sub-option in the Relay Agent Information option in the forwarded DHCP message. Takes a data type + and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + This is used only when DHCPRelay=downstream, and ignored otherwise. If unset, + the name of the interface will be used. + + + + + + + VirtualSubnetSelection= + + Specifies a unique identifier used by the DHCP server to select the downstream subnet + independently of giaddr. This value is used as the Virtual Subnet Selection sub-option in the Relay + Agent Information option in the forwarded DHCP message. Takes a value in the same format as + CircuitId=. This is used only when DHCPRelay=downstream, and + ignored otherwise. Defaults to unset, and the sub-option is not set. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See CircuitId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. This is used only when + DHCPRelay=downstream, and ignored otherwise. Defaults to unset, and no extra + sub-option will be appended. + + + + + + + InterfacePriority= + + Specifies the priority of the upstream interface. Takes an integer value. When the host has + multiple upstream interfaces, the upstream interface with the highest (largest) priority will be + used for forwarding DHCP messages to the upstream DHCP server. When multiple interfaces have the + same priority, which interface is used is unspecified. This is used only when + DHCPRelay=upstream, and ignored otherwise. Defaults to zero. + + + + + + + [DHCPServer] Section Options The [DHCPServer] section contains settings for the DHCP server, if enabled via the From cb2bba6b040ee11da822f06b0cb23a47444671e2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 08:29:51 +0900 Subject: [PATCH 2073/2155] network: drop use of legacy DHCP relay agent in sd-dhcp-server Then, this deprecates - BindToInterface= - RelayTarget= - RelayAgentCircuitId= - RelayAgentRemoteId= settings in [DHCPServer] section. These are gracefully translated as new settings. --- man/systemd.network.xml | 56 +------------- src/network/networkd-dhcp-relay.c | 18 +++++ src/network/networkd-dhcp-relay.h | 1 + src/network/networkd-dhcp-server-bus.c | 17 +---- src/network/networkd-dhcp-server.c | 75 +++++++------------ src/network/networkd-dhcp-server.h | 1 - src/network/networkd-link-bus.c | 3 +- src/network/networkd-link.c | 6 ++ src/network/networkd-link.h | 1 + src/network/networkd-network-gperf.gperf | 8 +- src/network/networkd-network.c | 4 +- src/network/networkd-network.h | 6 +- .../conf/25-agent-client-peer.network | 5 +- .../test-network/conf/25-agent-client.network | 1 - .../conf/25-agent-server-peer.network | 3 +- .../test-network/conf/25-agent-server.network | 16 ---- .../conf/25-agent-veth-server.netdev | 9 --- test/test-network/systemd-networkd-tests.py | 19 ++++- 18 files changed, 85 insertions(+), 164 deletions(-) delete mode 100644 test/test-network/conf/25-agent-server.network delete mode 100644 test/test-network/conf/25-agent-veth-server.netdev diff --git a/man/systemd.network.xml b/man/systemd.network.xml index fcb700bff03aa..dc97e17838aab 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -464,10 +464,9 @@ Even if this is enabled, the DHCP server will not be started automatically and wait for the persistent storage being ready to load/save leases in the storage, unless - RelayTarget= or PersistLeases=no/runtime are specified in the - [DHCPServer] section. It will be started after - systemd-networkd-persistent-storage.service is started, which calls - networkctl persistent-storage yes. See + PersistLeases=no/runtime is specified in the [DHCPServer] section. It will be + started after systemd-networkd-persistent-storage.service is started, which + calls networkctl persistent-storage yes. See networkctl1 for more details. @@ -4279,52 +4278,6 @@ ServerAddress=192.168.0.1/24 - - BindToInterface= - - Takes a boolean value. When yes, DHCP server socket will be bound - to its network interface and all socket communication will be restricted to this interface. - Defaults to yes, except if RelayTarget= is used (see below), - in which case it defaults to no. - - - - - - RelayTarget= - - Takes an IPv4 address, which must be in the format described in - inet_pton3. - Turns this DHCP server into a DHCP relay agent. See RFC 1542. - The address is the address of DHCP server or another relay agent to forward DHCP messages to and from. - - - - - - RelayAgentCircuitId= - - Specifies value for Agent Circuit ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Circuit ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - - - RelayAgentRemoteId= - - Specifies value for Agent Remote ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Remote ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - RapidCommit= @@ -4356,9 +4309,6 @@ ServerAddress=192.168.0.1/24 networkd.conf5, which defaults to yes, will be used. - When RelayTarget= is specified, this setting will be ignored and no leases - will be saved, as there will be no bound lease on the server. - diff --git a/src/network/networkd-dhcp-relay.c b/src/network/networkd-dhcp-relay.c index 5e793a0a124d3..7811d9d585757 100644 --- a/src/network/networkd-dhcp-relay.c +++ b/src/network/networkd-dhcp-relay.c @@ -209,6 +209,18 @@ static int link_configure_dhcp_relay(Link *link) { } link->dhcp_relay_interface = TAKE_PTR(interface); + + if (link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_COMPAT && + !link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_add_interface( + link->manager->dhcp_relay, + DHCP_RELAY_IFINDEX_UNBOUND, + /* is_upstream= */ true, + &link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + return 0; } @@ -298,6 +310,12 @@ int link_start_dhcp_relay(Link *link) { if (r < 0) return r; + if (link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + log_link_debug(link, "Relaying DHCPv4 messages."); return 0; } diff --git a/src/network/networkd-dhcp-relay.h b/src/network/networkd-dhcp-relay.h index 7d56740edeabb..2a676931bd4a7 100644 --- a/src/network/networkd-dhcp-relay.h +++ b/src/network/networkd-dhcp-relay.h @@ -6,6 +6,7 @@ typedef enum DHCPRelayInterfaceMode { DHCP_RELAY_INTERFACE_UPSTREAM, DHCP_RELAY_INTERFACE_DOWNSTREAM, + DHCP_RELAY_INTERFACE_COMPAT, _DHCP_RELAY_INTERFACE_MAX, _DHCP_RELAY_INTERFACE_INVALID = -EINVAL, } DHCPRelayInterfaceMode; diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index e5ee6d3f6d5c3..58e0a26f22fc1 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -30,9 +30,6 @@ static int property_get_leases( if (!s) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname); - if (sd_dhcp_server_is_in_relay_mode(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has DHCP relay agent active.", l->ifname); - r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)"); if (r < 0) return r; @@ -82,15 +79,12 @@ static int property_get_pool_size( 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; - + uint32_t v = l->dhcp_server ? l->dhcp_server->pool_size : UINT32_MAX; return sd_bus_message_append_basic(reply, 'u', &v); } @@ -102,15 +96,12 @@ static int property_get_pool_offset( 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; - + uint32_t v = l->dhcp_server ? l->dhcp_server->pool_offset : UINT32_MAX; return sd_bus_message_append_basic(reply, 'u', &v); } diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 9903c6b9d3233..2ee5a70abc513 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -16,6 +16,7 @@ #include "fileio.h" #include "hashmap.h" #include "hostname-setup.h" +#include "iovec-util.h" #include "network-common.h" #include "networkd-address.h" #include "networkd-dhcp-server.h" @@ -66,6 +67,31 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } + if (in4_addr_is_set(&network->dhcp_relay_target_address)) { + if (!in4_addr_is_set(&network->manager->dhcp_relay_server_address)) { + /* [DHCPServer] RelayTarget= in .network file is replaced with + * [DHCPRelay] ServerAddress= in networkd.conf. */ + network->manager->dhcp_relay_server_address = network->dhcp_relay_target_address; + + /* [DHCPServer] RelayAgentRemoteId= in .network file is replaced with + * [DHCPRelay] RemoteId= in networkd.conf. */ + if (!iovec_is_set(&network->manager->dhcp_relay_remote_id)) { + iovec_done(&network->manager->dhcp_relay_remote_id); + network->manager->dhcp_relay_remote_id = TAKE_STRUCT(network->dhcp_relay_remote_id); + } + } + + /* Copy [DHCPServer] ServerAddress= to [DHCPRelay] AgentAddress= if unspecified. */ + if (!in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + network->dhcp_relay_agent_address_in_addr = network->dhcp_server_address_in_addr; + + /* Assume this interface acts as a downstream interface of the DHCP relay agent. Also, + * configure a catch-all upstream socket. */ + network->dhcp_relay_interface_mode = DHCP_RELAY_INTERFACE_COMPAT; + network->dhcp_server = false; + return 0; + } + assert(network->dhcp_server_address_prefixlen <= 32); if (network->dhcp_server_address_prefixlen == 0) { @@ -160,9 +186,6 @@ static DHCPServerPersistLeases link_get_dhcp_server_persist_leases(Link *link) { assert(link->manager); assert(link->network); - if (in4_addr_is_set(&link->network->dhcp_server_relay_target)) - return DHCP_SERVER_PERSIST_LEASES_NO; /* On relay mode. Nothing saved in the persistent storage. */ - if (link->network->dhcp_server_persist_leases >= 0) return link->network->dhcp_server_persist_leases; @@ -571,7 +594,6 @@ static int dhcp4_server_configure(Link *link) { DHCPStaticLease *static_lease; Link *uplink = NULL; Address *address; - bool bind_to_interface; int r; assert(link); @@ -681,19 +703,6 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set router address for DHCP server: %m"); } - r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m"); - - bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface; - r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m"); - - r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m"); - if (link->network->dhcp_server_emit_timezone) { _cleanup_free_ char *buffer = NULL; const char *tz = NULL; @@ -815,38 +824,6 @@ int link_request_dhcp_server(Link *link) { return 0; } -int config_parse_dhcp_server_relay_agent_suboption( - 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) { - - char **suboption_value = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *suboption_value = mfree(*suboption_value); - return 0; - } - - const char *p = startswith(rvalue, "string:"); - if (!p) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue); - return 0; - } - return free_and_strdup(suboption_value, empty_to_null(p)); -} - int config_parse_dhcp_server_emit( const char *unit, const char *filename, diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index e46ad3a8579af..f60ca2d3971ae 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -18,7 +18,6 @@ int link_request_dhcp_server(Link *link); int link_start_dhcp4_server(Link *link); void manager_toggle_dhcp4_server_state(Manager *manager, bool start); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ipv6_only_preferred); diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index a375d0c18b235..53ac080a47247 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -855,8 +855,7 @@ int link_object_find(sd_bus *bus, const char *path, const char *interface, void if (r < 0) return 0; - if (streq(interface, "org.freedesktop.network1.DHCPServer") && - (!link->dhcp_server || sd_dhcp_server_is_in_relay_mode(link->dhcp_server))) + if (streq(interface, "org.freedesktop.network1.DHCPServer") && !link->dhcp_server) return 0; if (streq(interface, "org.freedesktop.network1.DHCPv4Client") && !link->dhcp_client) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 969818e9f7622..6d15e8deb4964 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -243,6 +243,7 @@ static void link_free_engines(Link *link) { return; link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); @@ -431,6 +432,10 @@ int link_stop_engines(Link *link, bool may_keep_dynamic) { if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent: %m")); + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface_compat); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent (compat): %m")); + r = sd_dhcp_server_stop(link->dhcp_server); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); @@ -1168,6 +1173,7 @@ static int link_drop_dynamic_config(Link *link, Network *network) { RET_GATHER(r, link_drop_dhcp6_config(link, network)); RET_GATHER(r, link_drop_dhcp_pd_config(link, network)); link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */ link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 4c3e53b93eabc..5a3d1d944744e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -144,6 +144,7 @@ typedef struct Link { bool bearer_configured:1; sd_dhcp_relay_interface *dhcp_relay_interface; + sd_dhcp_relay_interface *dhcp_relay_interface_compat; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 4b54c6d88de6f..81b9784b49a4c 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -369,9 +369,9 @@ DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, DHCPRelay.InterfacePriority, config_parse_int, 0, offsetof(Network, dhcp_relay_interface_priority) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 -DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) -DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id) -DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id) +DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_target_address) /* deprecated */ +DHCPServer.RelayAgentCircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) /* deprecated */ +DHCPServer.RelayAgentRemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_remote_id) /* deprecated */ DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.IPv6OnlyPreferredSec, config_parse_dhcp_server_ipv6_only_preferred, 0, offsetof(Network, dhcp_server_ipv6_only_preferred_usec) @@ -397,7 +397,7 @@ DHCPServer.PoolOffset, config_parse_uint32, DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) DHCPServer.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_extra_options) DHCPServer.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_vendor_options) -DHCPServer.BindToInterface, config_parse_bool, 0, offsetof(Network, dhcp_server_bind_to_interface) +DHCPServer.BindToInterface, config_parse_warn_compat, DISABLED_LEGACY, 0 DHCPServer.BootServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_boot_server_address) DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index b1569866d10da..d63eeaa47882e 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -437,7 +437,6 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID, .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), - .dhcp_server_bind_to_interface = true, .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_SIP].emit = true, @@ -773,13 +772,12 @@ static Network *network_free(Network *network) { set_free(network->dnssec_negative_trust_anchors); /* DHCP relay agent */ + iovec_done(&network->dhcp_relay_remote_id); iovec_done(&network->dhcp_relay_circuit_id); iovec_done(&network->dhcp_relay_vss); tlv_done(&network->dhcp_relay_extra_options); /* DHCP server */ - free(network->dhcp_server_relay_agent_circuit_id); - free(network->dhcp_server_relay_agent_remote_id); free(network->dhcp_server_boot_server_name); free(network->dhcp_server_boot_filename); free(network->dhcp_server_timezone); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index d9e516ff4ecaf..893b3022bb4b1 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -212,8 +212,10 @@ typedef struct Network { /* DHCP Relay Agent Support */ DHCPRelayInterfaceMode dhcp_relay_interface_mode; Address *dhcp_relay_agent_address; + struct in_addr dhcp_relay_target_address; /* for deprecated DHCPServer.RelayTarget= */ struct in_addr dhcp_relay_agent_address_in_addr; struct in_addr dhcp_relay_gateway_address; + struct iovec dhcp_relay_remote_id; /* for deprecated DHCPServer.RelayAgentRemoteId= */ struct iovec dhcp_relay_circuit_id; struct iovec dhcp_relay_vss; TLV dhcp_relay_extra_options; @@ -221,15 +223,11 @@ typedef struct Network { /* DHCP Server Support */ bool dhcp_server; - bool dhcp_server_bind_to_interface; unsigned char dhcp_server_address_prefixlen; struct in_addr dhcp_server_address_in_addr; const Address *dhcp_server_address; int dhcp_server_uplink_index; char *dhcp_server_uplink_name; - struct in_addr dhcp_server_relay_target; - char *dhcp_server_relay_agent_circuit_id; - char *dhcp_server_relay_agent_remote_id; NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; bool dhcp_server_emit_router; struct in_addr dhcp_server_router; diff --git a/test/test-network/conf/25-agent-client-peer.network b/test/test-network/conf/25-agent-client-peer.network index 4d7d758d29778..eaa3dfa57290a 100644 --- a/test/test-network/conf/25-agent-client-peer.network +++ b/test/test-network/conf/25-agent-client-peer.network @@ -3,13 +3,12 @@ Name=client-peer [Network] -Address=192.168.6.2/24 +Address=198.51.100.10/24 DHCPServer=yes -IPv4Forwarding=yes IPv6AcceptRA=no [DHCPServer] -RelayTarget=192.168.5.1 +RelayTarget=192.0.2.1 BindToInterface=no RelayAgentCircuitId=string:sample_circuit_id RelayAgentRemoteId=string:sample_remote_id diff --git a/test/test-network/conf/25-agent-client.network b/test/test-network/conf/25-agent-client.network index 219d40a9b7ca3..731f8fe5b6230 100644 --- a/test/test-network/conf/25-agent-client.network +++ b/test/test-network/conf/25-agent-client.network @@ -4,5 +4,4 @@ Name=client [Network] DHCP=yes -IPv4Forwarding=yes IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server-peer.network b/test/test-network/conf/25-agent-server-peer.network index 5e005c79ecd09..ef1e80fcadb02 100644 --- a/test/test-network/conf/25-agent-server-peer.network +++ b/test/test-network/conf/25-agent-server-peer.network @@ -3,6 +3,5 @@ Name=server-peer [Network] -Address=192.168.5.2/24 -IPv4Forwarding=yes +Address=192.0.2.2/24 IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server.network b/test/test-network/conf/25-agent-server.network deleted file mode 100644 index 0108039e6fa99..0000000000000 --- a/test/test-network/conf/25-agent-server.network +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=server - -[Network] -Address=192.168.5.1/24 -IPv4Forwarding=yes -DHCPServer=yes -IPv6AcceptRA=no - -[DHCPServer] -BindToInterface=no -PoolOffset=150 -PoolSize=1 -DNS=192.168.5.1 -NTP=192.168.5.1 diff --git a/test/test-network/conf/25-agent-veth-server.netdev b/test/test-network/conf/25-agent-veth-server.netdev deleted file mode 100644 index 1427024d4792b..0000000000000 --- a/test/test-network/conf/25-agent-veth-server.netdev +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[NetDev] -Name=server -Kind=veth -MACAddress=12:34:56:78:9b:bc - -[Peer] -Name=server-peer -MACAddress=12:34:56:78:9b:bd diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 28c418460c079..31496286472cb 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -8364,11 +8364,22 @@ def tearDown(self): tear_down_common() def test_relay_agent(self): + check_output('ip netns add ns-server') + check_output('ip link add server type veth peer server-peer') + 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='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + copy_network_unit( '25-agent-veth-client.netdev', - '25-agent-veth-server.netdev', '25-agent-client.network', - '25-agent-server.network', '25-agent-client-peer.network', '25-agent-server-peer.network', ) @@ -8378,7 +8389,7 @@ def test_relay_agent(self): output = networkctl_status('client') print(output) - self.assertRegex(output, r'Address: 192.168.5.150 \(DHCPv4 via 192.168.5.1\)') + self.assertRegex(output, r'Address: 198\.51\.100\.10[0-9] \(DHCPv4 via 192\.0\.2\.1\)') def test_relay_agent_on_bridge(self): copy_network_unit( @@ -8392,7 +8403,7 @@ def test_relay_agent_on_bridge(self): self.wait_online('bridge-relay:routable', 'client-peer:enslaved') # For issue #30763. - self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') + self.check_networkd_log('bridge-relay: Relaying DHCPv4 messages') def test_sd_dhcp_relay(self): check_output('ip netns add ns-relay') From 54c97ce6a478bdaa8a4fe88450696fd51fdeaa8c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 08:43:41 +0900 Subject: [PATCH 2074/2155] sd-dhcp-server: drop legacy DHCP relay mode --- src/libsystemd-network/dhcp-option.c | 31 --- src/libsystemd-network/dhcp-server-internal.h | 8 - .../fuzz-dhcp-server-relay.c | 45 ---- src/libsystemd-network/meson.build | 3 - src/libsystemd-network/sd-dhcp-server.c | 220 ++---------------- src/libsystemd-network/test-dhcp-server.c | 13 +- src/systemd/sd-dhcp-server.h | 5 - .../sample1 | Bin .../sample2 | Bin .../too-large-packet | Bin 10 files changed, 20 insertions(+), 305 deletions(-) delete mode 100644 src/libsystemd-network/fuzz-dhcp-server-relay.c rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/sample1 (100%) rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/sample2 (100%) rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/too-large-packet (100%) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index c5e8b29794433..7ad203f183db8 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -7,7 +7,6 @@ #include "alloc-util.h" #include "dhcp-option.h" -#include "dhcp-server-internal.h" #include "dns-domain.h" #include "hostname-util.h" #include "memory-util.h" @@ -39,8 +38,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, assert(size > 0); assert(offset); - int r; - if (code != SD_DHCP_OPTION_END) /* always make sure there is space for an END option */ size--; @@ -68,34 +65,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 3 + optlen; 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; - - if (server->agent_circuit_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, - strlen(server->agent_circuit_id), server->agent_circuit_id); - if (r < 0) - return r; - } - if (server->agent_remote_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID, - strlen(server->agent_remote_id), server->agent_remote_id); - if (r < 0) - return r; - } - - options[*offset] = code; - options[*offset + 1] = current_offset - *offset - 2; - assert(current_offset - *offset - 2 <= UINT8_MAX); - *offset = current_offset; - break; - } default: return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); } diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index a8e2cbf5c706d..d4caae66d42b7 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -32,14 +32,11 @@ typedef struct sd_dhcp_server { sd_event *event; int event_priority; sd_event_source *receive_message; - sd_event_source *receive_broadcast; int fd; int fd_raw; - int fd_broadcast; int ifindex; char *ifname; - bool bind_to_interface; be32_t address; be32_t netmask; be32_t subnet; @@ -73,11 +70,6 @@ typedef struct sd_dhcp_server { sd_dhcp_server_callback_t callback; void *callback_userdata; - struct in_addr relay_target; - - char *agent_circuit_id; - char *agent_remote_id; - int lease_dir_fd; char *lease_file; } sd_dhcp_server; diff --git a/src/libsystemd-network/fuzz-dhcp-server-relay.c b/src/libsystemd-network/fuzz-dhcp-server-relay.c deleted file mode 100644 index b2c881c06773a..0000000000000 --- a/src/libsystemd-network/fuzz-dhcp-server-relay.c +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "fuzz.h" -#include "sd-dhcp-server.c" - -ssize_t sendto(int __fd, const void *__buf, size_t __n, int flags, const struct sockaddr *__addr, socklen_t __addr_len) { - return __n; -} - -ssize_t sendmsg(int __fd, const struct msghdr *__message, int flags) { - return 0; -} - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - struct in_addr address = {.s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - union in_addr_union relay_address; - _cleanup_free_ uint8_t *message = NULL; - - if (size < sizeof(DHCPMessage)) - return 0; - - fuzz_setup_logging(); - - assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); - assert_se(in_addr_from_string(AF_INET, "192.168.5.1", &relay_address) >= 0); - assert_se(sd_dhcp_server_set_relay_target(server, &relay_address.in) >= 0); - assert_se(sd_dhcp_server_set_bind_to_interface(server, false) >= 0); - assert_se(sd_dhcp_server_set_relay_agent_information(server, "string:sample_circuit_id", "string:sample_remote_id") >= 0); - - size_t buflen = size; - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - assert_se(message = malloc(buflen)); - memcpy(message, data, size); - - server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(server->fd >= 0); - - (void) dhcp_server_relay_message(server, (DHCPMessage *) message, size - sizeof(DHCPMessage), buflen); - return 0; -} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 5d2368cc9881a..9b36f6edf2cf6 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -145,9 +145,6 @@ executables += [ network_fuzz_template + { 'sources' : files('fuzz-dhcp-server.c'), }, - network_fuzz_template + { - 'sources' : files('fuzz-dhcp-server-relay.c'), - }, network_fuzz_template + { 'sources' : files('fuzz-lldp-rx.c'), }, diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fc28469a86ccb..07f15ef6dc20c 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -113,12 +113,6 @@ int sd_dhcp_server_is_running(sd_dhcp_server *server) { return !!server->receive_message; } -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) { - assert_return(server, -EINVAL); - - return in4_addr_is_set(&server->relay_target); -} - static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { assert(server); @@ -142,9 +136,6 @@ static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { tlv_unref(server->extra_options); tlv_unref(server->vendor_options); - free(server->agent_circuit_id); - free(server->agent_remote_id); - safe_close(server->lease_dir_fd); free(server->lease_file); @@ -168,11 +159,9 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { .n_ref = 1, .fd_raw = -EBADF, .fd = -EBADF, - .fd_broadcast = -EBADF, .address = htobe32(INADDR_ANY), .netmask = htobe32(INADDR_ANY), .ifindex = ifindex, - .bind_to_interface = true, .default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC, .max_lease_time = DHCP_MAX_LEASE_TIME_USEC, .rapid_commit = true, @@ -289,11 +278,9 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { running = sd_dhcp_server_is_running(server); server->receive_message = sd_event_source_disable_unref(server->receive_message); - server->receive_broadcast = sd_event_source_disable_unref(server->receive_broadcast); server->fd_raw = safe_close(server->fd_raw); server->fd = safe_close(server->fd); - server->fd_broadcast = safe_close(server->fd_broadcast); if (running) log_dhcp_server(server, "STOPPED"); @@ -385,27 +372,21 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, assert(message); assert(len >= sizeof(DHCPMessage)); - if (server->bind_to_interface) { - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); - cmsg = CMSG_FIRSTHDR(&msg); - assert(cmsg); + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - /* we attach source interface and address info to the message - rather than binding the socket. This will be mostly useful - when we gain support for arbitrary number of server addresses - */ - pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); - assert(pktinfo); + pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); + assert(pktinfo); - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - } + pktinfo->ipi_ifindex = server->ifindex; + pktinfo->ipi_spec_dst.s_addr = server->address; if (sendmsg(server->fd, &msg, 0) < 0) return -errno; @@ -1000,83 +981,6 @@ static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { return true; } -static int append_agent_information_option(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t size) { - int r; - size_t offset; - - assert(server); - assert(message); - - r = dhcp_option_find_option(message->options, opt_length, SD_DHCP_OPTION_END, &offset); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, 0, server); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - return offset; -} - -static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t buflen) { - _cleanup_free_ DHCPPacket *packet = NULL; - int r; - - assert(server); - assert(message); - assert(sd_dhcp_server_is_in_relay_mode(server)); - - if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen)) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) received message without/invalid hardware address, discarding."); - - if (message->op == BOOTREQUEST) { - log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid)); - if (message->hops >= 16) - return -ETIME; - message->hops++; - - /* https://tools.ietf.org/html/rfc1542#section-4.1.1 */ - if (message->giaddr == 0) - message->giaddr = server->address; - - if (server->agent_circuit_id || server->agent_remote_id) { - r = append_agent_information_option(server, message, opt_length, buflen - sizeof(DHCPMessage)); - if (r < 0) - return log_dhcp_server_errno(server, r, "could not append relay option: %m"); - opt_length = r; - } - - return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length); - } else if (message->op == BOOTREPLY) { - log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid)); - if (message->giaddr != server->address) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) BOOTREPLY giaddr mismatch, discarding"); - - int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL); - if (message_type < 0) - return message_type; - - packet = malloc0(sizeof(DHCPPacket) + opt_length); - if (!packet) - return -ENOMEM; - memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length); - - r = dhcp_option_remove_option(packet->dhcp.options, opt_length, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); - if (r > 0) - opt_length = r; - - bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK; - const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr; - return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast); - } - return -EBADMSG; -} - static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { usec_t expiration; int r; @@ -1315,15 +1219,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz return 0; } -static size_t relay_agent_information_length(const char* agent_circuit_id, const char* agent_remote_id) { - size_t sum = 0; - if (agent_circuit_id) - sum += 2 + strlen(agent_circuit_id); - if (agent_remote_id) - sum += 2 + strlen(agent_remote_id); - return sum; -} - static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_free_ DHCPMessage *message = NULL; @@ -1351,10 +1246,6 @@ static int server_receive_message(sd_event_source *s, int fd, } size_t buflen = datagram_size; - if (sd_dhcp_server_is_in_relay_mode(server)) - /* Preallocate the additional size for DHCP Relay Agent Information Option if needed */ - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - message = malloc0(buflen); if (!message) return -ENOMEM; @@ -1377,15 +1268,10 @@ static int server_receive_message(sd_event_source *s, int fd, if (info && info->ipi_ifindex != server->ifindex) return 0; - if (sd_dhcp_server_is_in_relay_mode(server)) { - r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't relay message, ignoring: %m"); - } else { - r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); - } + r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + if (r < 0) + log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); + return 0; } @@ -1424,10 +1310,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { } server->fd_raw = r; - if (server->bind_to_interface) - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); - else - r = dhcp_network_bind_udp_socket(0, server->address, DHCP_PORT_SERVER, -1); + r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); if (r < 0) goto on_error; server->fd = r; @@ -1443,25 +1326,6 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { if (r < 0) goto on_error; - if (!server->bind_to_interface) { - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_BROADCAST, DHCP_PORT_SERVER, -1); - if (r < 0) - goto on_error; - - server->fd_broadcast = r; - - r = sd_event_add_io(server->event, &server->receive_broadcast, - server->fd_broadcast, EPOLLIN, - server_receive_message, server); - if (r < 0) - goto on_error; - - r = sd_event_source_set_priority(server->receive_broadcast, - server->event_priority); - if (r < 0) - goto on_error; - } - r = dhcp_server_load_leases(server); if (r < 0) log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); @@ -1490,18 +1354,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { return r; } -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled) { - assert_return(server, -EINVAL); - assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - - if (!!enabled == server->bind_to_interface) - return 0; - - server->bind_to_interface = enabled; - - return 1; -} - int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { int r; @@ -1649,46 +1501,6 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_ return 0; } -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) - return 0; - - server->relay_target = *address; - return 1; -} - -int sd_dhcp_server_set_relay_agent_information( - sd_dhcp_server *server, - const char *agent_circuit_id, - const char *agent_remote_id) { - _cleanup_free_ char *circuit_id_dup = NULL, *remote_id_dup = NULL; - - assert_return(server, -EINVAL); - - if (relay_agent_information_length(agent_circuit_id, agent_remote_id) > UINT8_MAX) - return -ENOBUFS; - - if (agent_circuit_id) { - circuit_id_dup = strdup(agent_circuit_id); - if (!circuit_id_dup) - return -ENOMEM; - } - - if (agent_remote_id) { - remote_id_dup = strdup(agent_remote_id); - if (!remote_id_dup) - return -ENOMEM; - } - - free_and_replace(server->agent_circuit_id, circuit_id_dup); - free_and_replace(server->agent_remote_id, remote_id_dup); - return 0; -} - int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path) { int r; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 4da4fdb116b5b..34263f044d082 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -22,7 +22,7 @@ static void test_pool(struct in_addr *address, unsigned size, int ret) { ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret)); } -static int test_basic(bool bind_to_interface) { +static int test_basic(void) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; struct in_addr address_lo = { @@ -33,14 +33,13 @@ static int test_basic(bool bind_to_interface) { }; int r; - log_debug("/* %s(bind_to_interface=%s) */", __func__, yes_no(bind_to_interface)); + log_debug("/* %s */", __func__); ASSERT_OK(sd_event_new(&event)); /* attach to loopback interface */ ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_NOT_NULL(server); - server->bind_to_interface = bind_to_interface; ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0)); ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY)); @@ -410,13 +409,9 @@ int main(int argc, char *argv[]) { test_static_lease(); test_domain_name(); - r = test_basic(true); - if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(bound to interface)"); - - r = test_basic(false); + r = test_basic(); if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(non-bound to interface)"); + return log_tests_skipped_errno(r, "cannot start dhcp server"); test_message_handler(); diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 1fb7d2e9ee60e..8c3dbab9b4238 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -57,7 +57,6 @@ int sd_dhcp_server_configure_pool(sd_dhcp_server *server, const struct in_addr * int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address); 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); -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled); int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz); int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name); int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *router); @@ -90,10 +89,6 @@ int sd_dhcp_server_set_rapid_commit(sd_dhcp_server *server, int enabled); int sd_dhcp_server_forcerenew(sd_dhcp_server *server); -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server); -int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address); -int sd_dhcp_server_set_relay_agent_information(sd_dhcp_server *server, const char* circuit_id, const char* remote_id); - int sd_dhcp_server_get_lease_address_by_name(sd_dhcp_server *server, const char *name, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample1 b/test/fuzz/fuzz-dhcp-relay/sample1 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample1 rename to test/fuzz/fuzz-dhcp-relay/sample1 diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample2 b/test/fuzz/fuzz-dhcp-relay/sample2 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample2 rename to test/fuzz/fuzz-dhcp-relay/sample2 diff --git a/test/fuzz/fuzz-dhcp-server-relay/too-large-packet b/test/fuzz/fuzz-dhcp-relay/too-large-packet similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/too-large-packet rename to test/fuzz/fuzz-dhcp-relay/too-large-packet From 51120d1d31a311199e18250e8c7bcee9ecbe3115 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 12 May 2026 15:15:08 +0900 Subject: [PATCH 2075/2155] NEWS: mention new DHCP relay agent --- NEWS | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/NEWS b/NEWS index 386b803cf6b03..42705e21f6d5a 100644 --- a/NEWS +++ b/NEWS @@ -73,6 +73,27 @@ CHANGES WITH 261 in spe: effectively means live upgrades from versions older than v247 are not supported anymore. + * systemd-networkd gained a new sd-dhcp-relay backend for DHCP relay + agent support. As part of this change, the following [DHCPServer] + settings are deprecated: + - BindToInterface= + - RelayTarget= + - RelayAgentCircuitId= + - RelayAgentRemoteId= + They are replaced by DHCPRelay= in [Network], along with new + [DHCPRelay] section settings in .network files: + - AgentAddress= + - GatewayAddress= + - CircuitId= + - VirtualSubnetSelection= + - ExtraOption= + - InterfacePriority= + and in networkd.conf: + - ServerAddress= + - OverrideServerIdentifier= + - RemoteId= + - ExtraOption= + * Required version of musl (when built with -Dlibc=musl) has been raised from 1.2.5 to 1.2.6. From dadc77c5fb7e79878831bb204e630850ab7e2a1d Mon Sep 17 00:00:00 2001 From: Aritra Basu Date: Tue, 21 Apr 2026 19:09:20 -0400 Subject: [PATCH 2076/2155] network: restart DHCPv6, NDisc, and RADV when tracked IPv6LL is dropped When the tracked IPv6 link-local address is removed, networkd clears link->ipv6ll_address, but keeps DHCPv6, NDisc, and RADV running. These engines keep using a stale source identity which affects the following: - DHCPv6 client continues to send Solicit/Renew/Rebind from a nonexistent source address. - NDisc continues to send Router Solicitations from a nonexistent source address. Router Advertisements cannot be received properly. - RADV continues to advertise with a stale source address. This can lead to downstream hosts configuring invalid routes. - DHCP-PD prefixes remain configured without a valid upstream DHCPv6 path. Added link_ipv6ll_lost() to stop IPv6 dynamic engines and related states: - sd_dhcp6_client_stop() - ndisc_stop() + ndisc_flush() - sd_radv_stop() This is called from address_drop() when the dropped address matches the tracked IPv6LL. After clearing the tracked address, it scans for another ready link-local address on the interface. If found, this is set as link->ipv6ll_address and link_ipv6ll_gained() is called to restart the engines with the new source identity. This resolves the FIXME in address_drop(). Signed-off-by: Aritra Basu --- src/network/networkd-address.c | 36 ++++++++++++++++++++++++++++-- src/network/networkd-link.c | 40 ++++++++++++++++++++++++++++++++++ src/network/networkd-link.h | 1 + src/network/networkd-radv.c | 10 +++++---- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 193c4a04b5f44..3880eab47fe9a 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -886,11 +886,43 @@ static int address_drop(Address *in, bool removed_by_us) { address_del_netlabel(address); - /* FIXME: if the IPv6LL address is dropped, stop DHCPv6, NDISC, RADV. */ if (address->family == AF_INET6 && - in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) + in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) { link->ipv6ll_address = (const struct in6_addr) {}; + Address *a; + bool has_replacement; + + /* If another ready IPv6LL address exists on this link, use it instead. */ + SET_FOREACH(a, link->addresses) { + if (a == address) + continue; + if (a->family != AF_INET6) + continue; + if (!in6_addr_is_link_local(&a->in_addr.in6)) + continue; + if (!address_is_ready(a)) + continue; + link->ipv6ll_address = a->in_addr.in6; + break; + } + + has_replacement = in6_addr_is_set(&link->ipv6ll_address); + + /* Stop engines bound to the dropped IPv6LL source address. Do not return early on error. + * address_detach() and link_update_operstate() must run to keep link state consistent. */ + r = link_ipv6ll_lost(link, &address->in_addr.in6, has_replacement); + if (r < 0) + log_link_warning_errno(link, r, "Failed to stop IPv6 services after IPv6LL loss, ignoring: %m"); + + /* If another IPv6LL address is available, restart engines with it. */ + if (has_replacement) { + r = link_ipv6ll_gained(link); + if (r < 0) + log_link_warning_errno(link, r, "Failed to restart IPv6 services with alternate IPv6LL address, ignoring: %m"); + } + } + ipv4acd_detach(link, address); address_detach(address); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a69a5e8979c3c..16fb529797d42 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -813,6 +813,46 @@ int link_ipv6ll_gained(Link *link) { return 0; } +int link_ipv6ll_lost(Link *link, const struct in6_addr *dropped_ipv6ll, bool has_replacement) { + int ret = 0, r; + + assert(link); + assert(dropped_ipv6ll); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + log_link_info(link, "Lost IPv6LL address %s%s.", + IN6_ADDR_TO_STRING(dropped_ipv6ll), + has_replacement ? ", switching to alternate IPv6LL source" : ""); + + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m")); + + /* DHCPv6 must be restarted to switch the client's source address, while NDisc and + * RADV can switch to the replacement IPv6LL in link_ipv6ll_gained() without flushing + * learned state. Keep link->ndisc_configured as-is in this path. */ + if (has_replacement) + return ret; + + r = ndisc_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m")); + link->ndisc_configured = false; + ndisc_flush(link); + + r = sd_radv_stop(link->radv); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Advertisement: %m")); + + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_IPV6LL); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not reconfigure stacked netdevs after IPv6LL loss: %m")); + + return ret; +} + int link_handle_bound_to_list(Link *link) { bool required_up = false; Link *l; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 456ec99185624..b51736ae456b8 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -233,6 +233,7 @@ bool link_multicast_enabled(Link *link); bool link_ipv6_enabled(Link *link); int link_ipv6ll_gained(Link *link); +int link_ipv6ll_lost(Link *link, const struct in6_addr *dropped_ipv6ll, bool has_replacement); bool link_has_ipv6_connectivity(Link *link); int link_stop_engines(Link *link, bool may_keep_dynamic); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 02322d4dabad5..1aafc0dd6d429 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -697,6 +697,12 @@ int radv_start(Link *link) { if (in6_addr_is_null(&link->ipv6ll_address)) return 0; + /* Update the source IPv6LL before the running check so replacement IPv6LL handover can + * rebind RADV without requiring stop/start. */ + r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); + if (r < 0) + return r; + if (sd_radv_is_running(link->radv)) return 0; @@ -706,10 +712,6 @@ int radv_start(Link *link) { return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); } - r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); - if (r < 0) - return r; - log_link_debug(link, "Starting IPv6 Router Advertisements"); return sd_radv_start(link->radv); } From 34db7a21a670254b241898ade4ef6242834803a8 Mon Sep 17 00:00:00 2001 From: Sebastian Bernardt Date: Sat, 18 Apr 2026 21:31:04 +1000 Subject: [PATCH 2077/2155] cleanup: VLAN Id -> VLAN ID "VLAN ID" is used throughout the documentation and codebase, but appears (when present) in `networkctl status` tables as `VLan Id`. --- src/network/networkctl-status-link.c | 2 +- src/network/networkd-bridge-fdb.c | 2 +- src/shared/vlan-util.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index dc765a74f1216..5083898ec3cec 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -570,7 +570,7 @@ static int link_status_one( } else if (streq_ptr(info->netdev_kind, "vlan") && info->vlan_id > 0) { r = table_add_many(table, - TABLE_FIELD, "VLan Id", + TABLE_FIELD, "VLAN ID", TABLE_UINT16, info->vlan_id); if (r < 0) return table_log_add_error(r); diff --git a/src/network/networkd-bridge-fdb.c b/src/network/networkd-bridge-fdb.c index 6a635a880185a..ff9356a444e65 100644 --- a/src/network/networkd-bridge-fdb.c +++ b/src/network/networkd-bridge-fdb.c @@ -139,7 +139,7 @@ static int bridge_fdb_configure_message(const BridgeFDB *fdb, Link *link, sd_net if (r < 0) return r; - /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */ + /* VLAN ID is optional. We'll add VLAN ID only if it's specified. */ if (fdb->vlan_id > 0) { r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb->vlan_id); if (r < 0) diff --git a/src/shared/vlan-util.h b/src/shared/vlan-util.h index b02001f238397..51efb3c7cccbd 100644 --- a/src/shared/vlan-util.h +++ b/src/shared/vlan-util.h @@ -7,7 +7,7 @@ #define VLANID_MAX 4094 #define VLANID_INVALID UINT16_MAX -/* Note that we permit VLAN Id 0 here, as that is apparently OK by the Linux kernel */ +/* Note that we permit VLAN ID 0 here, as that is apparently OK by the Linux kernel */ static inline bool vlanid_is_valid(uint16_t id) { return id <= VLANID_MAX; } From 00276b963c54f2025085f313b80d9b70bd57cca6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 18 May 2026 15:49:23 +0100 Subject: [PATCH 2078/2155] test: avoid test-fdstore failing units when exiting This is used as payload, so ensure the wrapping unit/container doesn't register a failure when this exits Follow-up for 9de91f0f4c715b278637af8b73cabac892d7e000 --- src/test/test-fdstore.c | 21 +++++++++++++++++---- test/units/TEST-13-NSPAWN.unpriv.sh | 2 +- test/units/TEST-91-LIVEUPDATE.sh | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/test/test-fdstore.c b/src/test/test-fdstore.c index 6469d396e1cea..0d7c16faf7d36 100644 --- a/src/test/test-fdstore.c +++ b/src/test/test-fdstore.c @@ -6,6 +6,7 @@ * * This binary is intentionally linked against libsystemd only so that it can go in the minimal image. */ +#include #include #include #include @@ -28,6 +29,10 @@ static void closep(int *fd) { *fd = -EBADF; } +static _Noreturn void exit_handler(int sig) { + _exit(EXIT_SUCCESS); +} + static int push_one(const char *fdname, const char *content) { _cleanup_(closep) int fd = -EBADF; int r; @@ -159,8 +164,16 @@ int main(int argc, char *argv[]) { if (r != EXIT_SUCCESS) return r; - /* On success, become sleep so if we are a container payload it can stay alive. */ - execlp("sleep", "sleep", "infinity", (char *) NULL); - fprintf(stderr, "execlp(sleep) failed: %m\n"); - return EXIT_FAILURE; + /* On success, stay alive so if we are a container payload we keep it running. Install handlers + * for the signals an outer supervisor may use to terminate us, so we exit cleanly (with status 0) + * and the container service ends up in 'inactive' rather than 'failed'. */ + struct sigaction sa = { .sa_handler = exit_handler }; + if (sigaction(SIGTERM, &sa, /* __oact= */ NULL) < 0 || + sigaction(SIGINT, &sa, /* __oact= */ NULL) < 0) { + fprintf(stderr, "Failed to install signal handlers: %m\n"); + return EXIT_FAILURE; + } + + for (;;) + pause(); } diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index 19b1d445c89d9..f14e3121d41d6 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -336,7 +336,7 @@ systemd-dissect --shift /home/testuser/.local/state/machines/fdstore foreign run0 -u testuser mkdir -p .config/systemd/nspawn/ run0 -u testuser -i "cat >.config/systemd/nspawn/fdstore.nspawn </run/systemd/nspawn/fdstore.nspawn </run/systemd/nspawn/fdstore.nspawn < Date: Mon, 18 May 2026 15:47:32 +0100 Subject: [PATCH 2079/2155] core: add FileDescriptorStorePreserve=on-success option Currently with FileDescriptorStorePreserve=yes the FD store is kept around regardless of what happens to a unit, which is useful in many cases. But in some cases, for example when complex services crash horribly, it's hard to reason about what was in the intermediate state, and it's better to start fresh. Add a new 'on-success' option for the FileDescriptorStorePreserve= setting that keeps it around only for as long as the unit doesn't go to a persistently failed state. This is especially useful in combination with LUO, where we don't want to keep around LUO sessions created by units that then proceeded to crash and burn, and might be in a bad state afterwards. --- docs/FILE_DESCRIPTOR_STORE.md | 5 +- man/systemd.service.xml | 37 ++++++++------ src/core/execute.c | 7 +-- src/core/execute.h | 1 + src/core/luo.c | 2 +- src/core/service.c | 14 +++--- src/shared/varlink-io.systemd.Unit.c | 3 +- test/units/TEST-13-NSPAWN.unpriv.sh | 20 ++++++-- test/units/TEST-91-LIVEUPDATE.sh | 72 ++++++++++++++++++++++++++++ 9 files changed, 131 insertions(+), 30 deletions(-) diff --git a/docs/FILE_DESCRIPTOR_STORE.md b/docs/FILE_DESCRIPTOR_STORE.md index c3336453781ca..6e1744510592c 100644 --- a/docs/FILE_DESCRIPTOR_STORE.md +++ b/docs/FILE_DESCRIPTOR_STORE.md @@ -123,7 +123,10 @@ This behavior can be modified via the [`FileDescriptorStorePreserve=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#FileDescriptorStorePreserve=) setting in service unit files. If set to `yes` the fdstore will be kept as long as the service definition is loaded into memory by the service manager, i.e. as -long as at least one other loaded unit has a reference to it. +long as at least one other loaded unit has a reference to it. If set to +`on-success` the behaviour is the same as `yes`, except that the fdstore is +discarded once the service enters the permanent `failed` state, i.e. after all +automated restart attempts driven by `Restart=` have been exhausted. The `systemctl clean --what=fdstore …` command may be used to explicitly clear the fdstore of a service. This is only allowed when the service is fully diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 028c1144b0820..0c5f222bb7a09 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1245,29 +1245,36 @@ RestartMaxDelaySec=160s FileDescriptorStorePreserve= Takes one of no, yes, - restart and controls when to release the service's file descriptor store - (i.e. when to close the contained file descriptors, if any). If set to no the - 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 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. - - When set to yes, and the service is itself running under another service - manager (e.g. a service of user@.service, or a payload inside + restart, on-success and controls when to release the + service's file descriptor store (i.e. when to close the contained file descriptors, if any). If set + to no the 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 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. If on-success + the behaviour is identical to yes, except that the file descriptor store is + discarded if the unit enters the permanent failed state (i.e. once all automated + restart attempts driven by Restart= have been exhausted). The store is preserved + across the transitionary failed states that precede each individual auto-restart attempt. + + When set to yes or on-success, and the service is + itself running under another service manager (e.g. a service of user@.service, + or a payload inside systemd-nspawn1), file descriptors pushed into the store are also forwarded one level up via the enveloping manager's $NOTIFY_SOCKET, tagged with the originating unit id, so that they are preserved across restarts of the inner manager and handed back to the originating unit when it is started again. For this to take effect, the enveloping unit must itself enable - FileDescriptorStoreMax= and FileDescriptorStorePreserve=yes. + FileDescriptorStoreMax= and a non-no/restart + value for FileDescriptorStorePreserve=. See the File Descriptor Store overview for details. - Setting this to yes also ensures the file descriptor store is kept loaded - across a kexec-based reboot on kernels supporting the Setting this to yes or on-success also ensures the + file descriptor store is kept loaded across a kexec-based reboot on kernels + supporting the Live Update Orchestrator, so that compatible file descriptors (such as memfd_create2) diff --git a/src/core/execute.c b/src/core/execute.c index 7935da743164b..ff6ef2de0457c 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3182,9 +3182,10 @@ static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = { - [EXEC_PRESERVE_NO] = "no", - [EXEC_PRESERVE_YES] = "yes", - [EXEC_PRESERVE_RESTART] = "restart", + [EXEC_PRESERVE_NO] = "no", + [EXEC_PRESERVE_YES] = "yes", + [EXEC_PRESERVE_RESTART] = "restart", + [EXEC_PRESERVE_ON_SUCCESS] = "on-success", }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES); diff --git a/src/core/execute.h b/src/core/execute.h index 4553ce9d84d0b..109dd774fcc5a 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -61,6 +61,7 @@ typedef enum ExecPreserveMode { EXEC_PRESERVE_NO, EXEC_PRESERVE_YES, EXEC_PRESERVE_RESTART, + EXEC_PRESERVE_ON_SUCCESS, _EXEC_PRESERVE_MODE_MAX, _EXEC_PRESERVE_MODE_INVALID = -EINVAL, } ExecPreserveMode; diff --git a/src/core/luo.c b/src/core/luo.c index 929a0e3f49575..91c3a01dad832 100644 --- a/src/core/luo.c +++ b/src/core/luo.c @@ -204,7 +204,7 @@ int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { s = SERVICE(u); - if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) + if (!IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) continue; if (!s->fd_store) diff --git a/src/core/service.c b/src/core/service.c index 6a02ca8d6f983..6997866f883f0 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -699,7 +699,7 @@ int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, * unit and the original fdname. This way fdstore persistence chains all the way up to whichever * entity is ultimately responsible for surviving across kexec/restart, regardless of fdname * length or charset constraints. */ - if (propagate_upstream && s->fd_store_preserve_mode == EXEC_PRESERVE_YES) { + if (propagate_upstream && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) { Manager *m = ASSERT_PTR(UNIT(s)->manager); char idx_str[STRLEN(SERVICE_FDSTORE_SUB_FDNAME_PREFIX) + DECIMAL_STR_MAX(uint64_t)]; @@ -721,7 +721,7 @@ int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, LIST_PREPEND(fd_store, s->fd_store, TAKE_PTR(fs)); s->n_fd_store++; - if (propagate_upstream && s->fd_store_preserve_mode == EXEC_PRESERVE_YES) + if (propagate_upstream && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) /* Refresh the JSON mapping memfd so the supervisor can resolve the new index. Do this * after LIST_PREPEND so the new entry is visible to the helper. */ (void) service_propagate_fd_store_mapping_upstream(UNIT(s)->manager); @@ -876,7 +876,7 @@ static int service_attach_external_fd_to_fdstore(Unit *u, int fd, const char *fd if (r > 0 && s->state == SERVICE_DEAD && s->deserialized_state == SERVICE_DEAD && - s->fd_store_preserve_mode == EXEC_PRESERVE_YES) { + IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) { service_set_state(s, SERVICE_DEAD_RESOURCES_PINNED); s->deserialized_state = SERVICE_DEAD_RESOURCES_PINNED; } @@ -2350,7 +2350,7 @@ static bool service_will_restart(Unit *u) { static ServiceState service_determine_dead_state(Service *s) { assert(s); - return SERVICE_FD_STORE_POPULATED(s) && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; + return SERVICE_FD_STORE_POPULATED(s) && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS) ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; } static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { @@ -2454,7 +2454,8 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) unit_destroy_runtime_data(UNIT(s), &s->exec_context, /* destroy_runtime_dir= */ true); /* Also get rid of the fd store, if that's configured. */ - if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO) + if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO || + (s->fd_store_preserve_mode == EXEC_PRESERVE_ON_SUCCESS && s->state == SERVICE_FAILED)) service_release_fd_store(s); /* Get rid of the IPC bits of the user */ @@ -6119,7 +6120,8 @@ static void service_release_resources(Unit *u) { service_release_extra_fds(s); s->root_directory_fd = asynchronous_close(s->root_directory_fd); - if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) + if (IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_NO, EXEC_PRESERVE_RESTART) || + (s->fd_store_preserve_mode == EXEC_PRESERVE_ON_SUCCESS && s->state == SERVICE_FAILED)) service_release_fd_store(s); if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 2ed91121e4875..0c63725c75077 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -25,7 +25,8 @@ 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_VALUE(restart), + SD_VARLINK_DEFINE_ENUM_VALUE(on_success)); SD_VARLINK_DEFINE_ENUM_TYPE( ExecKeyringMode, diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index f14e3121d41d6..31a75eae3f10a 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -343,7 +343,7 @@ run0 -u testuser mkdir -p ".config/systemd/user/systemd-nspawn@fdstore.service.d run0 -u testuser -i "cat >.config/systemd/user/systemd-nspawn@fdstore.service.d/fdstore.conf </dev/null || true loginctl disable-linger testuser diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh index 802aaae1b5d49..cd4bc3bc37054 100755 --- a/test/units/TEST-91-LIVEUPDATE.sh +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -138,6 +138,78 @@ EOF n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late-zerofds.service) test "$n_fds" -eq 0 systemctl start TEST-91-LIVEUPDATE-late-zerofds.service + + # Verify that with FileDescriptorStorePreserve=on-success the fdstore is + # discarded once the unit enters the permanent failed state, while still + # being preserved across the transitionary failed states that precede + # each automated auto-restart attempt. Use Restart=on-failure with + # StartLimitBurst=2 so the manager runs the helper twice before + # giving up. The helper: + # - on the first attempt pushes an fd into the fdstore, becomes ready, + # and then crashes, + # - on subsequent attempts asserts that the previously stored fd is + # handed back via $LISTEN_FDS (proving the fdstore survived the + # auto-restart) and then crashes again. + # When the start-limit is hit the unit lands in the permanent failed + # state, at which point the fdstore must be empty. + cat >/run/TEST-91-LIVEUPDATE-failure.sh <<'EOF' +#!/usr/bin/env bash +set -eux +state_file=/run/TEST-91-LIVEUPDATE-failure.attempt +attempt=$(cat "$state_file" 2>/dev/null || echo 0) +attempt=$((attempt + 1)) +echo "$attempt" > "$state_file" +if [[ "$attempt" -eq 1 ]]; then + systemd-notify --fd=0 --fdname=mem /run/systemd/system/TEST-91-LIVEUPDATE-failure.service < Date: Thu, 21 May 2026 10:11:53 +0200 Subject: [PATCH 2080/2155] docs: extend credentials docs with notes about imds --- docs/CREDENTIALS.md | 75 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/docs/CREDENTIALS.md b/docs/CREDENTIALS.md index abfdfb0ad297b..149cf2f7db274 100644 --- a/docs/CREDENTIALS.md +++ b/docs/CREDENTIALS.md @@ -52,9 +52,9 @@ purpose. Specifically, the following features are provided: 7. Credentials may be acquired from a hosting VM hypervisor (SMBIOS OEM strings or qemu `fw_cfg`), a hosting container manager, the kernel command line, - from the initrd, or from the UEFI environment via the EFI System Partition - (via `systemd-stub`). Such system credentials may then be propagated into - individual services as needed. + from the initrd, from the UEFI environment via the EFI System Partition + (via `systemd-stub`), or from a cloud Instance Metadata Service (IMDS). Such + system credentials may then be propagated into individual services as needed. 8. Credentials are an effective way to pass parameters into services that run with `RootImage=` or `RootDirectory=` and thus cannot read these resources @@ -67,7 +67,7 @@ purpose. Specifically, the following features are provided: ## Configuring per-Service Credentials -Within unit files, there are the following settings to configure service +Within unit files, there are the following settings to configure service credentials. 1. `LoadCredential=` may be used to load a credential from disk, from an @@ -170,7 +170,7 @@ plaintext form will be placed in `$CREDENTIALS_DIRECTORY`. Use a command such as `systemd-creds --system cat …` to access both forms of credentials, and decrypt them if needed (see [systemd-creds(1)](https://www.freedesktop.org/software/systemd/man/latest/systemd-creds.html) -for details. +for details). Note that generators typically run very early during boot (similar to initrd code), earlier than the `/var/` file system is necessarily mounted (which is @@ -366,6 +366,71 @@ Or propagated to services further down: systemd-run -p ImportCredential=mycred -P --wait systemd-creds cat mycred ``` +## Acquisition from Cloud Instance Metadata Services (IMDS) + +Most public cloud environments provide an "Instance Metadata Service" (IMDS), +i.e. a node-local network endpoint from which a virtual machine may acquire +information about itself and the parameters it was invoked with, including data +suitable for use as `systemd` credentials. `systemd` can automatically acquire +such data from the IMDS and import it into the system's credential store, where +it is then available to services via `ImportCredential=` and friends, the same +way as credentials acquired through any of the mechanisms described above. + +This functionality is implemented by +[`systemd-imdsd@.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imdsd@.service.html), +which provides a local Varlink IPC interface to the IMDS endpoint, and the +[`systemd-imds`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imds.html) +client tool. On recognized clouds these are pulled into the boot transaction +automatically by +[`systemd-imds-generator`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imds-generator.html), +which detects the cloud environment via the SMBIOS/DMI hardware database +(`hwdb.d/40-imds.hwdb`). + +At boot the `systemd-imds-import.service` runs `systemd-imds --import`, which +acquires data from the IMDS endpoint and writes the relevant fields as +credentials into `/run/credstore/` (and `/run/credstore.encrypted/` for +encrypted credentials). Because these directories are part of the credential +search path (see "Relevant Paths" below), credentials imported this way are +automatically picked up by `ImportCredential=` in consuming units. Specifically, +the following is imported: + +1. The instance's "userdata" field, if it is a JSON object containing a + `systemd.credentials` array. Each array entry carries a `name` field plus + one of `text` (a literal string), `data` (Base64-encoded binary data), or + `encrypted` (a Base64-encoded encrypted credential, as produced by + `systemd-creds encrypt`). This is the primary mechanism for passing + arbitrary credentials into a cloud instance. Note: if a traditional IMDS + client (such as `cloud-init`) is used the "userdata" field might be used for + that, and `systemd-imds-import.service` will gracefully ignore the data. Or + in other words: this functionality is not available if `cloud-init` is used. + +2. The instance's SSH public key, imported into the `ssh.authorized_keys.root` + credential (used to provision SSH access for the `root` user). + +3. The instance's hostname, imported into the `firstboot.hostname` credential + (consumed by `systemd-firstboot.service`). + +The acquired userdata is measured into the TPM2 (PCR 12) before it is imported, +so that the cloud-provided parameterization may be subjected to attestation. + +An example `systemd.credentials` userdata payload (passed as the instance's user +data via the cloud's provisioning interface) looks like this: + +```json +{ + "systemd.credentials": [ + { + "name": "mycred", + "text": "supersecret" + }, + { + "name": "mybinarycred", + "data": "YmFyCg==" + } + ] +} +``` + ## Well-Known Credentials Various services shipped with `systemd` consume credentials for tweaking behaviour: From 8533513aff3cc5bdb03b82ef503489ee8683478e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 12 Nov 2025 17:53:47 +0100 Subject: [PATCH 2081/2155] Introduce support for running code in fibers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Traditionally, asynchronous programming in systemd has been achieved using sd-event along with the asynchronous interfaces of sd-bus and sd-varlink. This works well when the system is reacting to events and all code triggered by those events can run without blocking. In these scenarios, the global Manager object is passed as userdata to the callback, and the callback can use the stack as usual, declaring local state and ensuring proper cleanup via _cleanup_. Control flow structures, such as loops, work as expected, and everything runs smoothly. However, challenges arise when the code needs to perform long-running operations within these callbacks. Since the system cannot block execution within the callback, we can't directly invoke a long-running operation and wait for its result without introducing complexities. Instead, we need to initiate the long-running task, register for completion with sd-event, sd-bus, or sd-varlink, and provide a callback to be invoked when the operation completes. This callback, however, only receives a single userdata pointer, which forces us to bundle all local variables into a struct and pass it along as part of the callback. On top of that, after queuing the asynchronous operation, the caller continues executing. As the caller's stack unwinds when the function exits, the resources and state within the local scope may be prematurely cleaned up. Therefore, the struct must store copies of the local variables or ensure proper reference counting to prevent premature resource cleanup. When multiple long-running operations need to be initiated within a loop, the complexity grows further. We must introduce additional shared state to track the completion of all operations before we can run any code that depends on their results. Furthermore, since the daemon may be shut down at any time, we must track the lifecycle of each long-running operation in the global Manager struct, ensuring proper cleanup even when stack unwinding can no longer manage the resources for us. Fibers, or green threads, provide a more natural way of handling asynchronous operations. By enabling cooperative multitasking within a single thread, fibers allow us to write code that looks like it’s running synchronously, but with the ability to yield control at predefined points, such as when waiting for long-running tasks to complete. With fibers, we can simplify the control flow by running asynchronous operations within a fiber, allowing us to "pause" execution while waiting for the long-running operation to finish and then "resume" the operation once it's complete. This eliminates the need for multiple callback chains, extensive state tracking, and the potential pitfalls of stack unwinding. This commit introduces the ability to execute long-running operations in a non-blocking manner while maintaining the simplicity and readability of synchronous code. The fiber-based approach will significantly improve the handling of complex workflows, making the code easier to write and maintain. The implementation is based on ucontext.h's makecontext() (with a fallback to the venerable sigaltstack() approach on musl), sigsetjmp()/siglongjmp() and sd-event. ucontext.h provides us with alternate stacks that we can switch between. We use sigsetjmp()/siglongjmp() instead of swapcontext() because the latter forcibly saves/restores a per context signal mask every time it is called. Using sigsetjmp()/siglongjmp(), we can avoid the unnecessary syscall and maintain a per thread signal mask, which makes much more sense than having a per fiber signal mask. The default stack size is the same as a regular thread. Because we use mmap() to allocate the stack, the memory won't actually be used until it is paged in by the kernel, so we don't actually use 8MB per fiber. To integrate fibers with the event loop, each fiber is assigned a deferred event source which resumes the fiber when enabled. The deferred event source is oneshot by default so the fiber will run immediately until it yields or suspends. If it yields, the deferred event source is enabled again (oneshot) immediately. If it suspends, before it suspends, one or more event sources are registered with sd-event that will enable the deferred event source (oneshot) to resume the fiber once the operation it is waiting for completes. Yielding or suspending the fiber is done by calling sd_fiber_yield() or sd_fiber_suspend() respectively. Both of these return zero on success or any error value from the async operation that caused the fiber to resume. This is also how fiber cancellation is implemented. When a fiber is cancelled, sd_fiber_yield() and sd_fiber_suspend() will return ECANCELED when the fiber is resumed, allowing the fiber to unwind its stack (which allows cleanup to happen automatically) and finish. Instead of having applications work directly with fibers, we hide them behind a generic futures interface to represent long-running operations, regardless of whether those operations are running on a fiber or not. Aside from fibers, the futures library (sd-future) will for example allow waiting for sd-event sources and doing sd-bus calls in the background as well. Fibers can suspend until a future is ready with sd_fiber_await() or by having the future wake up the fiber explicitly in its callback. A future always defaults to waking up the current fiber. Each future kind plugs into the library by providing an sd_future_ops vtable (alloc, free, cancel, set_priority). The library treats the impl pointer returned by alloc() as a black box. Future Implementations retrieve it via sd_future_get_private(). A future starts in SD_FUTURE_PENDING and transitions exactly once to SD_FUTURE_RESOLVED, carrying an integer result. Consumers can react to that transition either by installing a one-shot callback with sd_future_set_callback() (callback-style code) or by waiting on it from a fiber via sd_fiber_await() (synchronous-looking fiber code). sd_fiber_await() is itself built on a "wait future" that resolves when its target resolves; sd_future_new_wait() exposes the same primitive directly so non-fiber callers can chain futures without involving a fiber. Cancellation is cooperative: sd_future_cancel() invokes the future impl's cancel callback, which is responsible for tearing down its work and ultimately resolving the promise with -ECANCELED. For fiber futures this is what surfaces as the ECANCELED return from sd_fiber_yield()/sd_fiber_suspend() mentioned above. Fire-and-forget fibers — created by passing a NULL ret to sd_fiber_new() — take a self-reference on their future so they outlive the caller's scope. The self-ref is dropped when the fiber resolves. This floating mechanism (sd_fiber_set_floating()) is restricted to fiber futures because they uniquely guarantee resolution; allowing it for arbitrary future kinds would risk silent leaks for kinds that may never resolve. Note that fiber cleanup depends on the runtime operating normally. Each fiber's _cleanup_-style cleanups live on the fiber's own stack and run only when the fiber is resumed and allowed to unwind, which requires a working event loop to drive it to completion. The exit event source registered for top-level fibers ensures unwind on a normal sd_event_exit(), but if the event loop itself terminates abnormally (e.g. an unrecoverable allocation failure mid-dispatch) before all fibers have resolved, their stacks never unwind and any resources they own leak. The code lives in libsystemd as sd-future (not exported) for the following reasons: - We may want to make this a public libsystemd API in the future - The code can't live in src/basic as it makes heavy use of sd-event - The code can't live in src/shared as sd-bus and sd-event make use of it The log and log-context headers are updated with functions to allow fibers to have their own log prefix and log context. --- meson.build | 12 +- src/basic/architecture.h | 6 + src/basic/basic-forward.h | 2 + src/basic/log-context.c | 8 + src/basic/log-context.h | 2 + src/basic/log.c | 6 + src/basic/log.h | 2 + src/include/override/sys/mman.h | 7 + src/libsystemd/meson.build | 15 +- src/libsystemd/sd-common/sd-forward.h | 6 + src/libsystemd/sd-event/event-future.c | 118 ++ src/libsystemd/sd-event/event-future.h | 7 + src/libsystemd/sd-future/fiber.c | 812 ++++++++++++ src/libsystemd/sd-future/sd-future.c | 263 ++++ src/libsystemd/sd-future/test-fiber.c | 1171 +++++++++++++++++ src/systemd/_sd-common.h | 14 + src/systemd/meson.build | 1 + src/systemd/sd-future.h | 101 ++ .../TEST-02-UNITTESTS/meson.build | 2 +- test/test-link-abi.py | 1 + 20 files changed, 2552 insertions(+), 4 deletions(-) create mode 100644 src/libsystemd/sd-event/event-future.c create mode 100644 src/libsystemd/sd-event/event-future.h create mode 100644 src/libsystemd/sd-future/fiber.c create mode 100644 src/libsystemd/sd-future/sd-future.c create mode 100644 src/libsystemd/sd-future/test-fiber.c create mode 100644 src/systemd/sd-future.h diff --git a/meson.build b/meson.build index 1d2d59e4fa536..56f73ea30df5f 100644 --- a/meson.build +++ b/meson.build @@ -1017,6 +1017,14 @@ else endif conf.set10('HAVE_LIBCRYPT', have) +# musl declares the ucontext.h functions but does not implement them; the fiber bootstrap in +# src/libsystemd/sd-future/fiber.c relies on them, so on musl we have to link to libucontext. +if get_option('libc') == 'musl' + libucontext = dependency('libucontext') +else + libucontext = [] +endif + bpf_framework = get_option('bpf-framework') bpf_compiler = get_option('bpf-compiler') libbpf = dependency('libbpf', @@ -1720,6 +1728,7 @@ libsystemd_includes = [basic_includes, include_directories( 'src/libsystemd/sd-common', 'src/libsystemd/sd-device', 'src/libsystemd/sd-event', + 'src/libsystemd/sd-future', 'src/libsystemd/sd-hwdb', 'src/libsystemd/sd-id128', 'src/libsystemd/sd-journal', @@ -1760,7 +1769,7 @@ libsystemd = shared_library( link_with : [libc_wrapper_static, libbasic_static], link_whole : [libsystemd_static], - dependencies : [userspace], + dependencies : [libucontext, userspace], link_depends : libsystemd_sym, install : true, install_tag: 'libsystemd', @@ -1782,6 +1791,7 @@ if static_libsystemd != 'false' dependencies : [libgcrypt_cflags, liblz4_cflags, libm, + libucontext, libxz_cflags, libzstd_cflags, userspace], diff --git a/src/basic/architecture.h b/src/basic/architecture.h index ee91ea0d349b8..350a72d087a35 100644 --- a/src/basic/architecture.h +++ b/src/basic/architecture.h @@ -242,4 +242,10 @@ Architecture uname_architecture(void); # error "Please register your architecture here!" #endif +#if defined(__hppa__) || defined(__hppa64__) +# define STACK_GROWS_UP 1 +#else +# define STACK_GROWS_UP 0 +#endif + DECLARE_STRING_TABLE_LOOKUP(architecture, Architecture); diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 5edd494f783c6..5f9109cb17c1e 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -111,10 +111,12 @@ typedef enum UnitNameMangle UnitNameMangle; typedef enum UnitType UnitType; typedef enum WaitFlags WaitFlags; +typedef struct Fiber Fiber; typedef struct Hashmap Hashmap; typedef struct HashmapBase HashmapBase; typedef struct IteratedCache IteratedCache; typedef struct Iterator Iterator; +typedef struct LogContext LogContext; typedef struct OrderedHashmap OrderedHashmap; typedef struct OrderedSet OrderedSet; typedef struct Set Set; diff --git a/src/basic/log-context.c b/src/basic/log-context.c index a05b4b1980e6b..44eb7cd36f923 100644 --- a/src/basic/log-context.c +++ b/src/basic/log-context.c @@ -177,6 +177,14 @@ size_t log_context_num_fields(void) { return _log_context_num_fields; } +void log_context_swap(LogContext **log_context, size_t *num_fields) { + assert(log_context); + assert(num_fields); + + SWAP_TWO(_log_context, *log_context); + SWAP_TWO(_log_context_num_fields, *num_fields); +} + void _reset_log_level(int *saved_log_level) { assert(saved_log_level); diff --git a/src/basic/log-context.h b/src/basic/log-context.h index ca112fa862acf..638d2ed913b19 100644 --- a/src/basic/log-context.h +++ b/src/basic/log-context.h @@ -66,6 +66,8 @@ size_t log_context_num_contexts(void); /* Returns the number of fields in all attached log contexts. */ size_t log_context_num_fields(void); +void log_context_swap(LogContext **log_context, size_t *num_fields); + void _reset_log_level(int *saved_log_level); #define _LOG_CONTEXT_SET_LOG_LEVEL(level, l) \ diff --git a/src/basic/log.c b/src/basic/log.c index d8b441bfadf21..29702abd44e40 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -87,6 +87,12 @@ bool _log_message_dummy = false; /* Always false */ } \ } while (false) +void log_prefix_swap(const char **prefix) { + assert(prefix); + + SWAP_TWO(log_prefix, *prefix); +} + static void log_close_console(void) { /* See comment in log_close_journal() */ (void) safe_close_above_stdio(TAKE_FD(console_fd)); diff --git a/src/basic/log.h b/src/basic/log.h index 46a4339de5565..cb4e8b5d0f15b 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -380,6 +380,8 @@ int log_syntax_parse_error_internal( void log_setup(void); const char* _log_set_prefix(const char *prefix, bool force); + +void log_prefix_swap(const char **prefix); static inline const char* _log_unset_prefixp(const char **p) { assert(p); _log_set_prefix(*p, true); diff --git a/src/include/override/sys/mman.h b/src/include/override/sys/mman.h index 30ef92b83538e..9961ea4b21d93 100644 --- a/src/include/override/sys/mman.h +++ b/src/include/override/sys/mman.h @@ -18,3 +18,10 @@ static_assert(MFD_NOEXEC_SEAL == 0x0008U, ""); #else static_assert(MFD_EXEC == 0x0010U, ""); #endif + +/* since Linux 6.13 / glibc-2.42 */ +#ifndef MADV_GUARD_INSTALL +# define MADV_GUARD_INSTALL 102 +#else +static_assert(MADV_GUARD_INSTALL == 102, ""); +#endif diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index c6cf0fad34b60..061ff6213f61e 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -33,6 +33,7 @@ sd_daemon_sources = files('sd-daemon/sd-daemon.c') ############################################################ sd_event_sources = files( + 'sd-event/event-future.c', 'sd-event/event-util.c', 'sd-event/sd-event.c', ) @@ -75,6 +76,13 @@ sd_device_sources = files( ############################################################ +sd_future_sources = files( + 'sd-future/fiber.c', + 'sd-future/sd-future.c', +) + +############################################################ + sd_login_sources = files('sd-login/sd-login.c') ############################################################ @@ -135,8 +143,9 @@ libsystemd_sources = files( 'sd-resolve/sd-resolve.c', ) + sd_journal_sources + sd_id128_sources + sd_daemon_sources \ + sd_event_sources + sd_bus_sources + sd_device_sources \ - + sd_login_sources + sd_json_sources + sd_varlink_sources \ - + sd_path_sources + sd_netlink_sources + sd_network_sources + + sd_future_sources + sd_login_sources + sd_json_sources \ + + sd_varlink_sources + sd_path_sources + sd_netlink_sources \ + + sd_network_sources sources += libsystemd_sources @@ -151,6 +160,7 @@ libsystemd_static = static_library( link_with : [libc_wrapper_static, libbasic_static], dependencies : [libm, + libucontext, userspace], build_by_default : false) @@ -179,6 +189,7 @@ simple_tests += files( 'sd-bus/test-bus-vtable.c', 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', + 'sd-future/test-fiber.c', 'sd-hwdb/test-sd-hwdb.c', 'sd-id128/test-id128.c', 'sd-journal/test-audit-type.c', diff --git a/src/libsystemd/sd-common/sd-forward.h b/src/libsystemd/sd-common/sd-forward.h index 8abe655209dec..96ab84e982886 100644 --- a/src/libsystemd/sd-common/sd-forward.h +++ b/src/libsystemd/sd-common/sd-forward.h @@ -127,3 +127,9 @@ typedef struct sd_resolve sd_resolve; typedef struct sd_resolve_query sd_resolve_query; typedef struct sd_hwdb sd_hwdb; + +typedef struct sd_future sd_future; + +typedef int (*sd_future_func_t)(sd_future *f); +typedef int (*sd_fiber_func_t)(void *userdata); +typedef _sd_destroy_t sd_fiber_destroy_t; diff --git a/src/libsystemd/sd-event/event-future.c b/src/libsystemd/sd-event/event-future.c new file mode 100644 index 0000000000000..4595a7a6fb0b5 --- /dev/null +++ b/src/libsystemd/sd-event/event-future.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "event-future.h" + +typedef struct TimeFuture { + sd_event_source *source; + + /* Result the future resolves with on natural expiry (vs. cancellation). 0 for normal sleep, + * non-zero (e.g. -ETIMEDOUT) lets a fiber waiting on this future resume with that error. */ + int result; +} TimeFuture; + +static void* time_future_alloc(void) { + return new0(TimeFuture, 1); +} + +static void time_future_free(sd_future *f) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_event_source_unref(tf->source); + free(tf); +} + +static int time_future_cancel(sd_future *f) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + r = sd_event_source_set_enabled(tf->source, SD_EVENT_OFF); + RET_GATHER(r, sd_future_resolve(f, -ECANCELED)); + return r; +} + +static int time_future_set_priority(sd_future *f, int64_t priority) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + return sd_event_source_set_priority(tf->source, priority); +} + +static const sd_future_ops time_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = time_future_alloc, + .free = time_future_free, + .cancel = time_future_cancel, + .set_priority = time_future_set_priority, +}; + +static int time_handler(sd_event_source *s, usec_t usec, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(f)); + + return sd_future_resolve(f, tf->result); +} + +typedef int (*event_add_time_func)( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata); + +static int future_new_time_internal( + event_add_time_func add_time, + sd_event *e, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + int result, + sd_future **ret) { + + int r; + + assert(add_time); + assert(e); + assert(ret); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&time_future_ops, &f); + if (r < 0) + return r; + + TimeFuture *tf = sd_future_get_private(f); + tf->result = result; + + r = add_time(e, &tf->source, clock, usec, accuracy, time_handler, f); + if (r < 0) + return r; + + if (sd_fiber_is_running()) { + int64_t priority; + + r = sd_fiber_get_priority(&priority); + if (r < 0) + return r; + + r = sd_event_source_set_priority(tf->source, priority); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(f); + return 0; +} + +int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret) { + return future_new_time_internal(sd_event_add_time, e, clock, usec, accuracy, result, ret); +} + +int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret) { + return future_new_time_internal(sd_event_add_time_relative, e, clock, usec, accuracy, result, ret); +} diff --git a/src/libsystemd/sd-event/event-future.h b/src/libsystemd/sd-event/event-future.h new file mode 100644 index 0000000000000..7e956906ebf74 --- /dev/null +++ b/src/libsystemd/sd-event/event-future.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); +int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); diff --git a/src/libsystemd/sd-future/fiber.c b/src/libsystemd/sd-future/fiber.c new file mode 100644 index 0000000000000..48d26a27dd2c7 --- /dev/null +++ b/src/libsystemd/sd-future/fiber.c @@ -0,0 +1,812 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "errno-util.h" +#include "event-future.h" +#include "log-context.h" +#include "log.h" +#include "memory-util.h" +#include "pthread-util.h" +#include "time-util.h" + +#if HAS_FEATURE_ADDRESS_SANITIZER +#include +#endif + +/* glibc's _FORTIFY_SOURCE wraps siglongjmp() with __longjmp_chk, which asserts that the target SP is below + * the current SP. That assumption is incompatible with fiber switching, where the target SP lives on a + * separately-mmap'd stack and can be at any address relative to the caller. The fortify redirect happens + * in 's declaration of siglongjmp; we sidestep it by declaring our own alias that links + * directly to the unchecked "siglongjmp" symbol. musl doesn't fortify setjmp.h, so the alias is a plain + * synonym there. */ +_noreturn_ extern void siglongjmp_unchecked(sigjmp_buf env, int val) __asm__("siglongjmp"); + +static thread_local Fiber *current_fiber = NULL; + +typedef enum FiberState { + FIBER_STATE_INITIAL, + FIBER_STATE_READY, + FIBER_STATE_SUSPENDED, + FIBER_STATE_CANCELLED, + FIBER_STATE_COMPLETED, + _FIBER_STATE_MAX, + _FIBER_STATE_INVALID = -EINVAL, +} FiberState; + +typedef struct Fiber { + struct iovec stack; + sigjmp_buf context; /* Where to jump to when entering or resuming the fiber. */ + sigjmp_buf resume_context; /* Where to jump back to when the fiber yields or completes. */ + + /* Caller's stack range, recorded by fiber_run() on each entry so the fiber's siglongjmp back + * out (in fiber_swap() or the trampoline's terminate path) can hand AddressSanitizer the + * destination stack info. With ucontext this comes for free via uc_link/uc_stack; sigjmp_buf + * is opaque and doesn't carry it. */ + struct iovec resume_stack; + + FiberState state; + int result; /* Either resume error code or final return value */ + + sd_future *floating; /* Self-ref held while the fiber is floating; dropped on resolve. */ + + sd_event *event; + sd_event_source *defer_event_source; + sd_event_source *exit_event_source; + + char *name; + int64_t priority; + sd_fiber_func_t func; + void *userdata; + sd_fiber_destroy_t destroy; + + /* Storage for the swap performed in fiber_run(): while the fiber is suspended these hold the + * fiber's own log state; while it is running they hold the caller's log state. The active state + * always lives in the thread-locals in log.c / log-context.c. */ + LIST_HEAD(LogContext, log_context); + size_t log_context_num_fields; + const char *log_prefix; + +#if HAVE_VALGRIND_VALGRIND_H + unsigned stack_id; +#endif +} Fiber; + +static Fiber* fiber_get_current(void) { + return current_fiber; +} + +static void fiber_set_current(Fiber *f) { + current_fiber = f; +} + +static int fiber_allocate_stack(size_t size, void **ret) { + void *stack = NULL; + int r; + + /* The effective stack size is one page less than the given size, because we have to use + * one page as the guard page for the stack. */ + + assert(size > 0 && size % page_size() == 0); + assert(ret); + + stack = mmap(/* addr= */ NULL, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, + /* fd= */ -EBADF, /* offset= */ 0); + if (stack == MAP_FAILED) + return -errno; + + /* Place the guard page where stack overflow will hit it: the high end on architectures + * where the stack grows up (PA-RISC), the low end everywhere else. fiber_stack_usable() + * mirrors this with the inverse offset. */ + void *guard = STACK_GROWS_UP ? (uint8_t*) stack + size - page_size() : stack; + + /* Prefer MADV_GUARD_INSTALL (Linux 6.13+): unlike mprotect(PROT_NONE) it doesn't split + * the VMA, so guard installation skips the mmap-lock contention and per-guard VMA cost. + * Fall back to mprotect on older kernels, which return EINVAL for unknown advice. + * FIXME: delete when baseline above 6.13. */ + r = RET_NERRNO(madvise(guard, page_size(), MADV_GUARD_INSTALL)); + if (r == -EINVAL) + r = RET_NERRNO(mprotect(guard, page_size(), PROT_NONE)); + if (r < 0) { + (void) munmap(stack, size); + return r; + } + + *ret = TAKE_PTR(stack); + return 0; +} + +/* Usable stack range of a fiber: the full mmap region minus the guard page. Single source of + * truth for the layout assumed by fiber_allocate_stack(); every consumer (ucontext ss_sp, + * ASAN handoff iovecs, Valgrind stack registration) goes through here. + * + * iov_base is the lowest usable byte regardless of growth direction — that matches POSIX's + * definition of stack_t.ss_sp, so libc's makecontext() handles the direction for us. Only the + * guard page placement (and hence iov_base's offset within the mapping) varies. */ +static struct iovec fiber_stack_usable(const struct iovec *stack) { + assert(stack); + assert(stack->iov_len > page_size()); + return (struct iovec) { + .iov_base = STACK_GROWS_UP ? stack->iov_base : (uint8_t*) stack->iov_base + page_size(), + .iov_len = stack->iov_len - page_size(), + }; +} + +static inline void start_switch_stack(void **fake_stack_save, const struct iovec *dest) { +#if HAS_FEATURE_ADDRESS_SANITIZER + __sanitizer_start_switch_fiber(fake_stack_save, + dest ? dest->iov_base : NULL, + dest ? dest->iov_len : 0); +#endif +} + +static inline void finish_switch_stack(void *fake_stack_save) { +#if HAS_FEATURE_ADDRESS_SANITIZER + __sanitizer_finish_switch_fiber(fake_stack_save, NULL, NULL); +#endif +} + +/* Refresh f->resume_stack from whoever is currently the running fiber, so the next siglongjmp() out + * of f (in the trampoline or fiber_swap()) can hand the right destination stack to ASAN. Must be + * called before fiber_set_current(f) — relies on fiber_get_current() returning the caller. */ +static void fiber_set_resume_stack(Fiber *f, Fiber *resume) { + assert(f); + + if (resume) + f->resume_stack = fiber_stack_usable(&resume->stack); + else + f->resume_stack = (struct iovec) {}; +} + +_noreturn_ static void fiber_entry_point(void) { + Fiber *f = ASSERT_PTR(fiber_get_current()); + void *fake_stack_save = NULL; + + assert(f->func); + assert(IN_SET(f->state, FIBER_STATE_INITIAL, FIBER_STATE_READY, FIBER_STATE_CANCELLED)); + + finish_switch_stack(NULL); + + /* Capture our resumable point on the fiber's stack, then bounce back to whoever last set + * f->resume_context. On bootstrap that's fiber_bootstrap(); on every subsequent yield it's + * the most recent fiber_run(). sigsetjmp(buf, 0) skips the signal-mask save: switching is + * thread-shared with respect to signal masks. */ + if (sigsetjmp(f->context, /* savemask= */ 0) == 0) { + start_switch_stack(&fake_stack_save, &f->resume_stack); + siglongjmp_unchecked(f->resume_context, /* val= */ 1); + } + + /* Re-entered for real via fiber_run()'s siglongjmp(f->context). */ + finish_switch_stack(fake_stack_save); + + /* Block scope so the cleanups attached to LOG_SET_PREFIX / LOG_CONTEXT_PUSH_KEY_VALUE fire + * before the siglongjmp below — siglongjmp skips _cleanup_ attributes, so we have to make + * sure the scope ends via a normal control-flow path first. */ + { + LOG_SET_PREFIX(f->name); + LOG_CONTEXT_PUSH_KEY_VALUE("FIBER=", f->name); + + f->result = f->state == FIBER_STATE_CANCELLED ? -ECANCELED : f->func(f->userdata); + f->state = FIBER_STATE_COMPLETED; + } + + /* Pass NULL fake_stack_save to discard the fiber's fake stack since the fiber is done. */ + start_switch_stack(NULL, &f->resume_stack); + + /* Bounce back to whichever fiber_run() call most recently entered us. resume_context is + * per-fiber so nested fiber_run() — e.g. a bus method dispatched as a fiber handler while + * sd_event_loop() itself runs in a fiber — is safe. */ + siglongjmp_unchecked(f->resume_context, 1); + assert_not_reached(); +} + +static int fiber_init(Fiber *f) { + ucontext_t old_uc, uc; + void *fake_stack_save = NULL; + + assert(f); + + if (getcontext(&uc) < 0) + return -errno; + + struct iovec fiber_stack = fiber_stack_usable(&f->stack); + + uc.uc_link = NULL; /* Unused: trampoline siglongjmps out instead of returning. */ + uc.uc_stack.ss_sp = fiber_stack.iov_base; + uc.uc_stack.ss_size = fiber_stack.iov_len; + uc.uc_stack.ss_flags = 0; + + Fiber *prev = fiber_get_current(); + fiber_set_current(f); + + makecontext(&uc, fiber_entry_point, /* argc= */ 0); + + fiber_set_resume_stack(f, prev); + if (sigsetjmp(f->resume_context, /* savemask= */ 0) == 0) { + start_switch_stack(&fake_stack_save, &fiber_stack); + if (swapcontext(&old_uc, &uc) < 0) { + finish_switch_stack(fake_stack_save); + fiber_set_current(prev); + return -errno; + } + assert_not_reached(); /* Trampoline siglongjmps back; swapcontext doesn't return. */ + } + + finish_switch_stack(fake_stack_save); + + fiber_set_current(prev); + return 0; +} + +/* Swap the thread-local log prefix and log context with the values stashed in f. While the fiber is + * suspended, f holds the fiber's own log state; while it's running, f holds the caller's log state. The + * swap is its own inverse, so the same call drives both directions. */ +static void fiber_swap_log_state(Fiber *f) { + assert(f); + log_prefix_swap(&f->log_prefix); + log_context_swap(&f->log_context, &f->log_context_num_fields); +} + +static void reset_current_fiber(void) { + /* Restore the caller's log state stashed in the running fiber (if any) before clearing + * current_fiber. Without this, the child of a fork() that happened mid-fiber would inherit the + * fiber's log prefix / context list in its thread-locals even though no fiber is running. */ + Fiber *f = fiber_get_current(); + if (f) + fiber_swap_log_state(f); + fiber_set_current(NULL); +} + +static sd_event_source* fiber_current_event_source(Fiber *f) { + assert(f); + assert(f->state != FIBER_STATE_COMPLETED); + assert(f->event); + + return sd_event_get_state(f->event) == SD_EVENT_EXITING ? f->exit_event_source : f->defer_event_source; +} + +static int atfork_ret; + +static void install_atfork(void) { + /* __register_atfork() either returns 0 or -ENOMEM, in its glibc implementation. Since it's + * only half-documented (glibc doesn't document it but LSB does — though only superficially) + * we'll check for errors only in the most generic fashion possible. */ + atfork_ret = pthread_atfork(/* prepare= */ NULL, /* parent= */ NULL, reset_current_fiber); + if (atfork_ret != 0) + log_debug_errno(atfork_ret, "pthread_atfork() failed: %m"); +} + +static void fiber_resolve(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + fiber->defer_event_source = sd_event_source_disable_unref(fiber->defer_event_source); + fiber->exit_event_source = sd_event_source_disable_unref(fiber->exit_event_source); + /* The floating self-ref (if any) is potentially the last ref keeping the fiber alive — moving it + * into a local _cleanup_ slot ensures sd_future_resolve() runs callbacks and waiters while f is + * still valid; the local's cleanup drops the ref afterwards, at which point no further f->... + * access can happen. */ + _unused_ _cleanup_(sd_future_unrefp) sd_future *floating = TAKE_PTR(fiber->floating); + sd_future_resolve(f, fiber->result); +} + +static void fiber_enter(Fiber *fiber, Fiber *prev, void **fake_stack_save) { + fiber_set_current(fiber); + fiber_swap_log_state(fiber); + + struct iovec fiber_stack = fiber_stack_usable(&fiber->stack); + start_switch_stack(fake_stack_save, &fiber_stack); + fiber_set_resume_stack(fiber, prev); +} + +static void fiber_leave(Fiber *fiber, Fiber *prev, void *fake_stack_save) { + finish_switch_stack(fake_stack_save); + fiber_swap_log_state(fiber); + fiber_set_current(prev); +} + +static int fiber_run(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + if (fiber->state == FIBER_STATE_COMPLETED) + return -ESTALE; + + assert(IN_SET(fiber->state, FIBER_STATE_INITIAL, FIBER_STATE_READY, FIBER_STATE_CANCELLED)); + + static pthread_once_t atfork_once = PTHREAD_ONCE_INIT; + r = pthread_once(&atfork_once, install_atfork); + if (r != 0) + return -r; + if (atfork_ret != 0) + return -atfork_ret; + + LOG_SET_PREFIX(fiber->name); + LOG_CONTEXT_PUSH_KEY_VALUE("FIBER=", fiber->name); + + log_debug("Scheduling fiber"); + + /* Save the previously-current fiber (if any) so we can restore it when this fiber yields or + * completes. This matters when fiber_run() is invoked from within another fiber (e.g. an + * sd-event dispatch that happens to be running inside a fiber context itself): the + * LOG_SET_PREFIX/LOG_CONTEXT_PUSH above attached to whichever fiber was current at that moment, + * and their scope-level cleanup must see the same fiber_get_current() when it runs to detach + * them from the correct list. */ + Fiber *prev = fiber_get_current(); + void *fake_stack_save = NULL; + fiber_enter(fiber, prev, &fake_stack_save); + + /* This is where we start executing the fiber. Once it yields, we continue here as if nothing + * happened. resume_context captures this point; the fiber siglongjmps back to it. */ + if (sigsetjmp(fiber->resume_context, 0) == 0) + siglongjmp_unchecked(fiber->context, 1); + + fiber_leave(fiber, prev, fake_stack_save); + + switch (fiber->state) { + + case FIBER_STATE_COMPLETED: + if (fiber->result < 0 && fiber->result != -ECANCELED) + log_debug_errno(fiber->result, "Fiber failed with error: %m"); + else + log_debug("Fiber finished executing"); + + fiber_resolve(f); + break; + + case FIBER_STATE_CANCELLED: + case FIBER_STATE_READY: + log_debug("Fiber yielded execution"); + + r = sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); + if (r < 0) + return r; + break; + + case FIBER_STATE_SUSPENDED: + log_debug("Fiber suspended execution"); + /* Fiber is waiting for something - don't re-queue it */ + break; + + default: + assert_not_reached(); + } + + return 0; +} + +static int fiber_cancel(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + assert(fiber != fiber_get_current()); + + if (IN_SET(fiber->state, FIBER_STATE_COMPLETED, FIBER_STATE_CANCELLED)) + return 0; + + if (fiber->state == FIBER_STATE_INITIAL) { + /* The fiber's stack was allocated but never entered, so there are no scope-level cleanups + * waiting to run. Skip the dispatch round-trip that would just have fiber_entry_point() + * fall straight through with -ECANCELED, and settle the future right here — mirroring the + * FIBER_STATE_COMPLETED branch of fiber_run(). */ + fiber->result = -ECANCELED; + fiber->state = FIBER_STATE_COMPLETED; + fiber_resolve(f); + return 1; + } + + /* Once we cancel a fiber, we want to immediately resume it with -ECANCELED. */ + r = sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); + if (r < 0) + return r; + + fiber->state = FIBER_STATE_CANCELLED; + + return 1; +} + +static int fiber_on_defer(sd_event_source *s, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + return fiber_run(f); +} + +static int fiber_on_exit(sd_event_source *s, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + int r; + + /* The fiber may already have completed via the regular defer path before sd_event_exit() + * fires the exit source; in that case there's nothing left to drive and we'd otherwise + * trip fiber_run()'s -ESTALE return, which sd_event would log spuriously and disable the + * source for. */ + if (fiber->state == FIBER_STATE_COMPLETED) + return 0; + + /* If fiber_cancel() returned 1 the fiber was just marked cancelled and its deferred/exit event + * source was re-armed; we let the event loop dispatch that source on the next iteration so it goes + * through the normal fiber_on_defer/fiber_on_exit path rather than running it recursively here. */ + r = fiber_cancel(f); + if (r != 0) + return r; + + return fiber_run(f); +} + +static void* fiber_alloc(void) { + return new0(Fiber, 1); +} + +static void fiber_free(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + /* To make sure all memory is deallocated, the fiber has to have completed by the time we free it to + * make sure its stack has finished unwinding (which will invoke the registered cleanup functions). + * As this function may get called when not running on a fiber ourselves, we can't guarantee here + * that we can run the fiber to completion ourselves, so we insist that this happens before we get + * here. To ensure fibers are cleaned up before exiting the event loop, exit handlers are added for + * fibers created outside of existing fibers. For fibers created within running fibers, unwinding the + * outer fiber should take care of cleaning up any created child fibers (for example using + * sd_future_cancel_wait_unref()). + * + * FIBER_STATE_INITIAL is also accepted: the stack was allocated but never entered, so there are no + * registered cleanups to run. This covers the partial-construction failure path in sd_fiber_new() + * as well as fibers that are unrefed before the event loop ever dispatches them. */ + assert(IN_SET(fiber->state, FIBER_STATE_INITIAL, FIBER_STATE_COMPLETED)); + + if (fiber->destroy) + fiber->destroy(fiber->userdata); + +#if HAVE_VALGRIND_VALGRIND_H + if (fiber->stack.iov_base) + VALGRIND_STACK_DEREGISTER(fiber->stack_id); +#endif + + if (fiber->stack.iov_base) + (void) munmap(fiber->stack.iov_base, fiber->stack.iov_len); + + sd_event_source_disable_unref(fiber->defer_event_source); + sd_event_source_disable_unref(fiber->exit_event_source); + sd_event_unref(fiber->event); + + free(fiber->name); + free(fiber); +} + +sd_future* sd_fiber_get_current(void) { + Fiber *f = fiber_get_current(); + if (!f) + return NULL; + + return sd_event_source_get_userdata(fiber_current_event_source(f)); +} + +int sd_fiber_is_running(void) { + return !!fiber_get_current(); +} + +sd_event* sd_fiber_get_event(void) { + Fiber *f = fiber_get_current(); + assert_return(f, NULL); + return f->event; +} + +int sd_fiber_get_priority(int64_t *ret) { + Fiber *f = fiber_get_current(); + + assert_return(ret, -EINVAL); + assert_return(f, -ESRCH); + + *ret = f->priority; + return 0; +} + +static int fiber_swap(FiberState state) { + Fiber *f = ASSERT_PTR(fiber_get_current()); + + f->state = state; + + void *fake_stack_save = NULL; + + if (sigsetjmp(f->context, 0) == 0) { + start_switch_stack(&fake_stack_save, &f->resume_stack); + siglongjmp_unchecked(f->resume_context, 1); + } + + finish_switch_stack(fake_stack_save); + + /* When we get here, we've been resumed. */ + + if (f->state == FIBER_STATE_CANCELLED) + return -ECANCELED; + + /* sd_fiber_resume() stashes the resumer's value (an async wakeup error from a deadline + * timer, an io_uring CQE result, etc.) into f->result for us to surface here. Consume it + * unconditionally so it doesn't pollute subsequent suspends or the fiber's eventual return + * value — both negative errors and positive payloads (byte counts, accepted fds, revents + * masks) are valid resume values. */ + return TAKE_GENERIC(f->result, int, 0); +} + +int sd_fiber_yield(void) { + assert_return(fiber_get_current(), -ESRCH); + return fiber_swap(FIBER_STATE_READY); +} + +int sd_fiber_suspend(void) { + assert_return(fiber_get_current(), -ESRCH); + return fiber_swap(FIBER_STATE_SUSPENDED); +} + +static int fiber_set_priority(sd_future *f, int64_t priority) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r = 0; + + if (fiber->defer_event_source) + RET_GATHER(r, sd_event_source_set_priority(fiber->defer_event_source, priority)); + + if (fiber->exit_event_source) + RET_GATHER(r, sd_event_source_set_priority(fiber->exit_event_source, priority)); + + if (r >= 0) + fiber->priority = priority; + + return r; +} + +static const sd_future_ops fiber_future_ops; + +int sd_fiber_resume(sd_future *f, int result) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (fiber->state != FIBER_STATE_SUSPENDED) + return 0; + + /* Stash the result so fiber_swap() returns it from sd_fiber_suspend(). */ + fiber->result = result; + fiber->state = FIBER_STATE_READY; + return sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); +} + +/* The fiber_future ops pass the Fiber pointer through as the future's private state. The fiber resolves + * its own future once it finishes running, so fiber_cancel() intentionally does not resolve. */ +static const sd_future_ops fiber_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = fiber_alloc, + .free = fiber_free, + .cancel = fiber_cancel, + .set_priority = fiber_set_priority, +}; + +int sd_fiber_new(sd_event *e, const char *name, sd_fiber_func_t func, void *userdata, sd_fiber_destroy_t destroy, sd_future **ret) { + int r; + + assert_return(e, -EINVAL); + assert_return(name, -EINVAL); + assert_return(func, -EINVAL); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&fiber_future_ops, &f); + if (r < 0) + return r; + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY }; + if (getrlimit(RLIMIT_STACK, &rl) < 0) + log_debug_errno(errno, "Reading RLIMIT_STACK failed, ignoring: %m"); + if (rl.rlim_cur == RLIM_INFINITY) + rl.rlim_cur = 8U * U64_MB; /* Same as the default thread stack size */ + + /* Reserve room for the guard page so the usable region stays above PTHREAD_STACK_MIN, which + * is what libc/pthread routines (e.g. TLS setup on musl) assume. */ + size_t stack_len = ROUND_UP(rl.rlim_cur, page_size()); + if (stack_len < (size_t) PTHREAD_STACK_MIN + page_size()) + stack_len = ROUND_UP((size_t) PTHREAD_STACK_MIN + page_size(), page_size()); + + *fiber = (Fiber) { + .stack.iov_len = stack_len, + .state = FIBER_STATE_INITIAL, + .name = strdup(name), + .func = func, + .userdata = userdata, + .event = sd_event_ref(e), + }; + if (!fiber->name) + return -ENOMEM; + + r = fiber_allocate_stack(fiber->stack.iov_len, &fiber->stack.iov_base); + if (r < 0) + return r; + +#if HAVE_VALGRIND_VALGRIND_H + /* Register the usable stack range (above the guard page) before fiber_bootstrap() so the + * trampoline's first sigsetjmp doesn't trip Valgrind's stack-tracking heuristics. */ + struct iovec usable = fiber_stack_usable(&fiber->stack); + fiber->stack_id = VALGRIND_STACK_REGISTER( + usable.iov_base, + (uint8_t*) usable.iov_base + usable.iov_len); +#endif + + r = fiber_init(fiber); + if (r < 0) + return r; + + /* Execution of the fiber is driven by two event sources, one deferred, one exit. The exit event + * source kicks in when sd_event_exit() is called, as from that point onwards only exit event + * sources will be dispatched. */ + + r = sd_event_add_defer(e, &fiber->defer_event_source, fiber_on_defer, f); + if (r < 0) + return r; + + r = sd_event_source_set_description(fiber->defer_event_source, fiber->name); + if (r < 0) + return r; + + r = sd_event_add_exit(e, &fiber->exit_event_source, fiber_on_exit, f); + if (r < 0) + return r; + + r = sd_event_source_set_description(fiber->exit_event_source, fiber->name); + if (r < 0) + return r; + + /* If we're on a fiber, we'll rely on the parent fiber to cancel this fiber if the event loop is + * exiting. Otherwise, we'll trigger cancellation of this fiber via the exit event source. Why cancel + * via the exit event source? We can only run the fiber while the event loop is active, so we need to + * make sure all fibers finish running before the event loop is finished, which an exit event source + * allows us to do. */ + r = sd_event_source_set_enabled(fiber->exit_event_source, sd_fiber_is_running() ? SD_EVENT_OFF : SD_EVENT_ONESHOT); + if (r < 0) + return r; + + /* Stays in FIBER_STATE_INITIAL until the event loop first dispatches it via fiber_run(). */ + + if (ret) + *ret = TAKE_PTR(f); + else { + /* Fire-and-forget: the fiber is guaranteed to resolve (via completion, cancellation, or + * the event loop exit handler), so making the future floating cleans it up. */ + r = sd_fiber_set_floating(f, true); + if (r < 0) + return r; + } + + /* We only take ownership of the given userdata pointer on success so assign the destroy callback + * at the very end so we don't clean up the userdata pointer on failure. */ + fiber->destroy = destroy; + + return 0; +} + +int sd_fiber_set_floating(sd_future *f, int b) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (!!fiber->floating == !!b) + return 0; + + /* The floating self-ref keeps the future alive until the fiber resolves; fiber_run() drops it + * in the COMPLETED branch. Only valid for fiber futures because fibers uniquely guarantee + * resolution (via completion, cancellation, or the event loop exit handler). */ + if (b) + fiber->floating = sd_future_ref(f); + else + fiber->floating = sd_future_unref(fiber->floating); + + return 0; +} + +int sd_fiber_get_floating(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + return !!fiber->floating; +} + +int sd_fiber_sleep(uint64_t usec) { + Fiber *f = fiber_get_current(); + int r; + + if (!f) + return usleep_safe(usec); + + if (usec == 0) + return sd_fiber_yield(); + + /* Match usleep_safe(USEC_INFINITY): suspend indefinitely. Passing USEC_INFINITY to + * sd_event_add_time_relative() would overflow into -EOVERFLOW. */ + if (usec == USEC_INFINITY) + return sd_fiber_suspend(); + + assert(f->event); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + r = future_new_time_relative( + f->event, + CLOCK_MONOTONIC, + usec, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + + return sd_fiber_suspend(); +} + +int sd_fiber_await(sd_future *target) { + sd_future *f = sd_fiber_get_current(); + int r; + + assert_return(f, -ESRCH); + assert_return(target, -EINVAL); + assert_return(target != f, -EDEADLK); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (sd_future_state(target) == SD_FUTURE_RESOLVED) + return sd_future_result(target); + + /* Note that we do allow waiting for other fibers when the event loop is exiting, since waiting for + * other fibers does not require adding new event sources to the event loop. */ + if (sd_event_get_state(fiber->event) == SD_EVENT_FINISHED) + return -ECANCELED; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *wait = NULL; + r = sd_future_new_wait(target, &wait); + if (r < 0) + return r; + + return sd_fiber_suspend(); +} + +sd_future* sd_fiber_timeout(uint64_t timeout) { + Fiber *fiber = fiber_get_current(); + int r; + + assert_return(fiber, NULL); + + if (timeout == USEC_INFINITY) + return NULL; + + sd_future *timer; + r = future_new_time_relative( + fiber->event, + CLOCK_MONOTONIC, + timeout, + /* accuracy= */ 1, + /* result= */ -ETIME, + &timer); + if (r < 0) + return NULL; /* On allocation failure no timer is armed and the scope becomes a no-op. + * Errors here are rare; if the caller cares they can compare to NULL. */ + + return timer; +} diff --git a/src/libsystemd/sd-future/sd-future.c b/src/libsystemd/sd-future/sd-future.c new file mode 100644 index 0000000000000..ba3a1c52d2fa6 --- /dev/null +++ b/src/libsystemd/sd-future/sd-future.c @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "macro.h" +#include "set.h" + +struct sd_future { + unsigned n_ref; + + int state; + int result; + + Set *waiters; + + sd_future_func_t callback; + void *userdata; + + const sd_future_ops *ops; + + /* Opaque per-future state owned by the future implementation (the code that called + * sd_future_new()). The ops vtable above receives this pointer in its callbacks, and + * external code can fetch it via sd_future_get_private(). */ + void *private; +}; + +static int fiber_resume_trampoline(sd_future *f) { + /* The future's result is what the fiber should resume with. Impls choose the value at + * resolution time — e.g. a deadline timer resolves with -ETIME, a wait future resolves + * with the target's result, a normal IO/sleep future resolves with 0 on success. */ + return sd_fiber_resume(sd_future_get_userdata(f), sd_future_result(f)); +} + +int sd_future_resolve(sd_future *f, int result) { + int r = 0; + + assert_return(f, -EINVAL); + + if (f->state != SD_FUTURE_PENDING) + return 0; + + /* Hold a self-ref across callback/waiter dispatch: callbacks (e.g. bus_fiber_resolved() + * dropping the tracking-set's ref) may legitimately release what would otherwise be the + * last reference, and we still access f->waiters below. The cleanup unrefs at scope exit, + * which is when freeing is safe again. */ + _unused_ _cleanup_(sd_future_unrefp) sd_future *self = sd_future_ref(f); + + f->state = SD_FUTURE_RESOLVED; + f->result = result; + + if (f->callback) + RET_GATHER(r, f->callback(f)); + + /* We'd like the set to not be modified while iterating over it, hence take ownership over it in + * a local variable. Otherwise code invoked via sd_future_resolve() could try to modify the set while + * we're iterating over it (for example wait_future_free()). */ + Set *waiters = TAKE_PTR(f->waiters); + sd_future *w; + SET_FOREACH(w, waiters) + RET_GATHER(r, sd_future_resolve(w, result)); + + set_free(waiters); + + return r; +} + +static sd_future* sd_future_free(sd_future *f) { + if (!f) + return NULL; + + if (f->state == SD_FUTURE_PENDING) + sd_future_resolve(f, -ECANCELED); + + set_free(f->waiters); + + if (f->ops->free) + f->ops->free(f); + + return mfree(f); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_future, sd_future, sd_future_free); +DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_future*, sd_future_unref); +DEFINE_POINTER_ARRAY_FREE_FUNC(sd_future*, sd_future_unref); + +sd_future* sd_future_cancel_wait_unref(sd_future *f) { + int r; + + if (!f) + return NULL; + + /* We have to be able to suspend until the fiber we're waiting for finishes, and that's only + * possible if we're running on a fiber ourselves. */ + if (!sd_fiber_is_running()) + return sd_future_unref(f); + + r = sd_future_cancel(f); + if (r < 0) + log_debug_errno(r, "Failed to cancel future, ignoring: %m"); + + if (f->state == SD_FUTURE_PENDING) { + /* Fast path: when f's resolve callback already targets the current fiber (the default for + * futures created on this fiber), we can suspend directly and let the existing trampoline + * wake us up — no need to allocate a wait future just to learn about the resolution. + * Otherwise fall back to sd_fiber_await() which sets up an explicit waiter. */ + if (f->callback == fiber_resume_trampoline && f->userdata == sd_fiber_get_current()) + r = sd_fiber_suspend(); + else + r = sd_fiber_await(f); + if (r < 0 && r != -ECANCELED) + log_debug_errno(r, "Failed to wait for future to finish, ignoring: %m"); + } + + return sd_future_unref(f); +} + +DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_future*, sd_future_cancel_wait_unref); +DEFINE_POINTER_ARRAY_FREE_FUNC(sd_future*, sd_future_cancel_wait_unref); + +int sd_future_new(const sd_future_ops *ops, sd_future **ret) { + assert_return(ops, -EINVAL); + assert_return(ops->size >= endoffsetof_field(sd_future_ops, set_priority), -EINVAL); + assert_return(ops->alloc, -EINVAL); + assert_return(ops->free, -EINVAL); + assert_return(ret, -EINVAL); + + sd_future *f = new(sd_future, 1); + if (!f) + return -ENOMEM; + + *f = (sd_future) { + .n_ref = 1, + .state = SD_FUTURE_PENDING, + .ops = ops, + }; + + f->private = ops->alloc(); + if (!f->private) { + free(f); + return -ENOMEM; + } + + /* If we're being created on a fiber, default the callback to resuming that fiber on resolve — + * this is almost always what you want, and it saves the usual set_callback boilerplate before + * sd_fiber_suspend(). Callers that want different behavior can override with + * sd_future_set_callback(). */ + sd_future *fiber = sd_fiber_get_current(); + if (fiber) + (void) sd_future_set_callback(f, fiber_resume_trampoline, fiber); + + *ret = f; + return 0; +} + +int sd_future_state(sd_future *f) { + assert_return(f, -EINVAL); + return f->state; +} + +int sd_future_result(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(f->state == SD_FUTURE_RESOLVED, -EBUSY); + return f->result; +} + +void* sd_future_get_userdata(sd_future *f) { + assert_return(f, NULL); + return f->userdata; +} + +void* sd_future_get_private(sd_future *f) { + assert_return(f, NULL); + return f->private; +} + +const sd_future_ops* sd_future_get_ops(sd_future *f) { + assert_return(f, NULL); + return f->ops; +} + +int sd_future_set_callback(sd_future *f, sd_future_func_t callback, void *userdata) { + assert_return(f, -EINVAL); + + f->callback = callback; + f->userdata = userdata; + return 0; +} + +int sd_future_set_priority(sd_future *f, int64_t priority) { + assert_return(f, -EINVAL); + assert_return(f->state == SD_FUTURE_PENDING, -ESTALE); + assert_return(f->ops->set_priority, -EOPNOTSUPP); + + return f->ops->set_priority(f, priority); +} + +int sd_future_cancel(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(f->ops->cancel, -EOPNOTSUPP); + + if (f->state == SD_FUTURE_RESOLVED) + return 0; + + return f->ops->cancel(f); +} + +typedef struct WaitFuture { + sd_future *target; +} WaitFuture; + +static void* wait_future_alloc(void) { + return new0(WaitFuture, 1); +} + +static void wait_future_free(sd_future *f) { + WaitFuture *wf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + set_remove(wf->target->waiters, f); + sd_future_unref(wf->target); + free(wf); +} + +static int wait_future_cancel(sd_future *f) { + WaitFuture *wf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + set_remove(wf->target->waiters, f); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops wait_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = wait_future_alloc, + .free = wait_future_free, + .cancel = wait_future_cancel, +}; + +int sd_future_new_wait(sd_future *target, sd_future **ret) { + int r; + + assert_return(target, -EINVAL); + assert_return(ret, -EINVAL); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&wait_future_ops, &f); + if (r < 0) + return r; + + WaitFuture *wf = sd_future_get_private(f); + wf->target = sd_future_ref(target); + + if (target->state == SD_FUTURE_RESOLVED) + r = sd_future_resolve(f, target->result); + else + r = set_ensure_put(&target->waiters, &trivial_hash_ops, f); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} diff --git a/src/libsystemd/sd-future/test-fiber.c b/src/libsystemd/sd-future/test-fiber.c new file mode 100644 index 0000000000000..2760b919a0045 --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber.c @@ -0,0 +1,1171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#if HAVE_VALGRIND_VALGRIND_H +# include +#endif + +#include "sd-event.h" +#include "sd-future.h" + +#include "architecture.h" +#include "log-context.h" +#include "memory-util.h" +#include "pidref.h" +#include "process-util.h" +#include "tests.h" +#include "time-util.h" + +static int simple_fiber(void *userdata) { + int *value = ASSERT_PTR(userdata); + return *value; +} + +TEST(fiber_simple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 5; + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &f)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(sd_future_result(f), 5); +} + +/* Fiber that yields once */ +static int yielding_fiber(void *userdata) { + int *counter = userdata; + (*counter)++; + + sd_fiber_yield(); + + (*counter)++; + return 0; +} + +/* Test: Single fiber that yields */ +TEST(fiber_single_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", yielding_fiber, &counter, NULL, &f)); + + /* First iteration: fiber runs until first yield */ + ASSERT_EQ(counter, 0); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counter, 1); + + /* Second iteration: fiber runs from yield to completion */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counter, 2); + + /* No more fibers to run */ + ASSERT_OK_ZERO(sd_event_loop(e)); +} + +static int counting_fiber(void *userdata) { + int counter = 0; + + for (int i = 0; i < 5; i++) { + counter++; + sd_fiber_yield(); + } + + return counter; +} + +/* Test: Multiple fibers yielding cooperatively */ +TEST(fiber_multiple_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "counting-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, counting_fiber, NULL, NULL, &fibers[i])); + } + + ASSERT_OK(sd_event_loop(e)); + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_EQ(sd_future_result(fibers[i]), 5); +} + +static int priority_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + sd_fiber_yield(); + + return *counter; +} + +/* Test: Priority-based scheduling */ +TEST(fiber_priority_ascending) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "priority-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, priority_fiber, &counter, NULL, &fibers[i])); + ASSERT_OK(sd_future_set_priority(fibers[i], i)); + } + + ASSERT_OK(sd_event_loop(e)); + + /* The fibers have ascending priorities, so we the first one to run to completion, + * followed by the second one, etc. */ + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_EQ(sd_future_result(fibers[i]), (int) i + 1); +} + +TEST(fiber_priority_identical) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "priority-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, priority_fiber, &counter, NULL, &fibers[i])); + } + + ASSERT_OK(sd_event_loop(e)); + + /* The fibers have the same priorities, so we expect all of them to run once first, and then they'll + * all run again another time, so they should all return the same value. */ + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_EQ(sd_future_result(fibers[i]), (int) 5); +} + +static int error_fiber(void *userdata) { + return -ENOENT; +} + +TEST(fiber_error_return) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "error", error_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(sd_future_result(f), -ENOENT); +} + +static int cancel_fiber(void *userdata) { + return sd_fiber_yield(); +} + +TEST(fiber_cancel_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 42; + ASSERT_OK(sd_fiber_new(e, "cancel", cancel_fiber, &value, NULL, &f)); + + ASSERT_OK(sd_future_cancel(f)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +static int fiber_that_yields(void *userdata) { + int *yield_count = userdata; + int r; + + for (int i = 0; i < 5; i++) { + (*yield_count)++; + r = sd_fiber_yield(); + if (r < 0) + return r; /* Propagate cancellation error */ + } + + return 0; +} + +/* Test: fiber_yield() returns error when fiber is cancelled externally */ +TEST(fiber_cancel_propagation_via_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int yield_count = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", fiber_that_yields, &yield_count, NULL, &f)); + + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(yield_count, 1); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(yield_count, 2); + + ASSERT_OK(sd_future_cancel(f)); + + ASSERT_OK(sd_event_loop(e)); + + /* sd_fiber should have been cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); + ASSERT_EQ(yield_count, 2); +} + +/* Test: Cancel a fiber that has already completed */ +TEST(fiber_cancel_completed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 42; + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &f)); + + /* Run the fiber to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Canceling a completed fiber should be a no-op */ + ASSERT_OK(sd_future_cancel(f)); + ASSERT_EQ(sd_future_result(f), 42); +} + +static int multiple_yield_fiber(void *userdata) { + int *counter = userdata; + int r; + + for (int i = 0; i < 3; i++) { + (*counter)++; + r = sd_fiber_yield(); + if (r < 0) + return r; + } + + return 0; +} + +/* Test: Cancel one fiber among multiple */ +TEST(fiber_cancel_one_of_many) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counters[3] = {0, 0, 0}; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "multiple-yield", multiple_yield_fiber, &counters[i], NULL, &fibers[i])); + + /* Run one iteration - all fibers yield after incrementing once */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counters[0], 1); + ASSERT_EQ(counters[1], 1); + ASSERT_EQ(counters[2], 1); + + /* Cancel the second fiber */ + ASSERT_OK(sd_future_cancel(fibers[1])); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* First and third fibers should complete normally */ + ASSERT_EQ(counters[0], 3); + ASSERT_EQ(counters[2], 3); + ASSERT_EQ(sd_future_result(fibers[0]), 0); + ASSERT_EQ(sd_future_result(fibers[2]), 0); + + /* Second fiber should be canceled with counter at 1 */ + ASSERT_EQ(counters[1], 1); + ASSERT_EQ(sd_future_result(fibers[1]), -ECANCELED); +} + +/* Test: sd_fiber_await() - wait for a fiber to complete */ +static int slow_fiber(void *userdata) { + int *counter = userdata; + + for (int i = 0; i < 3; i++) { + (*counter)++; + sd_fiber_yield(); + } + + return 42; +} + +static int waiting_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + r = sd_future_result(target); + return r == 42 ? 0 : -EIO; +} + +TEST(fiber_wait_for_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create target fiber with lower priority (runs second) */ + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "slow", slow_fiber, &counter, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 1)); + + /* Create waiter fiber with higher priority (runs first) */ + ASSERT_OK(sd_fiber_new(e, "waiting", waiting_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK_EQ(sd_future_result(target), 42); + ASSERT_EQ(counter, 3); +} + +/* Test: wait for already completed fiber */ +static int wait_for_completed_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_completed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int value = 100; + + /* Create target fiber with higher priority (runs first) */ + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 0)); + /* Create waiter fiber with lower priority (runs second, after target completes) */ + ASSERT_OK(sd_fiber_new(e, "wait-for-completed", wait_for_completed_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(waiter), 100); + ASSERT_OK_EQ(sd_future_result(target), 100); +} + +/* Test: awaiting an already-resolved future returns the future's result directly */ +static int await_resolved_fiber(void *userdata) { + sd_future *target = userdata; + + ASSERT_EQ((int) sd_future_state(target), (int) SD_FUTURE_RESOLVED); + ASSERT_OK_EQ(sd_fiber_await(target), 77); + return 0; +} + +TEST(fiber_await_resolved_returns_result) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int value = 77; + + /* Higher-priority target runs to completion before the waiter starts. */ + ASSERT_OK(sd_fiber_new(e, "target", simple_fiber, &value, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 0)); + ASSERT_OK(sd_fiber_new(e, "await-resolved", await_resolved_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK_EQ(sd_future_result(target), 77); +} + +/* Test: wait for cancelled fiber */ +static int wait_for_cancelled_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_cancelled) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", fiber_that_yields, &counter, NULL, &target)); + ASSERT_OK(sd_fiber_new(e, "wait-for-cancelled", wait_for_cancelled_fiber, target, NULL, &waiter)); + + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + ASSERT_OK(sd_future_cancel(target)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_ERROR(sd_future_result(waiter), ECANCELED); + ASSERT_ERROR(sd_future_result(target), ECANCELED); +} + +/* Test: multiple fibers waiting for the same target */ +static int multi_waiter_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_multiple_waiters) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "slow", slow_fiber, &counter, NULL, &target)); + + sd_future *waiters[3] = {}; + CLEANUP_ELEMENTS(waiters, sd_future_unref_array_clear); + for (size_t i = 0; i < ELEMENTSOF(waiters); i++) + ASSERT_OK(sd_fiber_new(e, "multi-waiter", multi_waiter_fiber, target, NULL, &waiters[i])); + + ASSERT_OK(sd_event_loop(e)); + + for (size_t i = 0; i < ELEMENTSOF(waiters); i++) + ASSERT_OK_EQ(sd_future_result(waiters[i]), 42); + + ASSERT_OK_EQ(sd_future_result(target), 42); + ASSERT_EQ(counter, 3); +} + +/* Test: chain of waiting fibers */ +static int chain_waiter_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + r = sd_future_result(target); + return r + 1; +} + +TEST(fiber_wait_for_chain) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int value = 10; + + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &fibers[0])); + + /* Each subsequent fiber waits for the previous and adds 1 */ + for (size_t i = 1; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "chain-waiter", chain_waiter_fiber, fibers[i - 1], NULL, &fibers[i])); + + ASSERT_OK(sd_event_loop(e)); + + /* Check results: 10, 11, 12, 13, 14 */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_EQ(sd_future_result(fibers[i]), 10 + (int) i); +} + +static int nested_run_inner_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + int r = sd_fiber_yield(); + if (r < 0) + return r; + (*counter)++; + + return 0; +} + +static int nested_run_outer_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_future_unrefp) sd_future *nested = NULL; + int r; + + /* Yield once before the nested loop: this forces the outer fiber to later resume through its own + * siglongjmp back to its resume_context after the inner fiber_run() has executed, which is + * exactly the path that breaks when the resume context is stored thread-globally instead of + * per-fiber. */ + r = sd_fiber_yield(); + if (r < 0) + return r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_set_exit_on_idle(inner, true); + if (r < 0) + return r; + + /* Spawn a fiber on the inner event loop. Driving it via sd_event_loop(inner) causes fiber_run() to + * be invoked while we are already executing inside fiber_run() for the outer fiber. */ + r = sd_fiber_new(inner, "inner", nested_run_inner_fiber, counter, NULL, &nested); + if (r < 0) + return r; + + r = sd_event_loop(inner); + if (r < 0) + return r; + + r = sd_future_result(nested); + if (r < 0) + return r; + + /* Yield again after the inner loop has returned. If the outer fiber's resume context was clobbered + * by the nested fiber_run(), the siglongjmp underneath this yield would jump into an already + * unwound stack frame. */ + return sd_fiber_yield(); +} + +TEST(fiber_nested_run) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "outer", nested_run_outer_fiber, &counter, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(outer)); + + /* The inner fiber incremented the counter once before yielding and once after resuming. */ + ASSERT_EQ(counter, 2); +} + +static int nested_current_check_inner_fiber(void *userdata) { + sd_future **slots = ASSERT_PTR(userdata); + + slots[1] = sd_fiber_get_current(); + int r = sd_fiber_yield(); + if (r < 0) + return r; + /* After resuming, the current fiber must still be us, not the outer fiber that was current when + * fiber_run() re-entered. */ + if (sd_fiber_get_current() != slots[1]) + return -EBADF; + + return 0; +} + +static int nested_current_check_outer_fiber(void *userdata) { + sd_future **slots = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_future_unrefp) sd_future *nested = NULL; + int r; + + slots[0] = sd_fiber_get_current(); + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_set_exit_on_idle(inner, true); + if (r < 0) + return r; + + r = sd_fiber_new(inner, "inner", nested_current_check_inner_fiber, slots, NULL, &nested); + if (r < 0) + return r; + + r = sd_event_loop(inner); + if (r < 0) + return r; + + r = sd_future_result(nested); + if (r < 0) + return r; + + /* After the nested fiber_run() has returned, the current fiber must have been restored to the + * outer fiber rather than left as NULL or pointing at the (now freed) inner fiber. */ + if (sd_fiber_get_current() != slots[0]) + return -EBADF; + + return 0; +} + +TEST(fiber_nested_run_current_restored) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *slots[2] = {}; + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + ASSERT_OK(sd_fiber_new(e, "outer", nested_current_check_outer_fiber, slots, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(outer)); + + ASSERT_NOT_NULL(slots[0]); + ASSERT_NOT_NULL(slots[1]); + ASSERT_TRUE(slots[0] != slots[1]); +} + +static int nested_cancellation_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *nested = NULL; + int r; + + if (*counter >= 5) + return sd_fiber_sleep(10 * USEC_PER_SEC); + + (*counter)++; + + _cleanup_free_ char *name = NULL; + if (asprintf(&name, "nested-cancellation-%i", *counter) < 0) + return -ENOMEM; + + /* Create a nested fiber within this fiber */ + r = sd_fiber_new(sd_fiber_get_event(), name, nested_cancellation_fiber, counter, NULL, &nested); + if (r < 0) + return r; + + /* Wait for the nested fiber to complete */ + r = sd_fiber_await(nested); + if (r < 0) + return r; + + /* If we got here without cancellation, verify the nested fiber completed */ + return sd_future_result(nested); +} + +static int exit_loop_fiber(void *userdata) { + /* Just exit the event loop, causing the outer fiber to be cancelled */ + return sd_event_exit(sd_fiber_get_event(), 0); +} + +TEST(fiber_nested_cancellation) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + int counter = 0; + + /* Create outer fiber with higher priority (runs first) */ + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + ASSERT_OK(sd_fiber_new(e, "outer", nested_cancellation_fiber, &counter, NULL, &outer)); + + /* Create exit fiber with lower priority (runs after all nested fibers have suspended) */ + _cleanup_(sd_future_unrefp) sd_future *exit_fiber = NULL; + ASSERT_OK(sd_fiber_new(e, "exit-loop", exit_loop_fiber, NULL, NULL, &exit_fiber)); + ASSERT_OK(sd_future_set_priority(exit_fiber, 1)); + + /* Run the event loop - the exit fiber should cause it to exit, + * which should cancel the outer fiber, which should cancel the nested fiber, and so forth. */ + ASSERT_OK(sd_event_loop(e)); + + /* The exit fiber should have completed successfully */ + ASSERT_OK(sd_future_result(exit_fiber)); + + /* The outer fiber should have been cancelled */ + ASSERT_ERROR(sd_future_result(outer), ECANCELED); + + /* The nested fiber was created and incremented counter once before being cancelled */ + ASSERT_GT(counter, 0); +} + +static int nested_fiber_cleanup_nested_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + int r; + + r = sd_fiber_sleep(10 * USEC_PER_SEC); + if (r == -ECANCELED) + (*counter)++; + else if (r < 0) + return r; + + return 0; +} + +static int nested_fiber_cleanup_fiber(void *userdata) { + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *nested = NULL; + int r; + + /* Create a nested fiber within this fiber. */ + r = sd_fiber_new(sd_fiber_get_event(), "nested", nested_fiber_cleanup_nested_fiber, userdata, NULL, &nested); + if (r < 0) + return r; + + /* Yield and then exit, the nested fiber should be cancelled. */ + return sd_fiber_yield(); +} + +TEST(nested_fiber_cleanup) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "outer", nested_fiber_cleanup_fiber, &counter, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + + /* The outer fiber should have finished normally */ + ASSERT_OK(sd_future_result(outer)); + + /* The nested fiber was created and incremented its counter once when it was cancelled. */ + ASSERT_GT(counter, 0); +} + +static int priority_check_fiber(void *userdata) { + int64_t *ret = ASSERT_PTR(userdata); + + /* Verify that sd_fiber_get_priority() returns the value set via sd_future_set_priority() */ + ASSERT_OK(sd_fiber_get_priority(ret)); + + /* Exercise sd_fiber_sleep() which internally creates a time future. This verifies that the priority + * is correctly propagated to the time event source (via f->time.source, not f->io.source). */ + return sd_fiber_sleep(1); +} + +TEST(fiber_priority_get) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + int64_t got_priority = 0; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "priority-check", priority_check_fiber, &got_priority, NULL, &f)); + ASSERT_OK(sd_future_set_priority(f, 10)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify priority was stored and retrievable */ + ASSERT_EQ(got_priority, 10); +} + +static int floating_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + int r = sd_fiber_yield(); + if (r < 0) + return r; + (*counter)++; + + return 0; +} + +TEST(fiber_floating) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating", floating_fiber, &counter, NULL, &f)); + + ASSERT_OK_ZERO(sd_fiber_get_floating(f)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK_POSITIVE(sd_fiber_get_floating(f)); + + /* Drop our handle: the floating ref keeps the future alive until the fiber resolves, after + * which the self-unref frees it. If this didn't work we'd either leak (visible under ASan) or + * trip fiber_free()'s "state == COMPLETED" assertion. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +static int drop_extra_ref(sd_future *f) { + /* Drop an extra ref the test installed before the callback fires. After this returns, the + * floating self-ref is the only thing keeping the future alive — exercising the path where + * the floating unref in fiber_run() is the last unref. */ + sd_future_unref(f); + return 0; +} + +TEST(fiber_floating_callback_drops_ref) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating-cb", floating_fiber, &counter, NULL, &f)); + + ASSERT_OK(sd_fiber_set_floating(f, true)); + + /* Bump the ref for the callback to drop, then install the callback. */ + sd_future_ref(f); + ASSERT_OK(sd_future_set_callback(f, drop_extra_ref, NULL)); + + /* Drop our handle. Refs remaining: floating self-ref + the extra ref the callback will drop. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +TEST(fiber_floating_toggle) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating-toggle", floating_fiber, &counter, NULL, &f)); + + /* Toggling floating on and off again should leave the refcount unchanged: set_floating(true) + * takes a ref and set_floating(false) drops it. If the accounting were off, the subsequent + * event loop would either free the future while the fiber still runs (fiber_free assertion) + * or leak it. */ + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK(sd_fiber_set_floating(f, false)); + ASSERT_OK_ZERO(sd_fiber_get_floating(f)); + + /* Setting floating to the same value twice should be a no-op. */ + ASSERT_OK(sd_fiber_set_floating(f, false)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + + /* Drop our handle; the still-floating ref drives cleanup. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +/* Test: SD_FIBER_TIMEOUT scope expires while the fiber is suspended with no other wakeup source. */ +static int timeout_suspend_fiber(void *userdata) { + SD_FIBER_TIMEOUT(50 * USEC_PER_MSEC); + + /* Plain suspend with no other future to wake us — only the deadline timer can resume. */ + return sd_fiber_suspend(); +} + +TEST(fiber_timeout_suspend_expires) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "timeout-suspend", timeout_suspend_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ETIME); +} + +/* Test: SD_FIBER_TIMEOUT scope around a sleep that finishes before the deadline expires; the + * cleanup must cancel the timer cleanly without leaving a stale wakeup. */ +static int timeout_in_time_fiber(void *userdata) { + SD_FIBER_TIMEOUT(1 * USEC_PER_SEC); + return sd_fiber_sleep(10 * USEC_PER_MSEC); +} + +TEST(fiber_timeout_sleep_in_time) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "in-time", timeout_in_time_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: SD_FIBER_TIMEOUT(USEC_INFINITY) is a no-op — no timer is created and the fiber completes + * normally. */ +static int timeout_infinite_fiber(void *userdata) { + SD_FIBER_TIMEOUT(USEC_INFINITY); + return sd_fiber_sleep(10 * USEC_PER_MSEC); +} + +TEST(fiber_timeout_infinite_no_op) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "infinite", timeout_infinite_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: SD_FIBER_WITH_TIMEOUT block form returns -ETIME from the suspend inside it. */ +static int with_timeout_block_fiber(void *userdata) { + int r = 0; + SD_FIBER_WITH_TIMEOUT(50 * USEC_PER_MSEC) + r = sd_fiber_suspend(); + return r; +} + +TEST(fiber_with_timeout_block) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "with-timeout", with_timeout_block_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ETIME); +} + +/* Test: nested SD_FIBER_TIMEOUT — inner scope's timer fires first; once we're back in just the + * outer scope, suspending again must time out via the still-armed outer timer. */ +static int nested_timeout_fiber(void *userdata) { + int *fired = ASSERT_PTR(userdata); + + SD_FIBER_TIMEOUT(50 * USEC_PER_MSEC); /* outer */ + + SD_FIBER_WITH_TIMEOUT(20 * USEC_PER_MSEC) { /* inner — expires first */ + int r = sd_fiber_suspend(); + if (r != -ETIME) + return -ENOTRECOVERABLE; + (*fired)++; + } + + /* Inner scope is gone, but the outer timer is still armed (it only used ~20ms of its + * 100ms budget). Suspending again must eventually wake us with -ETIME. */ + int r = sd_fiber_suspend(); + if (r != -ETIME) + return -ENOTRECOVERABLE; + (*fired)++; + + return 0; +} + +TEST(fiber_timeout_nested) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + int fired = 0; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "nested-timeout", nested_timeout_fiber, &fired, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); + ASSERT_EQ(fired, 2); +} + +/* Test: signal mask is per-thread, not per-fiber. Changes one fiber makes via pthread_sigmask + * must be visible to other fibers on the same thread, both while the modifying fiber is + * suspended and after it resumes. The fiber switch (sigsetjmp/siglongjmp with savesigs=0) + * deliberately doesn't save or restore the mask. */ +static int sigmask_peer_fiber(void *userdata) { + sigset_t set, current; + + /* The waiter blocked SIGUSR1 before await'ing us; the per-thread mask should still + * have it blocked here. */ + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_TRUE(sigismember(¤t, SIGUSR1)); + + ASSERT_OK(sigemptyset(&set)); + ASSERT_OK(sigaddset(&set, SIGUSR1)); + ASSERT_OK_ZERO(-pthread_sigmask(SIG_UNBLOCK, &set, NULL)); + + return 0; +} + +static int sigmask_waiter_fiber(void *userdata) { + sd_future *peer = ASSERT_PTR(userdata); + sigset_t set, current; + + ASSERT_OK(sigemptyset(&set)); + ASSERT_OK(sigaddset(&set, SIGUSR1)); + ASSERT_OK_ZERO(-pthread_sigmask(SIG_BLOCK, &set, NULL)); + + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_TRUE(sigismember(¤t, SIGUSR1)); + + int r = sd_fiber_await(peer); + if (r < 0) + return r; + + /* The peer unblocked SIGUSR1 while we were suspended. The change is per-thread, so + * we must observe it here. */ + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_FALSE(sigismember(¤t, SIGUSR1)); + + return 0; +} + +TEST(fiber_signal_mask_is_per_thread) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sigset_t saved; + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, &saved)); + + _cleanup_(sd_future_unrefp) sd_future *waiter = NULL, *peer = NULL; + ASSERT_OK(sd_fiber_new(e, "sigmask-peer", sigmask_peer_fiber, NULL, NULL, &peer)); + ASSERT_OK(sd_future_set_priority(peer, 1)); + ASSERT_OK(sd_fiber_new(e, "sigmask-waiter", sigmask_waiter_fiber, peer, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK(sd_future_result(peer)); + + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, &saved, NULL)); +} + +/* Test: log context is per-fiber. fiber_run() swaps the thread-local log context (and prefix) with + * a per-fiber stash on entry and exit, so fields pushed by one fiber must not leak into another + * fiber that runs while the first is suspended, and must be restored when the first resumes. */ +static int log_context_peer_fiber(void *userdata) { + size_t *peer_observed = ASSERT_PTR(userdata); + + /* The waiter pushed a field before await'ing us. If log context were shared across fibers, + * we would observe it here. Record what we see and let the caller verify. */ + *peer_observed = log_context_num_fields(); + + return 0; +} + +static int log_context_waiter_fiber(void *userdata) { + sd_future *peer = ASSERT_PTR(userdata); + + size_t before_push = log_context_num_fields(); + + LOG_CONTEXT_PUSH("WAITER=here"); + size_t after_push = log_context_num_fields(); + if (after_push != before_push + 1) + return -EBADF; + + int r = sd_fiber_await(peer); + if (r < 0) + return r; + + /* Our pushed field must be visible again after the peer ran and resumed us. */ + if (log_context_num_fields() != after_push) + return -EBADF; + + return 0; +} + +TEST(fiber_log_context_per_fiber) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + size_t baseline = log_context_num_fields(); + + size_t peer_observed = 0; + _cleanup_(sd_future_unrefp) sd_future *waiter = NULL, *peer = NULL; + ASSERT_OK(sd_fiber_new(e, "log-peer", log_context_peer_fiber, &peer_observed, NULL, &peer)); + ASSERT_OK(sd_future_set_priority(peer, 1)); + ASSERT_OK(sd_fiber_new(e, "log-waiter", log_context_waiter_fiber, peer, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK(sd_future_result(peer)); + + /* Inside the peer, only the peer's own FIBER= field (pushed by fiber_run) should have been + * active — the waiter's WAITER= push must have been swapped out. */ + ASSERT_EQ(peer_observed, baseline + 1); + + /* The thread-local log context should be exactly as it was before the test ran. */ + ASSERT_EQ(log_context_num_fields(), baseline); +} + +static int stack_overflow_fiber(void *userdata) { + volatile char anchor; + size_t pagesz = page_size(); + + /* Walk one page at a time away from the fiber's current SP toward the guard page, + * writing one byte per page until the kernel raises a fatal signal. On downward + * stacks we walk to lower addresses (guard at the base); on upward stacks like + * hppa we walk to higher addresses (guard at the top of the mapping). The 64 MiB + * ceiling is purely a safety net so the test fails loudly instead of looping if + * the guard isn't there. */ + for (size_t i = 1; i < (64U * U64_MB) / pagesz; i++) { + uintptr_t off = i * pagesz; + volatile char *p = (volatile char *) (STACK_GROWS_UP + ? (uintptr_t) &anchor + off + : (uintptr_t) &anchor - off); + *p = 0; + } + return 0; +} + +TEST(fiber_stack_guard) { +#if HAS_FEATURE_ADDRESS_SANITIZER + (void) log_tests_skipped("ASan intercepts deliberate stack OOB writes"); + return; +#endif +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + (void) log_tests_skipped("Valgrind intercepts deliberate stack OOB writes"); + return; + } +#endif + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r = pidref_safe_fork("(stack-overflow)", FORK_RESET_SIGNALS|FORK_LOG, &pidref); + ASSERT_OK(r); + + if (r == 0) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "overflow", stack_overflow_fiber, NULL, NULL, &f)); + (void) sd_event_loop(e); + _exit(EXIT_SUCCESS); /* unreachable if the guard fires */ + } + + siginfo_t si; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_TRUE(IN_SET(si.si_code, CLD_KILLED, CLD_DUMPED)); + ASSERT_TRUE(IN_SET(si.si_status, SIGSEGV, SIGBUS)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h index f9c9a2627d55c..8da080bf18e61 100644 --- a/src/systemd/_sd-common.h +++ b/src/systemd/_sd-common.h @@ -69,6 +69,20 @@ typedef void (*_sd_destroy_t)(void *userdata); # define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) #endif +/* Mirror of CONCATENATE / UNIQ from macro-fundamental.h, available to public sd-* headers. */ +#ifndef _SD_CONCATENATE +# define _SD_XCONCATENATE(x, y) x ## y +# define _SD_CONCATENATE(x, y) _SD_XCONCATENATE(x, y) +#endif + +#ifndef _SD_UNIQ +# ifdef __COUNTER__ +# define _SD_UNIQ __COUNTER__ +# else +# define _SD_UNIQ __LINE__ +# endif +#endif + #ifndef _SD_BEGIN_DECLARATIONS # ifdef __cplusplus # define _SD_BEGIN_DECLARATIONS \ diff --git a/src/systemd/meson.build b/src/systemd/meson.build index ad455d73b217b..c35f811245db0 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -36,6 +36,7 @@ _not_installed_headers = [ 'sd-dhcp6-option.h', 'sd-dhcp6-protocol.h', 'sd-dns-resolver.h', + 'sd-future.h', 'sd-ipv4acd.h', 'sd-ipv4ll.h', 'sd-lldp-rx.h', diff --git a/src/systemd/sd-future.h b/src/systemd/sd-future.h new file mode 100644 index 0000000000000..9d0d03acf48a7 --- /dev/null +++ b/src/systemd/sd-future.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdfuturefoo +#define foosdfuturefoo + +/*** + 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 "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_event sd_event; +typedef struct sd_future sd_future; +typedef struct sd_future_ops sd_future_ops; +typedef int (*sd_future_func_t)(sd_future *f); +typedef int (*sd_fiber_func_t)(void *userdata); +typedef _sd_destroy_t sd_fiber_destroy_t; + +struct sd_future_ops { + size_t size; + void* (*alloc)(void); + void (*free)(sd_future *f); + int (*cancel)(sd_future *f); + int (*set_priority)(sd_future *f, int64_t priority); +}; + +__extension__ typedef enum _SD_ENUM_TYPE_S64(sd_future_state_t) { + SD_FUTURE_PENDING, + SD_FUTURE_RESOLVED, + _SD_ENUM_FORCE_S64(SD_FUTURE_STATE) +} sd_future_state_t; + +int sd_future_new(const sd_future_ops *ops, sd_future **ret); +int sd_future_cancel(sd_future *f); +int sd_future_resolve(sd_future *f, int result); + +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_future); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_future, sd_future_unref); +void sd_future_unref_array_clear(sd_future *array[], size_t n); +void sd_future_unref_array(sd_future *array[], size_t n); + +sd_future* sd_future_cancel_wait_unref(sd_future *f); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_future, sd_future_cancel_wait_unref); +void sd_future_cancel_wait_unref_array_clear(sd_future *array[], size_t n); +void sd_future_cancel_wait_unref_array(sd_future *array[], size_t n); + +int sd_future_state(sd_future *f); +int sd_future_result(sd_future *f); +void* sd_future_get_userdata(sd_future *f); +void* sd_future_get_private(sd_future *f); +const sd_future_ops* sd_future_get_ops(sd_future *f); + +int sd_future_set_callback(sd_future *f, sd_future_func_t callback, void *userdata); +int sd_future_set_priority(sd_future *f, int64_t priority); + +int sd_future_new_wait(sd_future *target, sd_future **ret); + +int sd_fiber_new(sd_event *e, const char *name, sd_fiber_func_t func, void *userdata, sd_fiber_destroy_t destroy, sd_future **ret); + +int sd_fiber_set_floating(sd_future *f, int b); +int sd_fiber_get_floating(sd_future *f); + +int sd_fiber_is_running(void); +sd_future* sd_fiber_get_current(void); +int sd_fiber_get_priority(int64_t *ret); +sd_event* sd_fiber_get_event(void); + +int sd_fiber_yield(void); +int sd_fiber_sleep(uint64_t usec); +int sd_fiber_await(sd_future *target); +int sd_fiber_suspend(void); +int sd_fiber_resume(sd_future *f, int result); + +sd_future* sd_fiber_timeout(uint64_t timeout); + +#define SD_FIBER_TIMEOUT(timeout) _SD_FIBER_TIMEOUT(_SD_UNIQ, (timeout)) +#define _SD_FIBER_TIMEOUT(uniq, timeout) \ + sd_future *_SD_CONCATENATE(_sd_fto_, uniq) __attribute__((cleanup(sd_future_cancel_wait_unrefp), unused)) = sd_fiber_timeout(timeout) + +#define SD_FIBER_WITH_TIMEOUT(timeout) _SD_FIBER_WITH_TIMEOUT(_SD_UNIQ, (timeout)) +#define _SD_FIBER_WITH_TIMEOUT(uniq, timeout) \ + for (sd_future *_SD_CONCATENATE(_sd_fto_, uniq) __attribute__((cleanup(sd_future_cancel_wait_unrefp), unused)) = sd_fiber_timeout(timeout), \ + *_SD_CONCATENATE(_sd_fto_b_, uniq) = (sd_future*) (uintptr_t) 1; \ + _SD_CONCATENATE(_sd_fto_b_, uniq); \ + _SD_CONCATENATE(_sd_fto_b_, uniq) = NULL) + +_SD_END_DECLARATIONS; + +#endif diff --git a/test/integration-tests/TEST-02-UNITTESTS/meson.build b/test/integration-tests/TEST-02-UNITTESTS/meson.build index 9be6f2a6d9532..b074903c00ac9 100644 --- a/test/integration-tests/TEST-02-UNITTESTS/meson.build +++ b/test/integration-tests/TEST-02-UNITTESTS/meson.build @@ -3,7 +3,7 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), - 'coredump-exclude-regex' : '/(bash|python3.[0-9]+|systemd-executor)$', + 'coredump-exclude-regex' : '/(bash|python3.[0-9]+|systemd-executor|test-fiber)$', 'cmdline' : integration_test_template['cmdline'] + [ ''' diff --git a/test/test-link-abi.py b/test/test-link-abi.py index a36e6c32d7ec0..8a935a513fe43 100755 --- a/test/test-link-abi.py +++ b/test/test-link-abi.py @@ -28,6 +28,7 @@ 'libm.so.', 'libresolv.so.', 'libc.musl-', + 'libucontext.', ) # GCC runtime support libraries (stack unwinding, soft-float helpers, C++ standard library). The From cf4c65afa86021a750de38bbed192eeb1c9fd425 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 25 Apr 2026 22:31:58 +0200 Subject: [PATCH 2082/2155] sd-future: add fiber-aware non-blocking I/O wrappers Add a family of sd_fiber_*() I/O wrappers that, when called from a fiber, behave like blocking I/O from the caller's perspective but yield to the event loop instead of blocking the thread: sd_fiber_read / sd_fiber_write sd_fiber_readv / sd_fiber_writev sd_fiber_recv / sd_fiber_send sd_fiber_connect sd_fiber_recvmsg / sd_fiber_sendmsg sd_fiber_recvfrom / sd_fiber_sendto sd_fiber_accept sd_fiber_ppoll Most of them share a single helper, fiber_io_operation(), which when invoked outside a fiber falls through to the underlying syscall directly, preserving the regular blocking behaviour. Inside a fiber the helper flips the fd to non-blocking (restoring its original mode on return), tries the syscall once on the fast path, and on EAGAIN/ EWOULDBLOCK creates an sd-event-backed IO future via future_new_io(), suspends the fiber, and retries the syscall once the event source fires. future_new_io() itself is added to sd-event/event-future.{c,h} as a new IoFuture kind. It wraps sd_event_add_io() into an sd_future: oneshot enable, EPOLLERR translated via SO_ERROR (suppressed for non-sockets), and the fd duplicated with F_DUPFD_CLOEXEC to avoid EEXIST when multiple sources watch the same descriptor. Together these let fiber-using code write straight-line socket and pipe I/O without bundling state into callbacks. --- src/basic/io-util.c | 9 + src/basic/io-util.h | 6 + src/libsystemd/meson.build | 2 + src/libsystemd/sd-event/event-future.c | 118 ++ src/libsystemd/sd-event/event-future.h | 1 + src/libsystemd/sd-future/fiber-io.c | 471 ++++++++ src/libsystemd/sd-future/test-fiber-io.c | 1388 ++++++++++++++++++++++ src/systemd/sd-future.h | 25 + 8 files changed, 2020 insertions(+) create mode 100644 src/libsystemd/sd-future/fiber-io.c create mode 100644 src/libsystemd/sd-future/test-fiber-io.c diff --git a/src/basic/io-util.c b/src/basic/io-util.c index 103aa2a7cde03..b4f643b5721aa 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -3,6 +3,7 @@ #include #include #include +#include /* IWYU pragma: keep */ #include #include @@ -10,6 +11,14 @@ #include "io-util.h" #include "time-util.h" +/* EPOLL_POLL_COMMON_MASK in io-util.h treats POLL* and EPOLL* as interchangeable; verify it. */ +assert_cc((uint32_t) POLLIN == EPOLLIN); +assert_cc((uint32_t) POLLOUT == EPOLLOUT); +assert_cc((uint32_t) POLLERR == EPOLLERR); +assert_cc((uint32_t) POLLHUP == EPOLLHUP); +assert_cc((uint32_t) POLLPRI == EPOLLPRI); +assert_cc((uint32_t) POLLRDHUP == EPOLLRDHUP); + int flush_fd(int fd) { int count = 0; diff --git a/src/basic/io-util.h b/src/basic/io-util.h index 918108c023b68..ab52dc8db6506 100644 --- a/src/basic/io-util.h +++ b/src/basic/io-util.h @@ -3,6 +3,12 @@ #include "basic-forward.h" +/* The intersection of poll() and epoll_wait() event masks. Linux defines POLL* and EPOLL* with the + * same numeric values for these — see the assert_cc()s in io-util.c — so this mask can be used + * interchangeably as a `revents` (poll) or `events` (epoll) bitset. */ +#define EPOLL_POLL_COMMON_MASK \ + (EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLPRI | EPOLLRDHUP) + int flush_fd(int fd); ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 061ff6213f61e..3c624051f0ded 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -77,6 +77,7 @@ sd_device_sources = files( ############################################################ sd_future_sources = files( + 'sd-future/fiber-io.c', 'sd-future/fiber.c', 'sd-future/sd-future.c', ) @@ -190,6 +191,7 @@ simple_tests += files( 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', 'sd-future/test-fiber.c', + 'sd-future/test-fiber-io.c', 'sd-hwdb/test-sd-hwdb.c', 'sd-id128/test-id128.c', 'sd-journal/test-audit-type.c', diff --git a/src/libsystemd/sd-event/event-future.c b/src/libsystemd/sd-event/event-future.c index 4595a7a6fb0b5..8c9960fcdd9d0 100644 --- a/src/libsystemd/sd-event/event-future.c +++ b/src/libsystemd/sd-event/event-future.c @@ -6,6 +6,124 @@ #include "alloc-util.h" #include "errno-util.h" #include "event-future.h" +#include "fd-util.h" + +typedef struct IoFuture { + sd_event_source *source; +} IoFuture; + +static void* io_future_alloc(void) { + return new0(IoFuture, 1); +} + +static void io_future_free(sd_future *f) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_event_source_unref(iof->source); + free(iof); +} + +static int io_future_cancel(sd_future *f) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r = 0; + + RET_GATHER(r, sd_event_source_set_enabled(iof->source, SD_EVENT_OFF)); + RET_GATHER(r, sd_future_resolve(f, -ECANCELED)); + return r; +} + +static int io_future_set_priority(sd_future *f, int64_t priority) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + return sd_event_source_set_priority(iof->source, priority); +} + +static const sd_future_ops io_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = io_future_alloc, + .free = io_future_free, + .cancel = io_future_cancel, + .set_priority = io_future_set_priority, +}; + +static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + + /* Resolve with the revents mask on success (matching io_uring poll_add's CQE convention) so + * callers can read it directly off the future result. EPOLLERR is the one exception: surface + * the actual socket error via SO_ERROR so callers like sd_fiber_connect() can return -errno + * directly without re-querying. */ + if (FLAGS_SET(revents, EPOLLERR)) { + int error = 0; + socklen_t len = sizeof(error); + + int r = RET_NERRNO(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)); + if (r == -ENOTSOCK) + return sd_future_resolve(f, (int) revents); + if (r >= 0 && error != 0) + return sd_future_resolve(f, -error); + if (r >= 0) + /* EPOLLERR was reported but SO_ERROR returned no pending error (e.g. + * already consumed elsewhere). Surface the revents mask so the caller + * still sees the error condition rather than mistaking it for success. */ + return sd_future_resolve(f, (int) revents); + /* On any other getsockopt() error fall through and resolve the future with that + * error so the waiting fiber wakes up rather than hanging forever. */ + return sd_future_resolve(f, r); + } + + return sd_future_resolve(f, (int) revents); +} + +int future_new_io(sd_event *e, int fd, uint32_t events, sd_future **ret) { + int r; + + assert(e); + assert(fd >= 0); + assert(ret); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&io_future_ops, &f); + if (r < 0) + return r; + + IoFuture *iof = sd_future_get_private(f); + + /* Duplicate fd to avoid EEXIST from epoll when adding the same fd multiple times */ + _cleanup_close_ int fd_copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd_copy < 0) + return -errno; + + r = sd_event_add_io(e, &iof->source, fd_copy, events, io_handler, f); + if (r < 0) + return r; + + r = sd_event_source_set_io_fd_own(iof->source, true); + if (r < 0) + return r; + + TAKE_FD(fd_copy); + + r = sd_event_source_set_enabled(iof->source, SD_EVENT_ONESHOT); + if (r < 0) + return r; + + if (sd_fiber_is_running()) { + int64_t priority; + + r = sd_fiber_get_priority(&priority); + if (r < 0) + return r; + + r = sd_event_source_set_priority(iof->source, priority); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(f); + return 0; +} typedef struct TimeFuture { sd_event_source *source; diff --git a/src/libsystemd/sd-event/event-future.h b/src/libsystemd/sd-event/event-future.h index 7e956906ebf74..3bc275e7b7ac9 100644 --- a/src/libsystemd/sd-event/event-future.h +++ b/src/libsystemd/sd-event/event-future.h @@ -3,5 +3,6 @@ #include "sd-forward.h" +int future_new_io(sd_event *e, int fd, uint32_t events, sd_future **ret); int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); diff --git a/src/libsystemd/sd-future/fiber-io.c b/src/libsystemd/sd-future/fiber-io.c new file mode 100644 index 0000000000000..823e8907185c5 --- /dev/null +++ b/src/libsystemd/sd-future/fiber-io.c @@ -0,0 +1,471 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "event-future.h" +#include "fd-util.h" +#include "io-util.h" +#include "time-util.h" + +typedef ssize_t (*FiberIOFunc)(int fd, void *args); + +static ssize_t fiber_io_operation( + int fd, + uint32_t events, + FiberIOFunc func, + void *args) { + _cleanup_(nonblock_resetp) int reset_fd = -EBADF; + int r; + + assert(fd >= 0); + assert(func); + + if (!sd_fiber_is_running()) + return func(fd, args); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + if (r > 0) + reset_fd = fd; + + ssize_t n = func(fd, args); + if (n >= 0 || !ERRNO_IS_NEG_TRANSIENT(n)) + return n; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(e, fd, events, &io); + if (r < 0) + return r; + + r = sd_fiber_suspend(); + if (r < 0) + return r; + + return func(fd, args); +} + +typedef struct ReadArgs { + void *buf; + size_t count; +} ReadArgs; + +static ssize_t read_callback(int fd, void *args) { + ReadArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = read(fd, a->buf, a->count); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_read(int fd, void *buf, size_t count) { + assert_return(fd >= 0, -EBADF); + assert_return(buf || count == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLIN, read_callback, &(ReadArgs) { + .buf = buf, + .count = count, + }); +} + +typedef struct WriteArgs { + const void *buf; + size_t count; +} WriteArgs; + +static ssize_t write_callback(int fd, void *args) { + WriteArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = write(fd, a->buf, a->count); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_write(int fd, const void *buf, size_t count) { + assert_return(fd >= 0, -EBADF); + assert_return(buf || count == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLOUT, write_callback, &(WriteArgs) { + .buf = buf, + .count = count, + }); +} + +typedef struct ReadvArgs { + const struct iovec *iov; + int iovcnt; +} ReadvArgs; + +static ssize_t readv_callback(int fd, void *args) { + ReadvArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = readv(fd, a->iov, a->iovcnt); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_readv(int fd, const struct iovec *iov, int iovcnt) { + assert_return(fd >= 0, -EBADF); + assert_return(iov || iovcnt == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLIN, readv_callback, &(ReadvArgs) { + .iov = iov, + .iovcnt = iovcnt, + }); +} + +typedef struct WritevArgs { + const struct iovec *iov; + int iovcnt; +} WritevArgs; + +static ssize_t writev_callback(int fd, void *args) { + WritevArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = writev(fd, a->iov, a->iovcnt); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_writev(int fd, const struct iovec *iov, int iovcnt) { + assert_return(fd >= 0, -EBADF); + assert_return(iov || iovcnt == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLOUT, writev_callback, &(WritevArgs) { + .iov = iov, + .iovcnt = iovcnt, + }); +} + +typedef struct RecvArgs { + void *buf; + size_t len; + int flags; +} RecvArgs; + +static ssize_t recv_callback(int fd, void *args) { + RecvArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recv(fd, a->buf, a->len, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recv(int sockfd, void *buf, size_t len, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLIN, recv_callback, &(RecvArgs) { + .buf = buf, + .len = len, + .flags = flags, + }); +} + +typedef struct SendArgs { + const void *buf; + size_t len; + int flags; +} SendArgs; + +static ssize_t send_callback(int fd, void *args) { + SendArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = send(fd, a->buf, a->len, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_send(int sockfd, const void *buf, size_t len, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLOUT, send_callback, &(SendArgs) { + .buf = buf, + .len = len, + .flags = flags, + }); +} + +int sd_fiber_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + _cleanup_(nonblock_resetp) int reset_fd = -EBADF; + int r; + + assert_return(sockfd >= 0, -EBADF); + assert_return(addr, -EINVAL); + + if (!sd_fiber_is_running()) + return RET_NERRNO(connect(sockfd, addr, addrlen)); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + r = fd_nonblock(sockfd, true); + if (r < 0) + return r; + if (r > 0) + reset_fd = sockfd; + + r = RET_NERRNO(connect(sockfd, addr, addrlen)); + if (r != -EINPROGRESS) + return r; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(e, sockfd, EPOLLOUT, &io); + if (r < 0) + return r; + + /* future_new_io resolves with the revents mask on success; translate any positive value + * (e.g. POLLOUT) back to the connect(2) success status. */ + r = sd_fiber_suspend(); + return r > 0 ? 0 : r; +} + +typedef struct RecvmsgArgs { + struct msghdr *msg; + int flags; +} RecvmsgArgs; + +static ssize_t recvmsg_callback(int fd, void *args) { + RecvmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recvmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recvmsg(int sockfd, struct msghdr *msg, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(msg, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLIN, recvmsg_callback, &(RecvmsgArgs) { + .msg = msg, + .flags = flags, + }); +} + +typedef struct SendmsgArgs { + const struct msghdr *msg; + int flags; +} SendmsgArgs; + +static ssize_t sendmsg_callback(int fd, void *args) { + SendmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = sendmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_sendmsg(int sockfd, const struct msghdr *msg, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(msg, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLOUT, sendmsg_callback, &(SendmsgArgs) { + .msg = msg, + .flags = flags, + }); +} + +static ssize_t recvfrom_callback(int fd, void *args) { + RecvmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recvmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { + ssize_t n; + + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + assert_return(!src_addr || addrlen, -EINVAL); + + /* io_uring has no direct recvfrom prep helper, so emulate via recvmsg with a single-iovec + * msghdr. The kernel updates msg_namelen in place; we copy it back to *addrlen below. */ + struct iovec iov = { .iov_base = buf, .iov_len = len }; + struct msghdr msg = { + .msg_name = src_addr, + .msg_namelen = src_addr ? *addrlen : 0, + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + n = fiber_io_operation(sockfd, EPOLLIN, recvfrom_callback, &(RecvmsgArgs) { + .msg = &msg, + .flags = flags, + }); + if (n < 0) + return n; + + if (addrlen) + *addrlen = msg.msg_namelen; + + return n; +} + +static ssize_t sendto_callback(int fd, void *args) { + SendmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = sendmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + struct iovec iov = { .iov_base = (void *) buf, .iov_len = len }; + struct msghdr msg = { + .msg_name = (void *) dest_addr, + .msg_namelen = dest_addr ? addrlen : 0, + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + return fiber_io_operation(sockfd, EPOLLOUT, sendto_callback, &(SendmsgArgs) { + .msg = &msg, + .flags = flags, + }); +} + +typedef struct AcceptArgs { + struct sockaddr *addr; + socklen_t *addrlen; + int flags; +} AcceptArgs; + +static ssize_t accept_callback(int fd, void *args) { + AcceptArgs *a = ASSERT_PTR(args); + + return RET_NERRNO(accept4(fd, a->addr, a->addrlen, a->flags)); +} + +int sd_fiber_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { + assert_return(sockfd >= 0, -EBADF); + + return fiber_io_operation(sockfd, EPOLLIN, accept_callback, &(AcceptArgs) { + .addr = addr, + .addrlen = addrlen, + .flags = flags, + }); +} + +int sd_fiber_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask) { + int r; + + assert_return(fds || n_fds == 0, -EINVAL); + + if (!sd_fiber_is_running()) + return RET_NERRNO(ppoll(fds, n_fds, timeout, sigmask)); + + /* When on a fiber signals are handled via sd-event hence we should never mess around with the + * signal mask when running on a fiber. */ + assert_return(!sigmask, -EOPNOTSUPP); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + /* No fds to wait on and no timeout means there's nothing that could ever wake the fiber up, + * since unlike raw ppoll() we cannot use signal delivery as a wakeup. Signals received while + * the fiber is suspended are handled by sd-event via signalfd, in which case the signal handler + * is expected to cancel the fiber via sd_future_cancel() if a wakeup is desired. */ + if (n_fds == 0 && !timeout) + return -EINVAL; + + bool zero_timeout = timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0; + + /* Try polling with zero timeout first to see if any are immediately ready. */ + r = RET_NERRNO(ppoll(fds, n_fds, &(const struct timespec) {}, /* sigmask= */ NULL)); + if (zero_timeout || r != 0) /* Either error or some fds are ready */ + return r; + + sd_future **futures = NULL; + CLEANUP_ARRAY(futures, n_fds, sd_future_cancel_wait_unref_array); + + futures = new0(sd_future*, n_fds); + if (!futures) + return -ENOMEM; + + /* Set up I/O event sources for all valid fds. POLL* and EPOLL* share their bit values (see + * EPOLL_POLL_COMMON_MASK in io-util.h), so we can pass the user-supplied event mask through + * to either backend without translation. */ + size_t n_io_futures = 0; + for (size_t i = 0; i < n_fds; i++) { + if (fds[i].fd < 0) + continue; + + uint32_t events = fds[i].events & EPOLL_POLL_COMMON_MASK; + if (events == 0) + continue; + + r = future_new_io(e, fds[i].fd, events, &futures[i]); + if (r < 0) + return r; + + n_io_futures++; + } + + /* A timeout that overflows usec_t saturates to USEC_INFINITY in timespec_load(); treat that + * like "no timeout" (matches sd_fiber_sleep(USEC_INFINITY)) rather than letting + * sd_event_add_time_relative() reject it with -EOVERFLOW — standard ppoll() would just + * wait a very long time. */ + usec_t usec = timeout ? timespec_load(timeout) : USEC_INFINITY; + + /* If every fd was skipped (negative or empty event mask) and we'd have no timer, there's + * nothing that could ever wake the fiber up — same situation as n_fds == 0 && !timeout, + * just not detectable upfront. Refuse rather than suspend forever. */ + if (n_io_futures == 0 && usec == USEC_INFINITY) + return -EINVAL; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + if (usec != USEC_INFINITY) { + r = future_new_time_relative( + e, + CLOCK_MONOTONIC, + usec, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + } + + r = sd_fiber_suspend(); + if (r < 0 && r != -ETIME) + return r; + + /* Always sweep fds with a non-blocking ppoll(): the timer and an fd readiness can resolve in + * the same event-loop tick (or the fd can become ready between the timer firing and us being + * scheduled), and ppoll() semantics give events precedence over the timeout in that case. */ + int n = RET_NERRNO(ppoll(fds, n_fds, &(const struct timespec) {}, /* sigmask= */ NULL)); + if (n != 0) + return n; + + /* No fds ready: distinguish our own timer from an external -ETIME. */ + if (timer && sd_future_state(timer) == SD_FUTURE_RESOLVED) + return 0; + + /* An IO future resolved with a revents mask (r > 0) but the readiness was already consumed + * by the time we swept — report 0 rather than leaking the bitmask as a (bogus) ppoll fd + * count to the caller. */ + if (r > 0) + return 0; + + return r; +} diff --git a/src/libsystemd/sd-future/test-fiber-io.c b/src/libsystemd/sd-future/test-fiber-io.c new file mode 100644 index 0000000000000..aef38950b6332 --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber-io.c @@ -0,0 +1,1388 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "fd-util.h" +#include "tests.h" +#include "time-util.h" + +/* Test: Basic pipe I/O with sd-event */ + +typedef struct PipeIOContext { + int *pipefd; + int order; +} PipeIOContext; + +static int pipe_read_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + n = sd_fiber_read(ctx->pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + /* Verify we read "hello" */ + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-read", pipe_read_fiber, &ctx, NULL, &f)); + + /* Write data to the pipe */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "hello", 5), 5); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify fiber read the data */ + ASSERT_OK_EQ(sd_future_result(f), 5); +} + +static int pipe_read_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + /* Record that the read fiber started before attempting the blocking read */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + n = sd_fiber_read(ctx->pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + /* After resuming, verify the write fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + /* Verify we read "hello" */ + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return (int) n; +} + +static int pipe_write_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + + /* Verify the read fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_write(ctx->pipefd[1], "hello", STRLEN("hello")); +} + +TEST(fiber_io_read_write) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + /* Higher priority for the read fiber, which will run first and then suspend because no data is + * available. The write fiber will run second, write data to the pipe, causing the read fiber to get + * resumed. */ + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-read", pipe_read_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "pipe-write", pipe_write_order_fiber, &ctx, NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full read->suspend->write->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 5); + ASSERT_OK_EQ(sd_future_result(fw), 5); +} + +/* Test: Multiple concurrent reads */ +static int concurrent_read_fiber(void *userdata) { + int *args = userdata; + int fd = args[0]; + int expected = args[1]; + char buf[64]; + ssize_t n; + + n = sd_fiber_read(fd, buf, sizeof buf); + if (n < 0) + return (int) n; + + if (n != 1 || buf[0] != (char) expected) + return -EIO; + + return 0; +} + +TEST(fiber_io_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 pipes and 3 fibers */ + int pipes[3][2]; + int args[3][2]; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + args[i][0] = pipes[i][0]; + args[i][1] = 'A' + i; + ASSERT_OK(sd_fiber_new(e, "concurrent-read", concurrent_read_fiber, args[i], NULL, &fibers[i])); + } + + /* Write data in reverse order */ + ASSERT_EQ(write(pipes[2][1], "C", 1), 1); + ASSERT_EQ(write(pipes[1][1], "B", 1), 1); + ASSERT_EQ(write(pipes[0][1], "A", 1), 1); + + /* Run until all complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK(sd_future_result(fibers[i])); + safe_close_pair(pipes[i]); + } +} + +/* Test: Cancel fiber during I/O */ +static int blocking_read_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[64]; + ssize_t n; + + n = sd_fiber_read(fd, buf, sizeof(buf)); + return (int) n; +} + +TEST(fiber_io_cancel) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-read", blocking_read_fiber, INT_TO_PTR(pipefd[0]), NULL, &f)); + + /* Run once - fiber will suspend on read */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Fiber should be suspended now - add explicit check via state tracking */ + + /* Cancel the fiber */ + ASSERT_OK(sd_future_cancel(f)); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Should be cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +TEST(fiber_io_fallback) { + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); /* Note: blocking pipe */ + + char buf[STRLEN("fallback")] = {}; + ASSERT_OK_EQ(sd_fiber_write(pipefd[1], "fallback", sizeof(buf)), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_read(pipefd[0], buf, sizeof(buf)), (ssize_t) sizeof(buf)); +} + +static int pipe_readv_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf1[5], buf2[5]; + struct iovec iov[] = { + { .iov_base = buf1, .iov_len = sizeof(buf1) }, + { .iov_base = buf2, .iov_len = sizeof(buf2) }, + }; + ssize_t n; + + /* Record that the read fiber started before attempting the blocking read */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* This will initially block since no data is available */ + n = sd_fiber_readv(ctx->pipefd[0], iov, ELEMENTSOF(iov)); + if (n < 0) + return (int) n; + + /* After resuming, verify the write fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + if (n != 10 || memcmp(buf1, "fiber", 5) != 0 || memcmp(buf2, "readv", 5) != 0) + return -EIO; + + return (int) n; +} + +static int pipe_writev_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + const char *part1 = "fiber"; + const char *part2 = "readv"; + struct iovec iov[] = { + { .iov_base = (void*) part1, .iov_len = 5 }, + { .iov_base = (void*) part2, .iov_len = 5 }, + }; + + /* Verify the read fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_writev(ctx->pipefd[1], iov, ELEMENTSOF(iov)); +} + +TEST(fiber_io_readv_writev) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + /* Higher priority for the read fiber, which will run first and then suspend because no data is + * available. The write fiber will run second, write data to the pipe, causing the read fiber to get + * resumed. */ + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-readv", pipe_readv_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "pipe-writev", pipe_writev_order_fiber, &ctx, NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full read->suspend->write->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 10); + ASSERT_OK_EQ(sd_future_result(fw), 10); +} + +static int concurrent_readv_fiber(void *userdata) { + int *args = userdata; + int fd = args[0]; + int expected1 = args[1]; + int expected2 = args[2]; + char buf1[1], buf2[1]; + struct iovec iov[] = { + { .iov_base = buf1, .iov_len = sizeof(buf1) }, + { .iov_base = buf2, .iov_len = sizeof(buf2) }, + }; + ssize_t n; + + n = sd_fiber_readv(fd, iov, ELEMENTSOF(iov)); + if (n < 0) + return (int) n; + + if (n != 2 || buf1[0] != (char) expected1 || buf2[0] != (char) expected2) + return -EIO; + + return 0; +} + +TEST(fiber_io_readv_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 pipes and 3 fibers */ + int pipes[3][2]; + int args[3][3]; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + args[i][0] = pipes[i][0]; + args[i][1] = 'A' + i; + args[i][2] = 'a' + i; + ASSERT_OK(sd_fiber_new(e, "concurrent-readv", concurrent_readv_fiber, args[i], NULL, &fibers[i])); + } + + /* Write data in reverse order */ + ASSERT_EQ(write(pipes[2][1], "Cc", 2), 2); + ASSERT_EQ(write(pipes[1][1], "Bb", 2), 2); + ASSERT_EQ(write(pipes[0][1], "Aa", 2), 2); + + /* Run until all complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK(sd_future_result(fibers[i])); + safe_close_pair(pipes[i]); + } +} + +typedef struct SocketIOContext { + int *sockfd; + int order; +} SocketIOContext; + +static int socket_send_order_fiber(void *userdata) { + SocketIOContext *ctx = ASSERT_PTR(userdata); + + /* Verify the recv fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_send(ctx->sockfd[0], "socket", STRLEN("socket"), 0); +} + +static int socket_recv_order_fiber(void *userdata) { + SocketIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + /* Record that the recv fiber started before attempting the blocking recv */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + n = sd_fiber_recv(ctx->sockfd[1], buf, sizeof(buf), 0); + if (n < 0) + return (int) n; + + /* After resuming, verify the send fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + /* Verify we received "socket" */ + if (n != 6 || memcmp(buf, "socket", 6) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recv_send) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + SocketIOContext ctx = { .sockfd = sockfd }; + + /* Higher priority for the recv fiber, which will run first and suspend */ + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recv", socket_recv_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "socket-send", socket_send_order_fiber, &ctx, NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 1)); + + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full recv->suspend->send->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 6); + ASSERT_OK_EQ(sd_future_result(fs), 6); +} + +static int socket_recv_peek_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf1[64], buf2[64]; + ssize_t n1, n2; + + /* First peek at the data */ + n1 = sd_fiber_recv(sockfd, buf1, sizeof(buf1), MSG_PEEK); + if (n1 < 0) + return (int) n1; + + /* Then actually read it */ + n2 = sd_fiber_recv(sockfd, buf2, sizeof(buf2), 0); + if (n2 < 0) + return (int) n2; + + /* Both should have read the same data */ + if (n1 != n2 || memcmp(buf1, buf2, n1) != 0) + return -EIO; + + if (n1 != 4 || memcmp(buf1, "peek", 4) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_recv_peek) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recv-peek", socket_recv_peek_fiber, INT_TO_PTR(sockfd[1]), NULL, &f)); + + /* Write data to the socket */ + ASSERT_OK_EQ_ERRNO(write(sockfd[0], "peek", 4), 4); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int socket_connect_fiber(void *userdata) { + struct sockaddr_un *addr = userdata; + _cleanup_close_ int sockfd = -EBADF; + + sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + return sd_fiber_connect(sockfd, (struct sockaddr*) addr, sizeof(*addr)); +} + +TEST(fiber_io_connect) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket with abstract namespace */ + _cleanup_close_ int listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(listen_fd); + + /* Use abstract socket (starts with null byte) */ + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-connect-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + /* Create fiber to connect */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-connect", socket_connect_fiber, &addr, NULL, &f)); + + /* Run the event loop - connection should complete */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int socket_sendmsg_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + struct iovec iov = { + .iov_base = (void*) "message", + .iov_len = STRLEN("message"), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + return sd_fiber_sendmsg(sockfd, &msg, 0); +} + +static int socket_recvmsg_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + ssize_t n; + + n = sd_fiber_recvmsg(sockfd, &msg, 0); + if (n < 0) + return (int) n; + + if (n != 7 || memcmp(buf, "message", 7) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recvmsg_sendmsg) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recvmsg", socket_recvmsg_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendmsg", socket_sendmsg_fiber, INT_TO_PTR(sockfd[0]), NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), 7); + ASSERT_OK_EQ(sd_future_result(fs), 7); +} + +static int socket_sendto_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + + /* For socketpair dgram sockets, we can use NULL address since they're connected */ + return sd_fiber_sendto(sockfd, "datagram", STRLEN("datagram"), 0, NULL, 0); +} + +static int socket_recvfrom_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + ssize_t n; + + n = sd_fiber_recvfrom(sockfd, buf, sizeof(buf), 0, + (struct sockaddr*) &addr, &addr_len); + if (n < 0) + return (int) n; + + if (n != 8 || memcmp(buf, "datagram", 8) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recvfrom_sendto) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recvfrom", socket_recvfrom_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendto", socket_sendto_fiber, INT_TO_PTR(sockfd[0]), NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), 8); + ASSERT_OK_EQ(sd_future_result(fs), 8); +} + +static int socket_sendmsg_fd_fiber(void *userdata) { + int *args = userdata; + int sockfd = args[0]; + int fd_to_send = args[1]; + struct iovec iov = { + .iov_base = (void*) "X", + .iov_len = 1, + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int)); + + return sd_fiber_sendmsg(sockfd, &msg, 0); +} + +static int socket_recvmsg_fd_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[1]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + int received_fd; + ssize_t n; + + n = sd_fiber_recvmsg(sockfd, &msg, 0); + if (n < 0) + return (int) n; + + if (n != 1 || buf[0] != 'X') + return -EIO; + + /* Extract the file descriptor */ + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) + return -EIO; + + memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); + + /* Verify we can use the fd */ + if (fcntl(received_fd, F_GETFD) < 0) + return -errno; + + close(received_fd); + return 0; +} + +TEST(fiber_io_sendmsg_recvmsg_fd) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + /* Create a test file descriptor to send */ + _cleanup_close_ int test_fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_OK_ERRNO(test_fd); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + int args[2] = { sockfd[0], test_fd }; + ASSERT_OK(sd_fiber_new(e, "socket-recvmsg-fd", socket_recvmsg_fd_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendmsg-fd", socket_sendmsg_fd_fiber, args, NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(fr)); + ASSERT_OK_EQ(sd_future_result(fs), 1); +} + +TEST(fiber_io_socket_fallback) { + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + char buf[STRLEN("fallback")] = {}; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockfd)); + + /* Test send/recv without fiber context */ + ASSERT_OK_EQ(sd_fiber_send(sockfd[0], "fallback", sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_recv(sockfd[1], buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + + /* Test sendto/recvfrom without fiber context */ + ASSERT_OK_EQ(sd_fiber_sendto(sockfd[0], "fallback", sizeof(buf), 0, NULL, 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_recvfrom(sockfd[1], buf, sizeof(buf), 0, NULL, NULL), (ssize_t) sizeof(buf)); +} + +static int blocking_recv_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + + return sd_fiber_recv(sockfd, buf, sizeof(buf), 0); +} + +TEST(fiber_io_socket_cancel) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-recv", blocking_recv_fiber, INT_TO_PTR(sockfd[0]), NULL, &f)); + + /* Run once - fiber will suspend on recv */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Cancel the fiber */ + ASSERT_OK(sd_future_cancel(f)); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Should be cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +/* Test: Basic accept operation */ +static int accept_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + int client_fd; + + client_fd = sd_fiber_accept(listen_fd, (struct sockaddr*) &addr, &addr_len, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + close(client_fd); + return 0; +} + +TEST(fiber_io_accept_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket with abstract namespace */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK_ERRNO(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-%d", getpid()); + + ASSERT_OK_ERRNO(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK_ERRNO(listen(listen_fd, 1)); + + /* Create fiber to accept connection */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept", accept_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect from outside fiber context */ + _cleanup_close_ int connect_fd = -EBADF; + ASSERT_OK(connect_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + ASSERT_OK(connect(connect_fd, (struct sockaddr*) &addr, sizeof(addr))); + + /* Run the event loop - accept should complete */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: Multiple sequential accepts */ +static int accept_multiple_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + struct sockaddr_un addr; + socklen_t addr_len; + int count = 0; + + for (int i = 0; i < 3; i++) { + _cleanup_close_ int client_fd = -EBADF; + + addr_len = sizeof(addr); + client_fd = sd_fiber_accept(listen_fd, (struct sockaddr*) &addr, &addr_len, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + count++; + } + + return count; +} + +TEST(fiber_io_accept_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-multi-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 5)); + + /* Create fiber to accept multiple connections */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept-multiple", accept_multiple_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect multiple times */ + int connect_fds[3] = { -EBADF, -EBADF, -EBADF }; + for (size_t i = 0; i < 3; i++) { + connect_fds[i] = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(connect_fds[i]); + ASSERT_OK(connect(connect_fds[i], (struct sockaddr*) &addr, sizeof(addr))); + } + + /* Run the event loop */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(f), 3); + + /* Clean up connection fds */ + for (size_t i = 0; i < 3; i++) + safe_close(connect_fds[i]); +} + +/* Test: Accept and exchange data */ +static int accept_and_read_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + _cleanup_close_ int client_fd = -EBADF; + char buf[64]; + ssize_t n; + + client_fd = sd_fiber_accept(listen_fd, NULL, NULL, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + n = sd_fiber_read(client_fd, buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_accept_and_read) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-read-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + /* Create fiber to accept and read */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept-and-read", accept_and_read_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect and send data */ + _cleanup_close_ int connect_fd = -EBADF; + ASSERT_OK(connect_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + ASSERT_OK(connect(connect_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK_EQ_ERRNO(write(connect_fd, "hello", 5), 5); + + /* Run the event loop */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with single fd ready immediately */ +static int poll_immediate_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Should have one fd ready */ + if (r != 1) + return -EIO; + + if (!(fds[0].revents & POLLIN)) + return -EIO; + + return 0; +} + +TEST(fiber_poll_immediate) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before creating fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "X", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-immediate", poll_immediate_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with fd that becomes ready after suspension */ +static int poll_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLIN)) + return -EIO; + + /* Read the data */ + char buf[1]; + if (read(pipefd[0], buf, 1) != 1 || buf[0] != 'Y') + return -EIO; + + return 0; +} + +TEST(fiber_poll) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-suspend", poll_fiber, pipefd, NULL, &f)); + + /* Run once - fiber will suspend on poll */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write data to wake it up */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Y", 1), 1); + + /* Complete execution */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with multiple fds */ +static int poll_multiple_fiber(void *userdata) { + int (*pipes)[2] = userdata; + struct pollfd fds[] = { + { .fd = pipes[0][0], .events = POLLIN }, + { .fd = pipes[1][0], .events = POLLIN }, + { .fd = pipes[2][0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Should have all three ready */ + if (r != 3) + return -EIO; + + for (size_t i = 0; i < 3; i++) { + if (!(fds[i].revents & POLLIN)) + return -EIO; + + char buf[1]; + if (read(fds[i].fd, buf, 1) != 1 || buf[0] != (char) ('A' + i)) + return -EIO; + } + + return 0; +} + +TEST(fiber_poll_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create three pipes */ + int pipes[3][2]; + for (size_t i = 0; i < 3; i++) + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-multiple", poll_multiple_fiber, pipes, NULL, &f)); + + /* Run once - fiber will suspend waiting for data */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write to all three pipes in different order */ + ASSERT_OK_EQ_ERRNO(write(pipes[2][1], "C", 1), 1); + ASSERT_OK_EQ_ERRNO(write(pipes[0][1], "A", 1), 1); + ASSERT_OK_EQ_ERRNO(write(pipes[1][1], "B", 1), 1); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + for (size_t i = 0; i < 3; i++) + safe_close_pair(pipes[i]); +} + +/* Test: poll with POLLOUT (write readiness) */ +static int poll_pollout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[1], .events = POLLOUT }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLOUT)) + return -EIO; + + /* Pipe should be writable */ + if (write(pipefd[1], "Z", 1) != 1) + return -errno; + + return 0; +} + +TEST(fiber_poll_pollout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-pollout", poll_pollout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify data was written */ + char buf[1]; + ASSERT_OK_EQ_ERRNO(read(pipefd[0], buf, 1), 1); + ASSERT_EQ(buf[0], 'Z'); +} + +/* Test: poll with timeout that expires */ +static int poll_timeout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + /* Poll with 100ms timeout - no data will arrive */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), &(struct timespec) { .tv_nsec = 100 * NSEC_PER_MSEC }, NULL); + if (r < 0) + return r; + + /* Should timeout with no fds ready */ + if (r != 0) + return -EIO; + + return 0; +} + +TEST(fiber_poll_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-timeout", poll_timeout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with zero timeout (should not block) */ +static int poll_zero_timeout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + /* Poll with zero timeout - should return immediately */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), &(struct timespec) {}, NULL); + if (r < 0) + return r; + + /* No data available, so should return 0 */ + if (r != 0) + return -EIO; + + /* Now write data */ + if (write(pipefd[1], "Q", 1) != 1) + return -errno; + + /* Poll again with zero timeout - should see data */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLIN)) + return -EIO; + + return 0; +} + +TEST(fiber_poll_zero_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-timeout", poll_zero_timeout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with zero fds and zero timeout (should return immediately) */ +static int poll_zero_fds_fiber(void *userdata) { + return sd_fiber_ppoll(NULL, 0, &(struct timespec) {}, NULL); +} + +TEST(fiber_poll_zero_fds) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-fds", poll_zero_fds_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(f), 0); +} + +/* Test: poll with zero fds and no timeout has no possible wakeup, must reject with -EINVAL */ +static int poll_zero_fds_no_timeout_fiber(void *userdata) { + return sd_fiber_ppoll(NULL, 0, NULL, NULL); +} + +TEST(fiber_poll_zero_fds_no_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-fds-no-timeout", poll_zero_fds_no_timeout_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EINVAL); +} + +/* Test: poll with negative fd (should be ignored) */ +static int poll_negative_fd_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = -1, .events = POLLIN }, + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Only the second fd should be ready */ + if (r != 1 || !(fds[1].revents & POLLIN)) + return -EIO; + + /* First fd should have no events */ + if (fds[0].revents != 0) + return -EIO; + + return 0; +} + +TEST(fiber_poll_negative_fd) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before creating fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "N", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-negative-fd", poll_negative_fd_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: Multiple fibers waiting on the same fd */ +typedef struct SharedFdArgs { + int pipefd; + int *counter; +} SharedFdArgs; + +static int shared_fd_read_fiber(void *userdata) { + SharedFdArgs *args = ASSERT_PTR(userdata); + char buf[1]; + ssize_t n; + + n = sd_fiber_read(args->pipefd, buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if (n != 1) + return -EIO; + + /* Increment counter to track successful reads */ + (*args->counter)++; + + return 0; +} + +TEST(fiber_io_same_fd_multiple_fibers) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Create 3 fibers all waiting on the same pipe read end */ + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + SharedFdArgs args[3]; + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + args[i].pipefd = pipefd[0]; + args[i].counter = &counter; + ASSERT_OK(sd_fiber_new(e, "shared-fd-read", shared_fd_read_fiber, &args[i], NULL, &fibers[i])); + } + + /* All fibers should suspend waiting for data */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write 3 bytes - each byte will wake one fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "ABC", 3), 3); + + /* Run until all fibers complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully and each should have read one byte */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_future_result(fibers[i])); + + ASSERT_EQ(counter, 3); +} + +static int blocking_fd_preserve_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + char buf[8] = {}; + ssize_t n; + + /* The pipe has data pre-filled, so this should succeed immediately on the fast path. + * This exercises the fd blocking state restore: fiber_io_operation() temporarily sets the fd + * to nonblocking, and must restore it to blocking on the success path. */ + n = sd_fiber_read(pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if ((size_t) n != sizeof(buf) || memcmp(buf, "blocking", sizeof(buf)) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_blocking_fd_preserved) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create a blocking pipe (no O_NONBLOCK) */ + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + /* Pre-fill the pipe so the read will succeed immediately */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "blocking", 8), 8); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-fd-preserve", blocking_fd_preserve_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify the read end is still in blocking mode after the fiber completed */ + ASSERT_OK_ZERO(fd_nonblock(pipefd[0], false)); +} + +static int socket_connect_blocking_fiber(void *userdata) { + struct sockaddr_un *addr = userdata; + _cleanup_close_ int sockfd = -EBADF; + + /* Use a blocking socket (no SOCK_NONBLOCK). sd_fiber_connect() should temporarily set it + * to nonblocking, handle the EINPROGRESS path with getsockopt(SO_ERROR), and restore + * the blocking state. */ + sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sockfd < 0) + return -errno; + + int r = sd_fiber_connect(sockfd, (struct sockaddr*) addr, sizeof(*addr)); + if (r < 0) + return r; + + /* Verify the socket is back in blocking mode */ + r = fd_nonblock(sockfd, false); + if (r < 0) + return r; + if (r > 0) + return -EBUSY; /* fd was nonblocking, but should have been restored to blocking */ + + return 0; +} + +TEST(fiber_io_connect_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(listen_fd); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-connect-blocking-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "connect-blocking", socket_connect_blocking_fiber, &addr, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/systemd/sd-future.h b/src/systemd/sd-future.h index 9d0d03acf48a7..5e0fa22525615 100644 --- a/src/systemd/sd-future.h +++ b/src/systemd/sd-future.h @@ -17,10 +17,18 @@ along with systemd; If not, see . ***/ +#include + #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; +struct iovec; +struct pollfd; +struct sockaddr; +struct msghdr; +struct timespec; + typedef struct sd_event sd_event; typedef struct sd_future sd_future; typedef struct sd_future_ops sd_future_ops; @@ -96,6 +104,23 @@ sd_future* sd_fiber_timeout(uint64_t timeout); _SD_CONCATENATE(_sd_fto_b_, uniq); \ _SD_CONCATENATE(_sd_fto_b_, uniq) = NULL) +/* Fiber I/O operations - use sd-event for non-blocking I/O when in fiber context */ +ssize_t sd_fiber_read(int fd, void *buf, size_t count); +ssize_t sd_fiber_write(int fd, const void *buf, size_t count); +ssize_t sd_fiber_readv(int fd, const struct iovec *iov, int iovcnt); +ssize_t sd_fiber_writev(int fd, const struct iovec *iov, int iovcnt); +ssize_t sd_fiber_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t sd_fiber_send(int sockfd, const void *buf, size_t len, int flags); +int sd_fiber_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +ssize_t sd_fiber_recvmsg(int sockfd, struct msghdr *msg, int flags); +ssize_t sd_fiber_sendmsg(int sockfd, const struct msghdr *msg, int flags); +ssize_t sd_fiber_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); +ssize_t sd_fiber_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); +int sd_fiber_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); +#ifndef __STRICT_ANSI__ +int sd_fiber_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); +#endif + _SD_END_DECLARATIONS; #endif From 7bc793e21f2d4bf67bd311545270bc515fe63ad9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 25 Apr 2026 22:06:54 +0200 Subject: [PATCH 2083/2155] sd-future: make src/basic blocking helpers fiber-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some helpers in src/basic — ppoll_usec_full() (used by fd_wait_for_event()), loop_read(), loop_read_exact(), loop_write_full() and pidref_wait_for_terminate_full() — block the calling thread. That's the right behaviour outside a fiber but not inside one, where blocking the thread also stalls every other fiber running on the same event loop. Rewriting every caller to pick a fiber or non-fiber variant explicitly would be a lot of churn and would split otherwise-shared code paths in two. Instead, the helpers detect at runtime whether they're running on a fiber and dispatch to a suspending variant when they are. FiberOps in fiber-ops.h holds five function pointers (ppoll, read, write, timeout, cancel_wait_unref); a fiber_ops global constant is populated whenever we enter a fiber with functions that delegate to suspending variants of common syscalls. With this approach, the variants themselves stay in libsystemd which is required because they make use of sd-event. - loop_read()/loop_read_exact() take the fiber read hook on a fiber unless the caller asked for a non-blocking attempt (do_poll=false) and the fd is already non-blocking — in that case we fall through to read() to preserve the existing return-EAGAIN-immediately semantic. The hook itself suspends on EAGAIN until data is available, so neither the do_poll knob nor the explicit fd_wait_for_event() retry loop are needed on the fiber path. - loop_write_full() likewise takes the fiber write hook on a fiber, except when timeout=0 with an already-non-blocking fd (preserving the fast-return-EAGAIN semantic). The fiber path runs inside a FIBER_OPS_TIMEOUT() scope so the caller's timeout is honoured via a deadline future, mirroring SD_FIBER_TIMEOUT() but reachable from src/basic without pulling in sd-future.h. - pidref_wait_for_terminate_full() polls the pidfd via fd_wait_for_event() before each waitid() when either a finite timeout is set or we're on a fiber, and requires pidref->fd >= 0 in those cases (returning -ENOMEDIUM otherwise — extending the rule that already applied to finite timeouts). The poll suspends the fiber via the ppoll hook above; the subsequent waitid() doesn't block because the pidfd is already signalled. --- src/basic/basic-forward.h | 1 + src/basic/fiber-ops.c | 51 ++ src/basic/fiber-ops.h | 34 ++ src/basic/io-util.c | 90 +++- src/basic/meson.build | 1 + src/basic/pidref.c | 23 +- src/libsystemd/meson.build | 1 + src/libsystemd/sd-future/fiber.c | 17 +- src/libsystemd/sd-future/test-fiber-ops.c | 574 ++++++++++++++++++++++ 9 files changed, 766 insertions(+), 26 deletions(-) create mode 100644 src/basic/fiber-ops.c create mode 100644 src/basic/fiber-ops.h create mode 100644 src/libsystemd/sd-future/test-fiber-ops.c diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 5f9109cb17c1e..6fa9f3bf868de 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -112,6 +112,7 @@ typedef enum UnitType UnitType; typedef enum WaitFlags WaitFlags; typedef struct Fiber Fiber; +typedef struct FiberOps FiberOps; typedef struct Hashmap Hashmap; typedef struct HashmapBase HashmapBase; typedef struct IteratedCache IteratedCache; diff --git a/src/basic/fiber-ops.c b/src/basic/fiber-ops.c new file mode 100644 index 0000000000000..330630f9dc636 --- /dev/null +++ b/src/basic/fiber-ops.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include +#include + +#include "errno-util.h" +#include "fiber-ops.h" + +static thread_local const FiberOps *fiber_ops = NULL; + +bool fiber_ops_is_set(void) { + return fiber_ops != NULL; +} + +void fiber_ops_set(const FiberOps *ops) { + fiber_ops = ops; +} + +int fiber_ops_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask) { + if (fiber_ops) + return fiber_ops->ppoll(fds, n_fds, timeout, sigmask); + + return RET_NERRNO(ppoll(fds, n_fds, timeout, sigmask)); +} + +ssize_t fiber_ops_read(int fd, void *buf, size_t count) { + if (fiber_ops) + return fiber_ops->read(fd, buf, count); + + ssize_t n = read(fd, buf, count); + return n < 0 ? -errno : n; +} + +ssize_t fiber_ops_write(int fd, const void *buf, size_t count) { + if (fiber_ops) + return fiber_ops->write(fd, buf, count); + + return RET_NERRNO(write(fd, buf, count)); +} + +sd_future* fiber_ops_timeout(uint64_t timeout) { + assert(fiber_ops); + + return fiber_ops->timeout(timeout); +} + +sd_future* fiber_ops_cancel_wait_unref(sd_future *f) { + assert(fiber_ops); + + return fiber_ops->cancel_wait_unref(f); +} diff --git a/src/basic/fiber-ops.h b/src/basic/fiber-ops.h new file mode 100644 index 0000000000000..64bd82353ba16 --- /dev/null +++ b/src/basic/fiber-ops.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "basic-forward.h" + +typedef struct sd_future sd_future; + +/* Hooks installed on a fiber so that functions in src/basic can transparently defer to the suspending + * variants in sd-future when invoked from a running fiber. Populated by sd_fiber_new() with pointers to the + * implementations in fiber-ops.c. */ +typedef struct FiberOps { + int (*ppoll)(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); + ssize_t (*read)(int fd, void *buf, size_t count); + ssize_t (*write)(int fd, const void *buf, size_t count); + sd_future* (*timeout)(uint64_t timeout); + sd_future* (*cancel_wait_unref)(sd_future *f); +} FiberOps; + +bool fiber_ops_is_set(void); +void fiber_ops_set(const FiberOps *fiber_ops); + +int fiber_ops_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); +ssize_t fiber_ops_read(int fd, void *buf, size_t count); +ssize_t fiber_ops_write(int fd, const void *buf, size_t count); + +/* Mirror of SD_FIBER_TIMEOUT() for code under src/basic that doesn't include sd-future.h: dispatches + * through FiberOps so the actual sd_fiber_timeout() implementation lives in libsystemd. */ +sd_future* fiber_ops_timeout(uint64_t timeout); +sd_future* fiber_ops_cancel_wait_unref(sd_future *f); +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_future*, fiber_ops_cancel_wait_unref); + +#define FIBER_OPS_TIMEOUT(timeout) _FIBER_OPS_TIMEOUT(UNIQ, (timeout)) +#define _FIBER_OPS_TIMEOUT(uniq, timeout) \ + _unused_ _cleanup_(fiber_ops_cancel_wait_unrefp) sd_future *UNIQ_T(_fot_, uniq) = fiber_ops_timeout(timeout) diff --git a/src/basic/io-util.c b/src/basic/io-util.c index b4f643b5721aa..3dbc3670a4a85 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include @@ -8,6 +9,7 @@ #include #include "errno-util.h" +#include "fiber-ops.h" #include "io-util.h" #include "time-util.h" @@ -69,25 +71,44 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { if (nbytes > (size_t) SSIZE_MAX) return -EINVAL; + /* do_poll == false means "don't wait, return what we have if EAGAIN". If the fd is already + * non-blocking, read() can't block the thread, so the non-fiber path satisfies that semantic + * correctly even from a fiber. Only use the fiber path when the fd is blocking (where read() + * would otherwise block the entire event loop). */ + int flags = 0; + if (fiber_ops_is_set() && !do_poll) { + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + } + do { ssize_t k; - k = read(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ - - (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; + if (fiber_ops_is_set() && (do_poll || !FLAGS_SET(flags, O_NONBLOCK))) { + /* On a fiber the read op suspends on EAGAIN until data is available, so we don't + * need a separate poll step or the do_poll knob. */ + k = fiber_ops_read(fd, p, nbytes); + if (k < 0) + return n > 0 ? n : k; + } else { + k = read(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; } - - return n > 0 ? n : -errno; } if (k == 0) @@ -137,6 +158,37 @@ int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) { p = buf; } + /* timeout == 0 means "don't wait, return -EAGAIN if not ready". If the fd is already + * non-blocking, write() can't block the thread, so the non-fiber path satisfies that + * semantic correctly even from a fiber. Only use the fiber path when the fd is blocking + * (where write() would otherwise block the entire event loop). */ + int flags = 0; + if (fiber_ops_is_set() && timeout == 0) { + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + } + + if (fiber_ops_is_set() && !FLAGS_SET(flags, O_NONBLOCK)) { + /* On a fiber the write op suspends on EAGAIN until the fd is writable; honor the + * caller's timeout via a deadline scope. */ + FIBER_OPS_TIMEOUT(timestamp_is_set(timeout) ? timeout : USEC_INFINITY); + + while (nbytes > 0) { + ssize_t k = fiber_ops_write(fd, p, nbytes); + if (k < 0) + return (int) k; + if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ + return -EIO; + + assert((size_t) k <= nbytes); + p += k; + nbytes -= k; + } + + return 0; + } + /* When timeout is 0 or USEC_INFINITY this is not used. But we initialize it to a sensible value. */ end = timestamp_is_set(timeout) ? usec_add(now(CLOCK_MONOTONIC), timeout) : USEC_INFINITY; @@ -220,11 +272,9 @@ int ppoll_usec_full(struct pollfd *fds, size_t n_fds, usec_t timeout, const sigs if (n_fds == 0 && timeout == 0) return 0; - r = ppoll(fds, n_fds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), ss); - if (r < 0) - return -errno; - if (r == 0) - return 0; + r = fiber_ops_ppoll(fds, n_fds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), ss); + if (r <= 0) + return r; for (size_t i = 0, n = r; i < n_fds && n > 0; i++) { if (fds[i].revents == 0) diff --git a/src/basic/meson.build b/src/basic/meson.build index 7cc62c7b8bdac..bae72fbcb27d1 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -36,6 +36,7 @@ basic_sources = files( 'ether-addr-util.c', 'extract-word.c', 'fd-util.c', + 'fiber-ops.c', 'fileio.c', 'filesystems.c', 'format-ifname.c', diff --git a/src/basic/pidref.c b/src/basic/pidref.c index 10ff9a63b12bc..33131c0586d12 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fiber-ops.h" #include "format-util.h" #include "hash-funcs.h" #include "io-util.h" @@ -466,16 +467,28 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re if (pidref->pid == 1 || pidref_is_self(pidref)) return -ECHILD; - if (timeout != USEC_INFINITY && pidref->fd < 0) + if (pidref->fd < 0 && (timeout != USEC_INFINITY || fiber_ops_is_set())) return -ENOMEDIUM; usec_t ts = timeout == USEC_INFINITY ? USEC_INFINITY : usec_add(now(CLOCK_MONOTONIC), timeout); + /* Poll the pidfd before waitid() if either there's a finite timeout (so we can honor it) or + * we're on a fiber (so fd_wait_for_event() can suspend us instead of blocking the event loop + * inside waitid()). Otherwise let waitid() block directly. The precondition above guarantees + * pidref->fd >= 0 in both cases. */ + bool poll_first = ts != USEC_INFINITY || fiber_ops_is_set(); + for (;;) { - if (ts != USEC_INFINITY) { - usec_t left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC)); - if (left == 0) - return -ETIMEDOUT; + if (poll_first) { + usec_t left; + + if (ts == USEC_INFINITY) + left = USEC_INFINITY; + else { + left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC)); + if (left == 0) + return -ETIMEDOUT; + } r = fd_wait_for_event(pidref->fd, POLLIN, left); if (r == 0) diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 3c624051f0ded..91a78155d2d3b 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -192,6 +192,7 @@ simple_tests += files( 'sd-device/test-sd-device-monitor.c', 'sd-future/test-fiber.c', 'sd-future/test-fiber-io.c', + 'sd-future/test-fiber-ops.c', 'sd-hwdb/test-sd-hwdb.c', 'sd-id128/test-id128.c', 'sd-journal/test-audit-type.c', diff --git a/src/libsystemd/sd-future/fiber.c b/src/libsystemd/sd-future/fiber.c index 48d26a27dd2c7..64dee8411df22 100644 --- a/src/libsystemd/sd-future/fiber.c +++ b/src/libsystemd/sd-future/fiber.c @@ -20,6 +20,7 @@ #include "architecture.h" #include "errno-util.h" #include "event-future.h" +#include "fiber-ops.h" #include "log-context.h" #include "log.h" #include "memory-util.h" @@ -270,8 +271,10 @@ static void reset_current_fiber(void) { * current_fiber. Without this, the child of a fork() that happened mid-fiber would inherit the * fiber's log prefix / context list in its thread-locals even though no fiber is running. */ Fiber *f = fiber_get_current(); - if (f) + if (f) { fiber_swap_log_state(f); + fiber_ops_set(NULL); + } fiber_set_current(NULL); } @@ -307,9 +310,19 @@ static void fiber_resolve(sd_future *f) { sd_future_resolve(f, fiber->result); } +static const FiberOps fiber_ops = { + .ppoll = sd_fiber_ppoll, + .read = sd_fiber_read, + .write = sd_fiber_write, + .timeout = sd_fiber_timeout, + .cancel_wait_unref = sd_future_cancel_wait_unref, +}; + static void fiber_enter(Fiber *fiber, Fiber *prev, void **fake_stack_save) { fiber_set_current(fiber); fiber_swap_log_state(fiber); + if (!prev) + fiber_ops_set(&fiber_ops); struct iovec fiber_stack = fiber_stack_usable(&fiber->stack); start_switch_stack(fake_stack_save, &fiber_stack); @@ -318,6 +331,8 @@ static void fiber_enter(Fiber *fiber, Fiber *prev, void **fake_stack_save) { static void fiber_leave(Fiber *fiber, Fiber *prev, void *fake_stack_save) { finish_switch_stack(fake_stack_save); + if (!prev) + fiber_ops_set(NULL); fiber_swap_log_state(fiber); fiber_set_current(prev); } diff --git a/src/libsystemd/sd-future/test-fiber-ops.c b/src/libsystemd/sd-future/test-fiber-ops.c new file mode 100644 index 0000000000000..f2c603fda0b87 --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber-ops.c @@ -0,0 +1,574 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "cleanup-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "pidref.h" +#include "process-util.h" +#include "tests.h" +#include "time-util.h" + +/* Test: wait_for_terminate basic functionality */ +static int wait_simple_fiber(void *userdata) { + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + /* Fork a child that exits immediately */ + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref); + if (r < 0) + return r; + + if (r == 0) + _exit(42); + + /* Parent - wait for child */ + r = pidref_wait_for_terminate(&pidref, &si); + if (r < 0) + return r; + + pidref_done(&pidref); + + /* Verify child exited with status 42 */ + if (si.si_code != CLD_EXITED || si.si_status != 42) + return -EIO; + + return 0; +} + +TEST(wait_for_terminate_fiber_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "wait-simple", wait_simple_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: wait_for_terminate with multiple children */ +static int wait_multiple_fiber(void *userdata) { + PidRef pidrefs[3] = { PIDREF_NULL, PIDREF_NULL, PIDREF_NULL }; + siginfo_t si; + int r; + + /* Fork three children with different exit codes */ + for (size_t i = 0; i < 3; i++) { + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidrefs[i]); + if (r < 0) + goto cleanup; + + if (r == 0) + /* Child process */ + _exit(10 + i); + } + + /* Wait for all three in order */ + for (size_t i = 0; i < 3; i++) { + r = pidref_wait_for_terminate(&pidrefs[i], &si); + if (r < 0) + goto cleanup; + + pidref_done(&pidrefs[i]); + + if (si.si_code != CLD_EXITED || si.si_status != (int) (10 + i)) { + r = -EIO; + goto cleanup; + } + } + + return 0; + +cleanup: + for (size_t i = 0; i < 3; i++) + pidref_done(&pidrefs[i]); + + return r; +} + +TEST(wait_for_terminate_fiber_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "wait-multiple", wait_multiple_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int concurrent_wait_fiber(void *userdata) { + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref); + if (r < 0) + return r; + + if (r == 0) + /* Child exits with specified status */ + _exit(PTR_TO_INT(userdata)); + + r = pidref_wait_for_terminate(&pidref, &si); + if (r < 0) + return r; + + pidref_done(&pidref); + + if (si.si_code != CLD_EXITED || si.si_status != PTR_TO_INT(userdata)) + return -EIO; + + return 0; +} + +TEST(wait_for_terminate_fiber_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 fibers, each waiting for a different child */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "concurrent-wait", concurrent_wait_fiber, INT_TO_PTR(20 + i), /* destroy= */ NULL, &fibers[i])); + + ASSERT_OK(sd_event_loop(e)); + + /* All fibers should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_future_result(fibers[i])); +} + +typedef struct LoopIOContext { + int *pipefd; + const char *data; + size_t len; + int order; +} LoopIOContext; + +static int loop_read_suspend_fiber(void *userdata) { + LoopIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + ssize_t n = loop_read(ctx->pipefd[0], buf, sizeof(buf), /* do_poll= */ true); + + /* While we were suspended, the writer fiber should have run. */ + ASSERT_EQ(ctx->order, 2); + + if (n < 0) + return (int) n; + if ((size_t) n != ctx->len || memcmp(buf, ctx->data, ctx->len) != 0) + return -EIO; + + return (int) n; +} + +static int loop_write_suspend_fiber(void *userdata) { + LoopIOContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + int r = loop_write(ctx->pipefd[1], ctx->data, ctx->len); + if (r < 0) + return r; + + /* Close the write end so the reader sees EOF after reading the data. */ + ctx->pipefd[1] = safe_close(ctx->pipefd[1]); + return 0; +} + +/* Test: two fibers cooperatively pass a small payload through a blocking pipe using the suspending + * loop helpers. Exercises the non-blocking flip, event-loop yielding, and the blocking-mode restore. */ +TEST(loop_read_write_suspend) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + static const char payload[] = "loop-suspend"; + LoopIOContext ctx = { + .pipefd = pipefd, + .data = payload, + .len = sizeof(payload) - 1, + }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read", loop_read_suspend_fiber, &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-write", loop_write_suspend_fiber, &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.len); + ASSERT_OK_ZERO(sd_future_result(fw)); + + /* The read fd started out blocking and loop_read() must have restored it before returning. */ + ASSERT_OK_ZERO(fcntl(pipefd[0], F_GETFL) & O_NONBLOCK); +} + +static int loop_read_exact_short_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[16]; + + /* Requesting more bytes than the peer writes should return -EIO once EOF is hit. */ + return loop_read_exact(fd, buf, sizeof(buf), /* do_poll= */ true); +} + +/* Test: loop_read_exact() returns -EIO when the peer closes early. */ +TEST(loop_read_exact_short) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-exact", loop_read_exact_short_fiber, + INT_TO_PTR(pipefd[0]), /* destroy= */ NULL, &f)); + + /* Write a few bytes and close the write end — less than the fiber asked for. */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "abc", 3), (ssize_t) 3); + pipefd[1] = safe_close(pipefd[1]); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_ERROR(sd_future_result(f), EIO); +} + +typedef struct LoopWriteTimeoutContext { + int fd; + int result; +} LoopWriteTimeoutContext; + +static int loop_write_timeout_fiber(void *userdata) { + LoopWriteTimeoutContext *ctx = ASSERT_PTR(userdata); + + /* Try to write much more than the pipe buffer can hold with a short timeout. The write will + * succeed partially and then hit -ETIME after exhausting the timeout while blocked. */ + static const char big_buf[128 * 1024] = { 0 }; + ctx->result = loop_write_full(ctx->fd, big_buf, sizeof(big_buf), 100 * USEC_PER_MSEC); + return 0; +} + +/* Test: loop_write_full() returns -ETIME when the peer never drains. */ +TEST(loop_write_full_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + /* Shrink the pipe buffer to its minimum (one page) so the 128K write below is guaranteed to block + * regardless of the architecture's page size. The default pipe buffer is 16 pages, which on + * 64K-page architectures (e.g. ppc64le) is 1 MiB — enough to absorb the entire write without ever + * blocking, defeating the purpose of the timeout. */ + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + LoopWriteTimeoutContext ctx = { .fd = pipefd[1], .result = 0 }; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-timeout", loop_write_timeout_fiber, &ctx, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_ZERO(sd_future_result(f)); + ASSERT_ERROR(ctx.result, ETIME); +} + +typedef struct PpollDispatchContext { + int *pipefd; + int order; +} PpollDispatchContext; + +static int ppoll_dispatch_read_fiber(void *userdata) { + PpollDispatchContext *ctx = ASSERT_PTR(userdata); + struct pollfd pfd = { + .fd = ctx->pipefd[0], + .events = POLLIN, + }; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* Direct ppoll_usec() call from a fiber must dispatch through sd_fiber_poll(), suspending the + * fiber instead of blocking the entire thread. If dispatch fails, the writer fiber never gets a + * chance to run and the test deadlocks. */ + int r = ppoll_usec(&pfd, 1, USEC_INFINITY); + if (r < 0) + return r; + + ASSERT_EQ(ctx->order, 2); + + if (r != 1 || !FLAGS_SET(pfd.revents, POLLIN)) + return -EIO; + + return 0; +} + +static int ppoll_dispatch_write_fiber(void *userdata) { + PpollDispatchContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + if (write(ctx->pipefd[1], "x", 1) < 0) + return -errno; + + return 0; +} + +/* Test: ppoll_usec() called from a fiber dispatches through the FiberOps hook to sd_fiber_poll(), + * yielding to the event loop instead of blocking. */ +TEST(ppoll_usec_dispatch) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PpollDispatchContext ctx = { .pipefd = pipefd }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "ppoll-read", ppoll_dispatch_read_fiber, &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "ppoll-write", ppoll_dispatch_write_fiber, &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(fr)); + ASSERT_OK(sd_future_result(fw)); +} + +static int loop_write_zero_timeout_nonblock_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + + /* Fill the pipe so the next write would block. The fd is non-blocking, so on a fiber + * loop_write_full(timeout=0) must take the non-fiber path and return -EAGAIN immediately + * rather than suspending. */ + static const char big_buf[128 * 1024] = { 0 }; + return loop_write_full(fd, big_buf, sizeof(big_buf), /* timeout= */ 0); +} + +/* Test: timeout == 0 on a non-blocking fd from a fiber preserves the "don't wait" semantic and + * returns -EAGAIN when the pipe buffer is full, instead of suspending the fiber. */ +TEST(loop_write_zero_timeout_nonblock) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-zt-nb", loop_write_zero_timeout_nonblock_fiber, + INT_TO_PTR(pipefd[1]), /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EAGAIN); +} + +typedef struct LoopWriteZeroBlockingContext { + int *pipefd; + size_t total; + int order; +} LoopWriteZeroBlockingContext; + +static int loop_write_zero_blocking_writer_fiber(void *userdata) { + LoopWriteZeroBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* timeout == 0 on a *blocking* fd from a fiber: the fast EAGAIN return isn't possible, so + * loop_write_full() takes the fiber path. The reader fiber drains the pipe, letting our + * write complete via fiber suspension/resume. */ + _cleanup_free_ char *big_buf = malloc0(ctx->total); + ASSERT_NOT_NULL(big_buf); + int r = loop_write_full(ctx->pipefd[1], big_buf, ctx->total, /* timeout= */ 0); + + ASSERT_EQ(ctx->order, 2); + return r; +} + +static int loop_write_zero_blocking_reader_fiber(void *userdata) { + LoopWriteZeroBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + _cleanup_free_ char *buf = malloc(ctx->total); + ASSERT_NOT_NULL(buf); + ssize_t n = loop_read(ctx->pipefd[0], buf, ctx->total, /* do_poll= */ true); + if (n < 0) + return (int) n; + return (int) n; +} + +/* Test: timeout == 0 on a blocking fd from a fiber takes the fiber path (suspends until the peer + * drains) instead of blocking the entire thread. */ +TEST(loop_write_zero_timeout_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + /* F_SETPIPE_SZ rounds up to the kernel's pipe minimum (typically a page); query the actual + * size and write more than that, so the write must wait on the reader regardless of page size. */ + int pipe_sz = fcntl(pipefd[1], F_GETPIPE_SZ); + ASSERT_OK_ERRNO(pipe_sz); + + LoopWriteZeroBlockingContext ctx = { .pipefd = pipefd, .total = (size_t) pipe_sz * 2 }; + + _cleanup_(sd_future_unrefp) sd_future *fw = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-zt-blk", loop_write_zero_blocking_writer_fiber, + &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-read-zt-blk", loop_write_zero_blocking_reader_fiber, + &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(fw)); + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.total); +} + +static int loop_read_no_poll_nonblock_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[64]; + + /* Empty non-blocking pipe + do_poll=false: on a fiber loop_read() must take the non-fiber + * path and return -EAGAIN immediately rather than suspending. */ + return (int) loop_read(fd, buf, sizeof(buf), /* do_poll= */ false); +} + +/* Test: do_poll == false on a non-blocking fd from a fiber preserves the "don't wait" semantic + * and returns -EAGAIN when no data is available, instead of suspending the fiber. */ +TEST(loop_read_no_poll_nonblock) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-np-nb", loop_read_no_poll_nonblock_fiber, + INT_TO_PTR(pipefd[0]), /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EAGAIN); +} + +typedef struct LoopReadNoPollBlockingContext { + int *pipefd; + const char *data; + size_t len; + int order; +} LoopReadNoPollBlockingContext; + +static int loop_read_no_poll_blocking_reader_fiber(void *userdata) { + LoopReadNoPollBlockingContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* do_poll == false on a *blocking* fd from a fiber: the fast EAGAIN return isn't possible, + * so loop_read() takes the fiber path and suspends until the writer fiber feeds data. */ + ssize_t n = loop_read(ctx->pipefd[0], buf, sizeof(buf), /* do_poll= */ false); + + ASSERT_EQ(ctx->order, 2); + + if (n < 0) + return (int) n; + if ((size_t) n != ctx->len || memcmp(buf, ctx->data, ctx->len) != 0) + return -EIO; + + return (int) n; +} + +static int loop_read_no_poll_blocking_writer_fiber(void *userdata) { + LoopReadNoPollBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + int r = loop_write(ctx->pipefd[1], ctx->data, ctx->len); + if (r < 0) + return r; + + ctx->pipefd[1] = safe_close(ctx->pipefd[1]); + return 0; +} + +/* Test: do_poll == false on a blocking fd from a fiber takes the fiber path (suspends until the + * peer feeds data) instead of blocking the entire thread. */ +TEST(loop_read_no_poll_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + static const char payload[] = "no-poll"; + LoopReadNoPollBlockingContext ctx = { + .pipefd = pipefd, + .data = payload, + .len = sizeof(payload) - 1, + }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-np-blk", loop_read_no_poll_blocking_reader_fiber, + &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-write-np-blk", loop_read_no_poll_blocking_writer_fiber, + &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.len); + ASSERT_OK_ZERO(sd_future_result(fw)); +} + +/* Test: loop_*() helpers transparently fall back to blocking I/O when called outside any + * fiber context. */ +TEST(loop_read_write_fallback) { + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + ASSERT_OK(loop_write(pipefd[1], "fallback", STRLEN("fallback"))); + + char buf[16]; + ssize_t n = loop_read(pipefd[0], buf, STRLEN("fallback"), /* do_poll= */ true); + ASSERT_OK_EQ(n, (ssize_t) STRLEN("fallback")); + ASSERT_EQ(memcmp(buf, "fallback", STRLEN("fallback")), 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 2f9959648fa0ad4b00052a04480b53f7a5829869 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 23 Mar 2026 10:15:27 +0100 Subject: [PATCH 2084/2155] sd-event: suspend instead of blocking when sd_event_run() runs on a fiber MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sd_event_run() blocks the calling thread on the event loop's epoll fd until something happens. When the caller is a fiber, that's the wrong behaviour: blocking the thread also stalls every other fiber and the outer event loop driving them. The most common way to hit this is a fiber that creates its own inner event loop (e.g. a server-style fiber that wants to dispatch its own sources independently of whatever loop the test or supervising fiber is running on) — with the existing implementation the inner sd_event_run() would hold the thread while the outer scheduler should be free to advance other fibers. Add an event_run_suspend() variant in sd-event/event-future.c that performs the same prepare/wait/dispatch dance, but when the fast path finds nothing ready it (a) creates an IO future watching the inner event loop's epoll fd on the *outer* event loop, (b) optionally creates a time future for the timeout, and (c) suspends the fiber. When either future fires the fiber is resumed and the prepare/wait/dispatch sequence runs once more to actually dispatch what's pending. sd_event_run() checks sd_fiber_is_running() and delegates to this variant when on a fiber; profile_delays accounting is intentionally skipped on that path since the underlying prepare/wait/dispatch primitives already account for themselves. --- src/libsystemd/meson.build | 1 + src/libsystemd/sd-event/event-future.c | 72 ++++ src/libsystemd/sd-event/event-future.h | 2 + src/libsystemd/sd-event/event-util.h | 3 + src/libsystemd/sd-event/sd-event.c | 13 +- src/libsystemd/sd-event/test-event-future.c | 358 ++++++++++++++++++++ 6 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 src/libsystemd/sd-event/test-event-future.c diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 91a78155d2d3b..4e9fd28d44231 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -190,6 +190,7 @@ simple_tests += files( 'sd-bus/test-bus-vtable.c', 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', + 'sd-event/test-event-future.c', 'sd-future/test-fiber.c', 'sd-future/test-fiber-io.c', 'sd-future/test-fiber-ops.c', diff --git a/src/libsystemd/sd-event/event-future.c b/src/libsystemd/sd-event/event-future.c index 8c9960fcdd9d0..4e0a0a87fc204 100644 --- a/src/libsystemd/sd-event/event-future.c +++ b/src/libsystemd/sd-event/event-future.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "errno-util.h" #include "event-future.h" +#include "event-util.h" #include "fd-util.h" typedef struct IoFuture { @@ -234,3 +235,74 @@ int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accura int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret) { return future_new_time_internal(sd_event_add_time_relative, e, clock, usec, accuracy, result, ret); } + +int event_run_suspend(sd_event *e, uint64_t timeout) { + sd_event *outer = sd_fiber_get_event(); + int r; + + assert(e); + assert(sd_fiber_is_running()); + assert(outer); + assert(e != outer); + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + PROTECT_EVENT(e); + + r = sd_event_prepare(e); + if (r < 0) + return r; + if (r == 0) { + r = sd_event_wait(e, 0); + if (r < 0) + return r; + } + if (r > 0) { + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + if (timeout == 0) + return 0; + + int fd = sd_event_get_fd(e); + if (fd < 0) + return fd; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(outer, fd, EPOLLIN, &io); + if (r < 0) + return r; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + if (timeout != USEC_INFINITY) { + r = future_new_time_relative( + outer, + CLOCK_MONOTONIC, + timeout, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + } + + r = sd_fiber_suspend(); + if (r < 0) + return r; + + r = sd_event_prepare(e); + if (r == 0) + r = sd_event_wait(e, 0); + if (r > 0) { + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} diff --git a/src/libsystemd/sd-event/event-future.h b/src/libsystemd/sd-event/event-future.h index 3bc275e7b7ac9..83d5939d6b02d 100644 --- a/src/libsystemd/sd-event/event-future.h +++ b/src/libsystemd/sd-event/event-future.h @@ -6,3 +6,5 @@ int future_new_io(sd_event *e, int fd, uint32_t events, sd_future **ret); int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); + +int event_run_suspend(sd_event *e, uint64_t timeout); diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h index dc3b3ed70ff12..ce213b9c9e4d9 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -5,6 +5,9 @@ #include "sd-forward.h" +#define PROTECT_EVENT(e) \ + _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); + extern const struct hash_ops event_source_hash_ops; int event_reset_time( diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 8c419fad306ea..4935ae1b4aac2 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -10,12 +10,15 @@ #include "sd-daemon.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-id128.h" #include "sd-messages.h" #include "alloc-util.h" #include "errno-util.h" +#include "event-future.h" #include "event-source.h" +#include "event-util.h" #include "fd-util.h" #include "format-util.h" #include "glyph-util.h" @@ -474,9 +477,6 @@ _public_ sd_event* sd_event_unref(sd_event *e) { return event_free(e); } -#define PROTECT_EVENT(e) \ - _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); - _public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) { int r; @@ -4932,6 +4932,13 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) { assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + /* When running on a fiber, delegate to the suspending implementation. Note that the + * profile_delays accounting below is intentionally skipped on that path: the suspending variant + * drives the event loop via sd_event_prepare()/sd_event_wait()/sd_event_dispatch() itself, which + * are the same primitives profile_delays tracks when called directly. */ + if (sd_fiber_is_running()) + return event_run_suspend(e, timeout); + if (e->profile_delays && e->last_run_usec != 0) { usec_t this_run; unsigned l; diff --git a/src/libsystemd/sd-event/test-event-future.c b/src/libsystemd/sd-event/test-event-future.c new file mode 100644 index 0000000000000..754daf0df2fc4 --- /dev/null +++ b/src/libsystemd/sd-event/test-event-future.c @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "fd-util.h" +#include "tests.h" +#include "time-util.h" + +static int timer_callback(sd_event_source *s, uint64_t usec, void *userdata) { + int *count = ASSERT_PTR(userdata); + int r; + + (*count)++; + + r = sd_event_source_set_time_relative(s, 5 * USEC_PER_MSEC); + if (r < 0) + return r; + + if (sd_fiber_is_running() && *count >= 3) + return sd_event_exit(sd_event_source_get_event(s), 0); + + return 0; +} + +static int event_run_fiber_func(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *inner_timer = NULL; + int r; + + /* Create inner event loop from within the fiber */ + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add a timer to the inner event loop that fires every 5ms */ + r = sd_event_add_time_relative(inner, &inner_timer, CLOCK_MONOTONIC, + 5 * USEC_PER_MSEC, 0, timer_callback, + userdata); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(inner_timer, SD_EVENT_ON); + if (r < 0) + return r; + + return sd_event_loop(inner); +} + +TEST(sd_event_loop_fiber) { + /* Create outer event loop for the fiber scheduler */ + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + /* Add a timer to the outer event loop that fires every 5ms */ + _cleanup_(sd_event_source_unrefp) sd_event_source *outer_timer = NULL; + int outer_timer_count = 0; + ASSERT_OK(sd_event_add_time_relative(outer, &outer_timer, CLOCK_MONOTONIC, + 5 * USEC_PER_MSEC, 0, timer_callback, + &outer_timer_count)); + + /* Create a fiber that will create and run the inner event loop */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int inner_timer_count = 0; + ASSERT_OK(sd_fiber_new(outer, "event-runner", event_run_fiber_func, &inner_timer_count, /* destroy= */ NULL, &f)); + + /* Run the outer event loop */ + ASSERT_OK(sd_event_loop(outer)); + + /* Fiber should have completed successfully */ + ASSERT_OK(sd_future_result(f)); + + /* Both timers should have fired at least once */ + ASSERT_EQ(inner_timer_count, 3); + ASSERT_GT(outer_timer_count, 0); +} + +static int event_run_fiber_timeout_func(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + int r; + + /* Create inner event loop from within the fiber */ + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Run with a short timeout - should timeout since there are no events */ + return sd_event_run(inner, 10 * USEC_PER_MSEC); +} + +TEST(sd_event_run_fiber_timeout) { + /* Create outer event loop for the fiber scheduler */ + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + /* Create a fiber that will run sd_event_run() with timeout */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "event-timeout", event_run_fiber_timeout_func, NULL, /* destroy= */ NULL, &f)); + + /* Run the outer event loop */ + ASSERT_OK(sd_event_loop(outer)); + + /* Fiber should have completed successfully (timeout returns 0) */ + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with zero timeout returns immediately */ +static int sd_event_run_zero_timeout_fiber(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + int r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* With zero timeout on an empty event loop, should return 0 immediately */ + r = sd_event_run(inner, 0); + if (r != 0) + return r < 0 ? r : -EIO; + + return 0; +} + +TEST(sd_event_run_zero_timeout) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-zero", sd_event_run_zero_timeout_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() dispatches immediately pending IO */ +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + int *counter = ASSERT_PTR(userdata); + char buf[64]; + + (*counter)++; + + /* Drain the fd */ + (void) read(fd, buf, sizeof(buf)); + + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +static int sd_event_run_immediate_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add IO source watching the read end of the pipe */ + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* Data is already available on the pipe (written before fiber started), so + * sd_event_run() should dispatch immediately without suspending */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + /* The IO callback should have fired */ + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_immediate) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before starting the fiber so it's immediately available */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "X", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-immediate", sd_event_run_immediate_fiber, pipefd, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with IO arriving during suspension */ +static int sd_event_run_io_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* No data available yet, so this will suspend the fiber until IO arrives */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_io) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-io", sd_event_run_io_fiber, pipefd, /* destroy= */ NULL, &f)); + + /* First iteration: fiber runs, adds IO source, suspends because no data */ + ASSERT_OK_POSITIVE(sd_event_run(outer, 0)); + + /* Write data to the pipe to wake the inner event loop */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Y", 1), 1); + + /* Complete: fiber resumes, dispatches IO, finishes */ + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: event_run called in a loop keeps event loop state consistent. + * This is a regression test for a bug where error paths after sd_event_prepare() + * could leave the inner event loop stuck in SD_EVENT_ARMED state. */ +static int sd_event_run_loop_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* Call sd_event_run() multiple times with short timeouts. + * Each call should leave the inner event loop in a clean state for the next call. */ + for (int i = 0; i < 5; i++) { + r = sd_event_run(inner, 10 * USEC_PER_MSEC); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* After multiple timeouts, the event loop should still be usable. + * Write data and do one more run to verify. */ + if (counter == 0) { + /* Data wasn't written yet, do a final run with longer timeout */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + } + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_loop) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-loop", sd_event_run_loop_fiber, pipefd, /* destroy= */ NULL, &f)); + + /* Let the fiber run through a few timeout iterations */ + for (int i = 0; i < 10; i++) + ASSERT_OK(sd_event_run(outer, 50 * USEC_PER_MSEC)); + + /* Write data to unblock the fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Z", 1), 1); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with an inner timer that fires during suspension */ +static int inner_timer_handler(sd_event_source *s, uint64_t usec, void *userdata) { + int *counter = ASSERT_PTR(userdata); + (*counter)++; + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +static int sd_event_run_timer_fiber(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add a timer that fires after 10ms */ + r = sd_event_add_time_relative(inner, &source, CLOCK_MONOTONIC, + 10 * USEC_PER_MSEC, 0, inner_timer_handler, + &counter); + if (r < 0) + return r; + + /* Should suspend, then resume when the timer fires */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_timer) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-timer", sd_event_run_timer_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 9727f6536c5890c3a20e45de1355791e8ed523d4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 11 May 2026 16:27:34 +0200 Subject: [PATCH 2085/2155] sd-bus: make sd-bus fiber-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes to teach sd-bus how to behave when called from a fiber, in order of increasing depth: 2. sd_bus_call() now redirects to a new bus_call_suspend() helper when the caller is a fiber whose event loop is the same one the bus is attached to. The plain bus_poll() path serializes all bus traffic on the slot's reply (only one method call can be in flight per sd_bus*), which would defeat the point of running multiple fibers against one bus. bus_call_suspend() builds on the async sd-bus API: it wraps the call in a new BusFuture (sd-bus/bus-future.{c,h}) that resolves when the reply or method-error arrives, lets the fiber await that future, and surfaces the reply to the caller via future_get_bus_reply(). Because the futures live on the event loop rather than a per-bus slot, multiple fibers can drive concurrent method calls against the same bus. 3. A new private SD_BUS_VTABLE_METHOD_FIBER flag dispatches a vtable method handler on its own fiber, so handlers are free to use sd_bus_call() against the same bus, sd_fiber_sleep(), loop_read(), etc. without stalling the event loop for other connections or handlers. The flag stays out of sd-bus-vtable.h (its bit value is reserved there to prevent collisions) — the fiber runtime is a systemd-internal implementation detail. Lifecycle of fiber-dispatched handlers is tracked on the bus itself: a new bus->fiber_futures set holds a ref to each in-flight handler. bus_enter_closing() cancels every entry and process_closing() returns with the bus still in CLOSING state until the set drains, so we can be sure no fiber handler outlives the bus. bus_fiber_resolved() removes the entry on completion. bus_free()'s assert(set_isempty()) makes the invariant load-bearing. Note that plain sd_bus_call() already works correctly on a fiber as it calls ppoll_usec() which has already been modified to suspend when running on a fiber. To exercise these changes the existing thread-based client/server sd-bus tests (test-bus-chat, test-bus-objects, test-bus-peersockaddr, test-bus-server, test-bus-watch-bind) are migrated to fibers, and a new test-bus-fiber is added that covers SD_BUS_VTABLE_METHOD_FIBER — including handlers that issue nested sd_bus_call() on the same bus, the cancel-on-close path, and concurrent dispatches across multiple fibers. --- src/libsystemd/meson.build | 2 + src/libsystemd/sd-bus/bus-future.c | 135 ++++++++++++ src/libsystemd/sd-bus/bus-future.h | 14 ++ src/libsystemd/sd-bus/bus-internal.h | 13 ++ src/libsystemd/sd-bus/bus-objects.c | 109 +++++++++ src/libsystemd/sd-bus/sd-bus.c | 50 ++++- src/libsystemd/sd-bus/test-bus-chat.c | 51 +++-- src/libsystemd/sd-bus/test-bus-fiber.c | 207 ++++++++++++++++++ src/libsystemd/sd-bus/test-bus-objects.c | 62 ++---- src/libsystemd/sd-bus/test-bus-peersockaddr.c | 33 +-- src/libsystemd/sd-bus/test-bus-server.c | 48 ++-- src/libsystemd/sd-bus/test-bus-watch-bind.c | 102 +++++---- src/systemd/sd-bus-vtable.h | 1 + 13 files changed, 675 insertions(+), 152 deletions(-) create mode 100644 src/libsystemd/sd-bus/bus-future.c create mode 100644 src/libsystemd/sd-bus/bus-future.h create mode 100644 src/libsystemd/sd-bus/test-bus-fiber.c diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 4e9fd28d44231..0cecea3b0d194 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -49,6 +49,7 @@ sd_bus_sources = files( 'sd-bus/bus-dump.c', 'sd-bus/bus-dump-json.c', 'sd-bus/bus-error.c', + 'sd-bus/bus-future.c', 'sd-bus/bus-internal.c', 'sd-bus/bus-introspect.c', 'sd-bus/bus-kernel.c', @@ -185,6 +186,7 @@ libsystemd_pc = custom_target( simple_tests += files( 'sd-bus/test-bus-creds.c', + 'sd-bus/test-bus-fiber.c', 'sd-bus/test-bus-introspect.c', 'sd-bus/test-bus-match.c', 'sd-bus/test-bus-vtable.c', diff --git a/src/libsystemd/sd-bus/bus-future.c b/src/libsystemd/sd-bus/bus-future.c new file mode 100644 index 0000000000000..d7037b3f15bed --- /dev/null +++ b/src/libsystemd/sd-bus/bus-future.c @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-bus.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "bus-future.h" +#include "bus-internal.h" +#include "bus-message.h" + +typedef struct BusFuture { + sd_bus_slot *slot; + sd_bus_message *reply; +} BusFuture; + +static void* bus_future_alloc(void) { + return new0(BusFuture, 1); +} + +static void bus_future_free(sd_future *f) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(f)); + sd_bus_slot_unref(bf->slot); + sd_bus_message_unref(bf->reply); + free(bf); +} + +static int bus_future_cancel(sd_future *f) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + bf->slot = sd_bus_slot_unref(bf->slot); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops bus_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = bus_future_alloc, + .free = bus_future_free, + .cancel = bus_future_cancel, +}; + +static int bus_future_handler(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + sd_future *f = ASSERT_PTR(userdata); + BusFuture *bf = ASSERT_PTR(sd_future_get_private(f)); + int r; + + /* Resolve with 0 on a success reply and -errno (derived from the D-Bus error name) on a + * method error reply, so a caller awaiting the future learns about call failures from the + * resolution value alone. The reply itself is always stashed in bf->reply so + * future_get_bus_reply() can hand back the detailed sd_bus_error (name + message) on + * top of the bare errno. Cancellation surfaces as -ECANCELED via bus_future_cancel(), + * with bf->reply left NULL — callers can distinguish "got an error reply" from "no reply + * will arrive" by whether future_get_bus_reply() can produce a message. */ + bf->slot = sd_bus_slot_unref(bf->slot); + bf->reply = sd_bus_message_ref(m); + + r = sd_bus_message_is_method_error(m, NULL) ? -sd_bus_message_get_errno(m) : 0; + return sd_future_resolve(f, r); +} + +int bus_call_future(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_future **ret) { + int r; + + assert(bus); + assert(m); + assert(ret); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&bus_future_ops, &f); + if (r < 0) + return r; + + BusFuture *bf = sd_future_get_private(f); + + r = sd_bus_call_async(bus, &bf->slot, m, bus_future_handler, f, usec); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} + +int future_get_bus_reply(sd_future *f, sd_bus_error *reterr_error, sd_bus_message **ret_reply) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_bus_message *reply = ASSERT_PTR(bf->reply); + + assert(sd_future_get_ops(f) == &bus_future_ops); + assert(sd_future_state(f) == SD_FUTURE_RESOLVED); + + if (sd_bus_message_is_method_error(reply, NULL)) { + if (reterr_error) + return sd_bus_error_copy(reterr_error, sd_bus_message_get_error(reply)); + return -sd_bus_message_get_errno(reply); + } + + if (reply->n_fds > 0 && !sd_bus_message_get_bus(reply)->accept_fd) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, + "Reply message contained file descriptors which I couldn't accept. Sorry."); + + if (reterr_error) + *reterr_error = SD_BUS_ERROR_NULL; + if (ret_reply) + *ret_reply = sd_bus_message_ref(reply); + + return 1; +} + +int bus_call_suspend( + sd_bus *bus, + sd_bus_message *m, + uint64_t usec, + sd_bus_error *reterr_error, + sd_bus_message **ret_reply) { + + int r; + + assert(bus); + assert(m); + assert(sd_fiber_is_running()); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f = NULL; + r = bus_call_future(bus, m, usec, &f); + if (r < 0) + return sd_bus_error_set_errno(reterr_error, r); + + r = sd_fiber_suspend(); + + /* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber + * cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract, + * so surface the resume error directly. When the future is resolved, future_get_bus_reply() + * recovers either the reply or the detailed sd_bus_error from the error reply. */ + if (sd_future_state(f) != SD_FUTURE_RESOLVED) + return sd_bus_error_set_errno(reterr_error, r); + + return future_get_bus_reply(f, reterr_error, ret_reply); +} diff --git a/src/libsystemd/sd-bus/bus-future.h b/src/libsystemd/sd-bus/bus-future.h new file mode 100644 index 0000000000000..ec9bd80b1598a --- /dev/null +++ b/src/libsystemd/sd-bus/bus-future.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +int bus_call_future(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_future **ret); +int future_get_bus_reply(sd_future *f, sd_bus_error *reterr_error, sd_bus_message **ret_reply); + +int bus_call_suspend( + sd_bus *bus, + sd_bus_message *m, + uint64_t usec, + sd_bus_error *reterr_error, + sd_bus_message **ret_reply); diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 19a3b67d12f6a..3a52f738d6bd7 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -17,6 +17,13 @@ #define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/run/dbus/system_bus_socket" #define DEFAULT_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" +/* Private vtable flag: dispatch the method handler on its own fiber, so it can use suspending + * primitives (sd_bus_call() on a fiber, sd_fiber_sleep(), loop_read_suspend(), ...) without + * blocking the event loop for other connections or method calls. Kept out of the public + * sd-bus-vtable.h so the fiber runtime stays an implementation detail of systemd. The bit value is + * reserved in sd-bus-vtable.h to make sure it never collides with a future public flag. */ +#define SD_BUS_VTABLE_METHOD_FIBER (UINT64_C(1) << 10) + typedef struct BusReplyCallback { sd_bus_message_handler_t callback; usec_t timeout_usec; /* this is a relative timeout until we reach the BUS_HELLO state, and an absolute one right after */ @@ -222,6 +229,12 @@ typedef struct sd_bus { Set *vtable_methods; Set *vtable_properties; + /* Futures for outstanding SD_BUS_VTABLE_METHOD_FIBER dispatches. Entries are added as the + * dispatcher spawns each fiber and removed when the fiber resolves. On bus_enter_closing() + * we cancel everything in here and then wait in process_closing() until the set drains, + * before tearing down the rest of the bus. */ + Set *fiber_futures; + union sockaddr_union sockaddr; socklen_t sockaddr_size; diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index 83ba3a523992b..795cc68837655 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -3,6 +3,7 @@ #include #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-internal.h" @@ -337,6 +338,67 @@ static int check_access(sd_bus *bus, sd_bus_message *m, BusVTableMember *c, sd_b return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); } +typedef struct BusFiberData { + sd_bus *bus; + sd_bus_message *message; + sd_bus_slot *slot; + sd_bus_message_handler_t handler; + void *userdata; +} BusFiberData; + +static BusFiberData* bus_fiber_data_free(BusFiberData *d) { + if (!d) + return NULL; + + sd_bus_slot_unref(d->slot); + sd_bus_message_unref(d->message); + sd_bus_unref(d->bus); + return mfree(d); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusFiberData*, bus_fiber_data_free); + +static void bus_fiber_data_destroy(void *userdata) { + bus_fiber_data_free(userdata); +} + +static void bus_fiber_future_unref(void *p) { + sd_future_unref(p); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + bus_fiber_future_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + bus_fiber_future_unref); + +static int bus_fiber_resolved(sd_future *f) { + sd_bus *bus = ASSERT_PTR(sd_future_get_userdata(f)); + + assert_se(set_remove(bus->fiber_futures, f) == f); + sd_future_unref(f); + return 0; +} + +static int bus_fiber_entry(void *userdata) { + BusFiberData *d = ASSERT_PTR(userdata); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + /* Note: unlike the synchronous dispatch path, we deliberately do NOT set + * bus->current_slot/handler/userdata around the callback. Those fields track the slot of the + * message currently being dispatched inline and must be NULL at each entry into + * bus_process_internal(). Because a fiber handler can yield and let the event loop dispatch + * other messages before it resumes, leaving current_slot non-NULL across yields would trip + * that invariant. Fiber handlers receive their slot's userdata via the handler argument, so + * sd_bus_get_current_slot()/handler()/userdata() simply aren't meaningful inside them — the + * handler should use the message/userdata parameters directly instead. */ + r = d->handler(d->message, d->userdata, &error); + + return bus_maybe_reply_error(d->message, r, &error); +} + static int method_callbacks_run( sd_bus *bus, sd_bus_message *m, @@ -407,6 +469,53 @@ static int method_callbacks_run( slot = container_of(c->parent, sd_bus_slot, node_vtable); + if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_METHOD_FIBER)) { + /* A fiber-dispatched method requires an event loop to spawn the fiber on. + * By the time a method call actually arrives the bus is running, so the + * event loop should already be attached — if not, the caller set up the bus + * wrong and there's no meaningful recovery. */ + assert(bus->event); + + _cleanup_(bus_fiber_data_freep) BusFiberData *d = new(BusFiberData, 1); + if (!d) + return -ENOMEM; + + *d = (BusFiberData) { + .bus = sd_bus_ref(bus), + .message = sd_bus_message_ref(m), + .slot = sd_bus_slot_ref(slot), + .handler = c->vtable->x.method.handler, + .userdata = u, + }; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_fiber_new(bus->event, c->member, bus_fiber_entry, d, bus_fiber_data_destroy, &f); + if (r < 0) + return bus_maybe_reply_error(m, r, NULL); + + /* The fiber now owns d via bus_fiber_data_destroy. Drop our cleanup before any + * further fallible calls, so a later failure unwinding f doesn't double-free d. */ + TAKE_PTR(d); + + r = set_ensure_put(&bus->fiber_futures, &bus_fiber_future_hash_ops, f); + if (r < 0) + return bus_maybe_reply_error(m, r, NULL); + assert(r > 0); + + /* Track the future on the bus so shutdown can cancel it and wait for it. */ + r = sd_future_set_callback(f, bus_fiber_resolved, bus); + if (r < 0) { + /* TAKE_PTR(f) hasn't run yet, so our cleanup attribute still owns the + * ref; set_remove() returns the raw pointer without firing the hash_ops + * destructor, and the cleanup will unref f on return. */ + assert_se(set_remove(bus->fiber_futures, f) == f); + return bus_maybe_reply_error(m, r, NULL); + } + + TAKE_PTR(f); + return 1; + } + bus->current_slot = sd_bus_slot_ref(slot); bus->current_handler = c->vtable->x.method.handler; bus->current_userdata = u; diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 27f788d995576..e44c439fad862 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -10,12 +10,14 @@ #include "sd-bus.h" #include "sd-event.h" +#include "sd-future.h" #include "af-list.h" #include "alloc-util.h" #include "bus-container.h" #include "bus-control.h" #include "bus-error.h" +#include "bus-future.h" #include "bus-internal.h" #include "bus-kernel.h" #include "bus-label.h" @@ -222,6 +224,12 @@ static sd_bus* bus_free(sd_bus *b) { ordered_hashmap_free(b->reply_callbacks); prioq_free(b->reply_callbacks_prioq); + /* Outstanding fiber handlers pin the bus via their BusFiberData ref, so by the time refcount + * reaches zero and bus_free() runs, every fiber has already resolved and removed itself from + * this set. */ + assert(set_isempty(b->fiber_futures)); + set_free(b->fiber_futures); + assert(b->match_callbacks.type == BUS_MATCH_ROOT); bus_match_free(&b->match_callbacks); @@ -1809,6 +1817,9 @@ _public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { } void bus_enter_closing(sd_bus *bus, int exit_code) { + sd_future *f; + int r; + assert(bus); if (!IN_SET(bus->state, BUS_WATCH_BIND, BUS_OPENING, BUS_AUTHENTICATING, BUS_HELLO, BUS_RUNNING)) @@ -1816,6 +1827,19 @@ void bus_enter_closing(sd_bus *bus, int exit_code) { bus_set_state(bus, BUS_CLOSING); bus->exit_code = exit_code; + + /* Cancel all outstanding fiber-dispatched method handlers. Most cancellations are scheduled + * asynchronously (fibers resolve with -ECANCELED the next time they run), but a fiber still + * in FIBER_STATE_INITIAL resolves synchronously, which fires bus_fiber_resolved() and + * removes f from this set mid-iteration. That's safe because SET_FOREACH permits removal of + * exactly the current entry — see the assertion in hashmap_iterate_entry(). Either way this + * doesn't block here: process_closing() waits for the fiber_futures set to drain before it + * continues tearing down the rest of the bus. */ + SET_FOREACH(f, bus->fiber_futures) { + r = sd_future_cancel(f); + if (r < 0) + log_debug_errno(r, "Failed to cancel outstanding fiber method handler, ignoring: %m"); + } } /* Define manually so we can add the PID check */ @@ -2388,23 +2412,30 @@ _public_ int sd_bus_call( sd_bus_error *reterr_error, sd_bus_message **ret_reply) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); usec_t timeout; uint64_t cookie; size_t i; int r; - bus_assert_return(m, -EINVAL, reterr_error); - bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, reterr_error); - bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, reterr_error); + bus_assert_return(_m, -EINVAL, reterr_error); + bus_assert_return(_m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, reterr_error); + bus_assert_return(!(_m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, reterr_error); bus_assert_return(!bus_error_is_dirty(reterr_error), -EINVAL, reterr_error); if (bus) assert_return(bus = bus_resolve(bus), -ENOPKG); else - assert_return(bus = m->bus, -ENOTCONN); + assert_return(bus = _m->bus, -ENOTCONN); bus_assert_return(!bus_origin_changed(bus), -ECHILD, reterr_error); + /* If the current fiber and the bus share their event loop, we can use sd_bus_call_suspend() + * instead which does an async method call. This allows multiple invocations of sd_bus_call() to + * happen across multiple fibers at once. */ + if (sd_fiber_is_running() && bus->event == sd_fiber_get_event()) + return bus_call_suspend(bus, _m, usec, reterr_error, ret_reply); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + if (!BUS_IS_OPEN(bus->state)) { r = -ENOTCONN; goto fail; @@ -3177,7 +3208,14 @@ static int process_closing(sd_bus *bus, sd_bus_message **ret) { assert(bus); assert(bus->state == BUS_CLOSING); - /* First, fail all outstanding method calls */ + /* Wait for any still-running fiber method handlers to finish unwinding their cancellation + * before tearing down the rest of the bus. bus_enter_closing() scheduled the cancel; each + * fiber resolves asynchronously and bus_fiber_resolved() removes it from the set. Returning + * 1 here keeps the bus in CLOSING state so the event loop drives the fibers to completion. */ + if (!set_isempty(bus->fiber_futures)) + return 1; + + /* Then, fail all outstanding method calls */ c = ordered_hashmap_first(bus->reply_callbacks); if (c) return process_closing_reply_callback(bus, c); diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index 1f358ccd3396e..d6f4860c41511 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-error.h" @@ -102,7 +102,8 @@ static int server_init(sd_bus **ret) { return 0; } -static int server(sd_bus *bus) { +static int server(void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); bool client1_gone = false, client2_gone = false; int r; @@ -178,7 +179,9 @@ static int server(sd_bus *bus) { client2_gone = true; } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { - sleep(1); + r = sd_fiber_sleep(1 * USEC_PER_SEC); + if (r < 0) + return r; r = sd_bus_reply_method_return(m, NULL); if (r < 0) @@ -194,10 +197,10 @@ static int server(sd_bus *bus) { log_info("Received fd=%d", fd); - if (write(fd, &x, 1) < 0) { - r = log_error_errno(errno, "Failed to write to fd: %m"); + ssize_t n = sd_fiber_write(fd, &x, 1); + if (n < 0) { safe_close(fd); - return r; + return log_error_errno(n, "Failed to write to fd: %m"); } r = sd_bus_reply_method_return(m, NULL); @@ -217,7 +220,7 @@ static int server(sd_bus *bus) { return 0; } -static void* client1(void *p) { +static int client1(void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -277,9 +280,9 @@ static void* client1(void *p) { goto finish; } - errno = 0; - if (read(pp[0], &x, 1) <= 0) { - log_error("Failed to read from pipe: %s", STRERROR_OR_EOF(errno)); + ssize_t n = sd_fiber_read(pp[0], &x, 1); + if (n <= 0) { + log_error("Failed to read from pipe: %s", STRERROR_OR_EOF(n)); goto finish; } @@ -303,7 +306,7 @@ static void* client1(void *p) { } - return INT_TO_PTR(r); + return r; } static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { @@ -315,7 +318,7 @@ static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_er return 1; } -static void* client2(void *p) { +static int client2(void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -494,7 +497,7 @@ static void* client2(void *p) { (void) sd_bus_send(bus, q, NULL); } - return INT_TO_PTR(r); + return r; } static ino_t get_inode(int fd) { @@ -626,9 +629,9 @@ TEST(ctrunc) { } TEST(chat) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client1 = NULL, *f_client2 = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - pthread_t c1, c2; - void *p; int r; test_setup_logging(LOG_INFO); @@ -639,16 +642,18 @@ TEST(chat) { log_info("Initialized..."); - ASSERT_OK(-pthread_create(&c1, NULL, client1, NULL)); - ASSERT_OK(-pthread_create(&c2, NULL, client2, NULL)); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "client-1", client1, NULL, /* destroy= */ NULL, &f_client1)); + ASSERT_OK(sd_fiber_new(e, "client-2", client2, NULL, /* destroy= */ NULL, &f_client2)); + ASSERT_OK(sd_fiber_new(e, "server", server, bus, /* destroy= */ NULL, &f_server)); - r = server(bus); + ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(c1, &p)); - ASSERT_OK(PTR_TO_INT(p)); - ASSERT_OK(-pthread_join(c2, &p)); - ASSERT_OK(PTR_TO_INT(p)); - ASSERT_OK(r); + ASSERT_OK(sd_future_result(f_client1)); + ASSERT_OK(sd_future_result(f_client2)); + ASSERT_OK(sd_future_result(f_server)); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd/sd-bus/test-bus-fiber.c b/src/libsystemd/sd-bus/test-bus-fiber.c new file mode 100644 index 0000000000000..2cadf1dc17591 --- /dev/null +++ b/src/libsystemd/sd-bus/test-bus-fiber.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-event.h" +#include "sd-future.h" + +#include "bus-internal.h" +#include "tests.h" +#include "time-util.h" + +typedef struct Context { + /* Counters for the concurrency check: every Concurrent invocation bumps in_flight on entry + * and drops it on exit, and tracks the maximum observed concurrency. If fiber dispatch + * works, two overlapping client calls must both be inside the handler at the same time, + * giving a max of at least 2. */ + int in_flight; + int max_in_flight; + sd_future *waiter; +} Context; + +static int method_concurrent(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + Context *c = ASSERT_PTR(userdata); + + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + c->in_flight++; + if (c->in_flight > c->max_in_flight) + c->max_in_flight = c->in_flight; + + /* Synchronize on the peer instead of sleeping: the first handler to enter stashes its + * fiber and suspends; the second resumes it and then yields, so the first runs to + * completion (sending its reply) before the second proceeds. Both are therefore in + * flight at the same time regardless of how long any individual dispatch takes, and + * the reply order matches the request order. */ + if (c->in_flight < 2) { + assert(!c->waiter); + c->waiter = sd_fiber_get_current(); + ASSERT_OK(sd_fiber_suspend()); + } else { + ASSERT_OK(sd_fiber_resume(TAKE_PTR(c->waiter), 0)); + ASSERT_OK(sd_fiber_yield()); + } + + c->in_flight--; + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_fail_errno(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + /* Yielding first exercises the deferred-error path in the fiber entry: the handler returns + * a negative errno after suspending, and bus_maybe_reply_error() must still turn that into + * a matching sd_bus error reply. */ + ASSERT_OK(sd_fiber_sleep(1 * USEC_PER_MSEC)); + + return -EACCES; +} + +static int method_fail_error(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + ASSERT_OK(sd_fiber_sleep(1 * USEC_PER_MSEC)); + + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "bad arguments from fiber"); +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Concurrent", NULL, NULL, method_concurrent, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_METHOD("FailErrno", NULL, NULL, method_fail_errno, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_METHOD("FailError", NULL, NULL, method_fail_error, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_VTABLE_END, +}; + +typedef struct Setup { + int fds[2]; + Context *c; +} Setup; + +static int attach_pair(Setup *s, sd_bus **ret_server, sd_bus **ret_client) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + sd_id128_t id; + + assert(ret_server); + assert(ret_client); + + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(sd_bus_new(&server)); + ASSERT_OK(sd_bus_set_description(server, "server")); + ASSERT_OK(sd_bus_set_fd(server, s->fds[0], s->fds[0])); + ASSERT_OK(sd_bus_set_server(server, true, id)); + ASSERT_OK(sd_bus_attach_event(server, sd_fiber_get_event(), 0)); + ASSERT_OK(sd_bus_add_object_vtable(server, NULL, "/test", "test.Fiber", vtable, s->c)); + ASSERT_OK(sd_bus_start(server)); + + ASSERT_OK(sd_bus_new(&client)); + ASSERT_OK(sd_bus_set_description(client, "client")); + ASSERT_OK(sd_bus_set_fd(client, s->fds[1], s->fds[1])); + ASSERT_OK(sd_bus_attach_event(client, sd_fiber_get_event(), 0)); + ASSERT_OK(sd_bus_start(client)); + + *ret_server = TAKE_PTR(server); + *ret_client = TAKE_PTR(client); + return 0; +} + +static int call_concurrent_fiber(void *userdata) { + sd_bus *client = ASSERT_PTR(userdata); + + /* A plain suspending sd_bus_call() — on a fiber this goes through sd_bus_call_suspend() + * which multiplexes onto the single client connection, so multiple caller fibers can have + * calls in flight at the same time. */ + return sd_bus_call_method(client, NULL, "/test", "test.Fiber", "Concurrent", + NULL, NULL, NULL); +} + +static int concurrency_fiber(void *userdata) { + Setup *s = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f_a = NULL, *f_b = NULL; + + ASSERT_OK(attach_pair(s, &server, &client)); + + /* Two concurrent calls on the shared client bus. Each lands in method_concurrent which + * blocks on the peer; if fiber dispatch works the second is entered while the first is + * suspended, so max_in_flight on the context reaches 2. */ + ASSERT_OK(sd_fiber_new(sd_fiber_get_event(), "call-a", call_concurrent_fiber, client, + /* destroy= */ NULL, &f_a)); + ASSERT_OK(sd_fiber_new(sd_fiber_get_event(), "call-b", call_concurrent_fiber, client, + /* destroy= */ NULL, &f_b)); + + ASSERT_OK(sd_fiber_await(f_a)); + ASSERT_OK(sd_fiber_await(f_b)); + + ASSERT_OK(sd_future_result(f_a)); + ASSERT_OK(sd_future_result(f_b)); + return 0; +} + +TEST(fiber_method_concurrency) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + Context c = {}; + Setup s = { .c = &c }; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, s.fds)); + + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "concurrency", concurrency_fiber, &s, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); + ASSERT_GE(c.max_in_flight, 2); +} + +static int errors_fiber(void *userdata) { + Setup *s = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + + ASSERT_OK(attach_pair(s, &server, &client)); + + /* A fiber handler that returns a negative errno gets turned into a matching sd_bus error + * reply (bus_maybe_reply_error → sd_bus_reply_method_errno). */ + _cleanup_(sd_bus_error_free) sd_bus_error e1 = SD_BUS_ERROR_NULL; + ASSERT_ERROR(sd_bus_call_method(client, NULL, "/test", "test.Fiber", "FailErrno", + &e1, NULL, NULL), + EACCES); + ASSERT_TRUE(sd_bus_error_has_name(&e1, SD_BUS_ERROR_ACCESS_DENIED)); + + /* A fiber handler that populates sd_bus_error directly propagates both name and message. */ + _cleanup_(sd_bus_error_free) sd_bus_error e2 = SD_BUS_ERROR_NULL; + ASSERT_FAIL(sd_bus_call_method(client, NULL, "/test", "test.Fiber", "FailError", + &e2, NULL, NULL)); + ASSERT_TRUE(sd_bus_error_has_name(&e2, SD_BUS_ERROR_INVALID_ARGS)); + ASSERT_STREQ(e2.message, "bad arguments from fiber"); + + return 0; +} + +TEST(fiber_method_errors) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + Context c = {}; + Setup s = { .c = &c }; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, s.fds)); + + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "errors", errors_fiber, &s, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index 4ad60f0d58225..ac33086a6f374 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-internal.h" @@ -211,9 +210,9 @@ static int enumerator3_callback(sd_bus *bus, const char *path, void *userdata, c return 1; } -static void* server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; +static int server(void *userdata) { + struct context *c = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; sd_id128_t id; int r; @@ -242,36 +241,25 @@ static void* server(void *p) { log_error("Loop!"); r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } + if (r < 0) + return log_error_errno(r, "Failed to process requests: %m"); if (r == 0) { r = sd_bus_wait(bus, UINT64_MAX); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } + if (r < 0) + return log_error_errno(r, "Failed to wait: %m"); continue; } } - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); + return 0; } -static int client(struct context *c) { +static int client(void *p) { + struct context *c = ASSERT_PTR(p); _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_strv_free_ char **lines = NULL; const char *s; @@ -575,16 +563,13 @@ static int client(struct context *c) { ASSERT_OK(sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, NULL)); - sd_bus_flush(bus); - return 0; } int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; struct context c = {}; - pthread_t s; - void *p; - int r, q; test_setup_logging(LOG_DEBUG); @@ -593,21 +578,16 @@ int main(int argc, char *argv[]) { ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds)); - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - q = pthread_join(s, &p); - if (q != 0) - return -q; + ASSERT_OK(sd_fiber_new(e, "server", server, &c, /* destroy= */ NULL, &f_server)); + ASSERT_OK(sd_fiber_new(e, "client", client, &c, /* destroy= */ NULL, &f_client)); - if (r < 0) - return r; + ASSERT_OK(sd_event_loop(e)); - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); + ASSERT_OK(sd_future_result(f_server)); + ASSERT_OK(sd_future_result(f_client)); free(c.something); free(c.automatic_string_property); diff --git a/src/libsystemd/sd-bus/test-bus-peersockaddr.c b/src/libsystemd/sd-bus/test-bus-peersockaddr.c index 2cac35dde4033..bee76c9b10ca7 100644 --- a/src/libsystemd/sd-bus/test-bus-peersockaddr.c +++ b/src/libsystemd/sd-bus/test-bus-peersockaddr.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" +#include "sd-future.h" #include "bus-dump.h" #include "fd-util.h" @@ -38,9 +38,9 @@ static bool gid_list_same(const gid_t *a, size_t n, const gid_t *b, size_t m) { gid_list_contained(b, m, a, n); } -static void* server(void *p) { +static int server(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int listen_fd = PTR_TO_INT(p), fd = -EBADF; + _cleanup_close_ int listen_fd = PTR_TO_INT(userdata), fd = -EBADF; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; _cleanup_free_ char *our_comm = NULL; sd_id128_t id; @@ -48,7 +48,7 @@ static void* server(void *p) { ASSERT_OK(sd_id128_randomize(&id)); - ASSERT_OK_ERRNO(fd = accept4(listen_fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK)); + ASSERT_OK(fd = sd_fiber_accept(listen_fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK)); ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_fd(bus, fd, fd)); @@ -114,17 +114,18 @@ static void* server(void *p) { } } - return NULL; + return 0; } -static void* client(void *p) { +static int client(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *z; ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_description(bus, "wuffwuff")); - ASSERT_OK(sd_bus_set_address(bus, p)); + ASSERT_OK(sd_bus_set_address(bus, userdata)); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_start(bus)); ASSERT_OK(sd_bus_call_method(bus, "foo.foo", "/foo", "foo.foo", "Foo", NULL, &reply, "s", "foo")); @@ -132,17 +133,18 @@ static void* client(void *p) { ASSERT_OK(sd_bus_message_read(reply, "s", &z)); ASSERT_STREQ(z, "bar"); - return NULL; + return 0; } TEST(description) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; _cleanup_free_ char *a = NULL; _cleanup_close_ int fd = -EBADF; union sockaddr_union sa = { .un.sun_family = AF_UNIX, }; socklen_t salen; - pthread_t s, c; ASSERT_OK_ERRNO(fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); ASSERT_OK_ERRNO(bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path))); /* force auto-bind */ @@ -155,13 +157,18 @@ TEST(description) { ASSERT_OK(asprintf(&a, "unix:abstract=%s", sa.un.sun_path + 1)); - ASSERT_OK(-pthread_create(&s, NULL, server, INT_TO_PTR(fd))); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "server", server, INT_TO_PTR(fd), /* destroy= */ NULL, &f_server)); TAKE_FD(fd); - ASSERT_OK(-pthread_create(&c, NULL, client, a)); + ASSERT_OK(sd_fiber_new(e, "client", client, a, /* destroy= */ NULL, &f_client)); + + ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(s, NULL)); - ASSERT_OK(-pthread_join(c, NULL)); + ASSERT_OK(sd_future_result(f_server)); + ASSERT_OK(sd_future_result(f_client)); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd/sd-bus/test-bus-server.c b/src/libsystemd/sd-bus/test-bus-server.c index 989d2bf10dcaa..1edcec858f2ac 100644 --- a/src/libsystemd/sd-bus/test-bus-server.c +++ b/src/libsystemd/sd-bus/test-bus-server.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" +#include "sd-event.h" +#include "sd-future.h" +#include "errno-util.h" #include "log.h" #include "memory-util.h" #include "string-util.h" @@ -20,7 +22,8 @@ struct context { bool server_anonymous_auth; }; -static int _server(struct context *c) { +static int server(void *userdata) { + struct context *c = ASSERT_PTR(userdata); _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; sd_id128_t id; bool quit = false; @@ -29,6 +32,7 @@ static int _server(struct context *c) { ASSERT_OK(sd_id128_randomize(&id)); ASSERT_OK(sd_bus_new(&bus)); + ASSERT_OK(sd_bus_set_description(bus, "server")); ASSERT_OK(sd_bus_set_fd(bus, c->fds[0], c->fds[0])); ASSERT_OK(sd_bus_set_server(bus, 1, id)); ASSERT_OK(sd_bus_set_anonymous(bus, c->server_anonymous_auth)); @@ -74,17 +78,16 @@ static int _server(struct context *c) { return 0; } -static void* server(void *p) { - return INT_TO_PTR(_server(p)); -} - -static int client(struct context *c) { +static int client(void *userdata) { + struct context *c = ASSERT_PTR(userdata); _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; ASSERT_OK(sd_bus_new(&bus)); + ASSERT_OK(sd_bus_set_description(bus, "client")); ASSERT_OK(sd_bus_set_fd(bus, c->fds[1], c->fds[1])); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds)); ASSERT_OK(sd_bus_set_anonymous(bus, c->client_anonymous_auth)); ASSERT_OK(sd_bus_start(bus)); @@ -103,10 +106,10 @@ static int client(struct context *c) { static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, bool client_anonymous_auth, bool server_anonymous_auth) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; struct context c; - pthread_t s; - void *p; - int r, q; + int r = 0; zero(c); @@ -117,23 +120,18 @@ static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_f c.client_anonymous_auth = client_anonymous_auth; c.server_anonymous_auth = server_anonymous_auth; - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - r = client(&c); + ASSERT_OK(sd_fiber_new(e, "server", server, &c, /* destroy= */ NULL, &f_server)); + ASSERT_OK(sd_fiber_new(e, "client", client, &c, /* destroy= */ NULL, &f_client)); - q = pthread_join(s, &p); - if (q != 0) - return -q; + ASSERT_OK(sd_event_loop(e)); - if (r < 0) - return r; + RET_GATHER(r, sd_future_result(f_client)); + RET_GATHER(r, sd_future_result(f_server)); - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - return 0; + return r; } int main(int argc, char *argv[]) { @@ -145,7 +143,7 @@ int main(int argc, char *argv[]) { ASSERT_OK(test_one(false, false, false, false)); ASSERT_OK(test_one(true, true, true, true)); ASSERT_OK(test_one(true, true, false, true)); - ASSERT_ERROR(test_one(true, true, true, false), EPERM); + ASSERT_ERROR(test_one(true, true, true, false), EACCES); return EXIT_SUCCESS; } diff --git a/src/libsystemd/sd-bus/test-bus-watch-bind.c b/src/libsystemd/sd-bus/test-bus-watch-bind.c index 1bf4ee7017119..6561633b8a823 100644 --- a/src/libsystemd/sd-bus/test-bus-watch-bind.c +++ b/src/libsystemd/sd-bus/test-bus-watch-bind.c @@ -1,10 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include - #include "sd-bus.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-id128.h" #include "alloc-util.h" @@ -44,33 +42,33 @@ static const sd_bus_vtable vtable[] = { SD_BUS_VTABLE_END, }; -static void* thread_server(void *p) { +static int server(void *userdata) { _cleanup_free_ char *suffixed = NULL, *suffixed_basename = NULL, *suffixed2 = NULL, *d = NULL; _cleanup_close_ int fd = -EBADF; union sockaddr_union u; - const char *path = p; + const char *path = ASSERT_PTR(userdata); int r; log_debug("Initializing server"); /* Let's play some games, by slowly creating the socket directory, and renaming it in the middle */ - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(mkdir_parents(path, 0755)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(path_extract_directory(path, &d)); ASSERT_OK(asprintf(&suffixed, "%s.%" PRIx64, d, random_u64())); ASSERT_OK_ERRNO(rename(d, suffixed)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(asprintf(&suffixed2, "%s.%" PRIx64, d, random_u64())); ASSERT_OK_ERRNO(symlink(suffixed2, d)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(path_extract_filename(suffixed, &suffixed_basename)); ASSERT_OK_ERRNO(symlink(suffixed_basename, suffixed2)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); socklen_t sa_len; r = sockaddr_un_set_path(&u.un, path); @@ -81,13 +79,13 @@ static void* thread_server(void *p) { ASSERT_OK_ERRNO(fd); ASSERT_OK_ERRNO(bind(fd, &u.sa, sa_len)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK_ERRNO(listen(fd, SOMAXCONN_DELUXE)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(touch(path)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); log_debug("Initialized server"); @@ -101,8 +99,7 @@ static void* thread_server(void *p) { ASSERT_OK(sd_event_new(&event)); - bus_fd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - ASSERT_OK_ERRNO(bus_fd); + ASSERT_OK(bus_fd = sd_fiber_accept(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); log_debug("Accepted server connection"); @@ -129,13 +126,13 @@ static void* thread_server(void *p) { log_debug("Server done"); - return NULL; + return 0; } -static void* thread_client1(void *p) { +static int client1(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; - const char *path = p, *t; + const char *path = ASSERT_PTR(userdata), *t; log_debug("Initializing client1"); @@ -151,59 +148,65 @@ static void* thread_client1(void *p) { log_debug("Client1 done"); - return NULL; -} - -static int client2_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - ASSERT_OK_ZERO(sd_bus_message_is_method_error(m, NULL)); - ASSERT_OK(sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 0)); return 0; } -static void* thread_client2(void *p) { +static int client2(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *path = p, *t; + const char *path = ASSERT_PTR(userdata), *t; log_debug("Initializing client2"); - ASSERT_OK(sd_event_new(&event)); ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_description(bus, "client2")); t = strjoina("unix:path=", path); ASSERT_OK(sd_bus_set_address(bus, t)); ASSERT_OK(sd_bus_set_watch_bind(bus, true)); - ASSERT_OK(sd_bus_attach_event(bus, event, 0)); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_start(bus)); - ASSERT_OK(sd_bus_call_method_async(bus, NULL, "foo.bar", "/foo", "foo.TestInterface", "Foobar", client2_callback, NULL, NULL)); - - ASSERT_OK(sd_event_loop(event)); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + ASSERT_OK(sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Foobar", NULL, &m, NULL)); + ASSERT_OK_ZERO(sd_bus_message_is_method_error(m, NULL)); log_debug("Client2 done"); - return NULL; + return 0; } -static void request_exit(const char *path) { +typedef struct RequestExitArgs { + const char *path; + sd_future *client1; + sd_future *client2; +} RequestExitArgs; + +static int request_exit(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + RequestExitArgs *args = ASSERT_PTR(userdata); const char *t; + /* Wait for all client fibers to complete before requesting exit */ + ASSERT_OK(sd_fiber_await(args->client1)); + ASSERT_OK(sd_fiber_await(args->client2)); + ASSERT_OK(sd_bus_new(&bus)); - t = strjoina("unix:path=", path); + t = strjoina("unix:path=", args->path); ASSERT_OK(sd_bus_set_address(bus, t)); ASSERT_OK(sd_bus_set_watch_bind(bus, true)); ASSERT_OK(sd_bus_set_description(bus, "request-exit")); ASSERT_OK(sd_bus_start(bus)); ASSERT_OK(sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Exit", NULL, NULL, NULL)); + + return 0; } int main(int argc, char *argv[]) { _cleanup_(rm_rf_physical_and_freep) char *d = NULL; - pthread_t server, client1, client2; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client1 = NULL, *f_client2 = NULL, *f_exit = NULL; char *path; test_setup_logging(LOG_DEBUG); @@ -214,16 +217,27 @@ int main(int argc, char *argv[]) { path = strjoina(d, "/this/is/a/socket"); - ASSERT_OK(-pthread_create(&server, NULL, thread_server, path)); - ASSERT_OK(-pthread_create(&client1, NULL, thread_client1, path)); - ASSERT_OK(-pthread_create(&client2, NULL, thread_client2, path)); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - ASSERT_OK(-pthread_join(client1, NULL)); - ASSERT_OK(-pthread_join(client2, NULL)); + ASSERT_OK(sd_fiber_new(e, "server", server, path, /* destroy= */ NULL, &f_server)); - request_exit(path); + ASSERT_OK(sd_fiber_new(e, "client-1", client1, path, /* destroy= */ NULL, &f_client1)); + ASSERT_OK(sd_fiber_new(e, "client-2", client2, path, /* destroy= */ NULL, &f_client2)); - ASSERT_OK(-pthread_join(server, NULL)); + RequestExitArgs args = { + .path = path, + .client1 = f_client1, + .client2 = f_client2, + }; + ASSERT_OK(sd_fiber_new(e, "request-exit", request_exit, &args, /* destroy= */ NULL, &f_exit)); - return 0; + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f_client1)); + ASSERT_OK(sd_future_result(f_client2)); + ASSERT_OK(sd_future_result(f_exit)); + ASSERT_OK(sd_future_result(f_server)); + + return EXIT_SUCCESS; } diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h index 5c11ca8ae5b71..036bda3fe47e9 100644 --- a/src/systemd/sd-bus-vtable.h +++ b/src/systemd/sd-bus-vtable.h @@ -44,6 +44,7 @@ __extension__ enum { SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, SD_BUS_VTABLE_SENSITIVE = 1ULL << 8, /* covers both directions: method call + reply */ SD_BUS_VTABLE_ABSOLUTE_OFFSET = 1ULL << 9, + /* Bit 10 is reserved for the private SD_BUS_VTABLE_METHOD_FIBER flag (see bus-internal.h). */ _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 }; From 14605a317cbb3bc8eae3214ab03b927e66a74950 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 14 Apr 2026 08:54:49 +0000 Subject: [PATCH 2086/2155] sd-varlink: make sd-varlink fiber-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add varlink_server_bind_fiber() and varlink_server_bind_fiber_many() in varlink-util.{c,h} for registering a method handler that should run on a dedicated fiber per dispatch. The fiber-bound methods live in a separate s->fiber_methods map alongside the regular s->methods; bind_internal()/bind_many_internal() are factored out so the regular and fiber bind variants share their parsing/insertion code. Registering the same method in both maps is rejected because the dispatcher consults the regular map first and would otherwise silently shadow the fiber binding. varlink_dispatch_fiber() builds a VarlinkFiberData (refs to the connection, parameters, and method name), spawns a fiber via sd_fiber_new(), and makes the future floating so the fiber self-manages its lifetime — neither the dispatcher nor the connection has to track it. The fiber's priority is set to one below the connection's quit event source so that on graceful shutdown the fiber's exit handler fires (and runs its cleanup) before varlink's quit_callback() closes the connection underneath it; this is what lets a fiber-bound handler reply or flush its sentinel on a still-open connection during shutdown. The connection state transitions are reordered so they happen before the fiber spawn rather than after the synchronous callback returns: the fiber runs after dispatch has already moved past PROCESSING, which matches the behaviour expected for a deferred reply (the fiber may either reply immediately, or stash the connection and reply later, in which case the post-callback logic treats it as a PENDING_METHOD). Note that all the synchronous varlink APIs (sd_varlink_call() and friends) already behave properly when on a fiber because they call json_stream_wait() which calls ppoll_usec() which we already fixed to suspend when called from a fiber. The client/server varlink tests are migrated to fibers (threads → mock server fibers on the same event loop) to exercise the new paths. --- src/libsystemd/sd-varlink/sd-varlink.c | 301 ++++++++++++--- src/libsystemd/sd-varlink/test-varlink.c | 362 ++++++++++++++----- src/libsystemd/sd-varlink/varlink-internal.h | 3 +- src/libsystemd/sd-varlink/varlink-util.h | 4 + 4 files changed, 529 insertions(+), 141 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 8e43e38800bde..af9dd30973b4d 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -6,6 +6,7 @@ #include "sd-daemon.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -37,6 +38,7 @@ #include "varlink-internal.h" #include "varlink-io.systemd.h" #include "varlink-org.varlink.service.h" +#include "varlink-util.h" #define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U #define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 128U @@ -956,6 +958,178 @@ static int generic_method_get_interface_description( SD_JSON_BUILD_PAIR_STRING("description", text)); } +static int varlink_dispatch_sentinel(sd_varlink *v) { + int r; + + assert(v); + assert(v->sentinel); + + if (v->previous) { + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); + if (r >= 0) { + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; + /* Mirror sd_varlink_reply()'s post-enqueue state machine: PENDING_* means we're + * outside the dispatch stack frame (e.g. called from varlink_fiber_entry after + * the fiber returned), so we go straight to IDLE_SERVER ourselves. PROCESSING_* + * means we're inside varlink_dispatch_method(), which will transition us. */ + if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) { + varlink_clear_current(v); + varlink_set_state(v, VARLINK_IDLE_SERVER); + } else + varlink_set_state(v, VARLINK_PROCESSED_METHOD); + } + + return r; + } + + char *sentinel = TAKE_PTR(v->sentinel); + + /* Propagate the sentinel to the client if one was configured and no replies were enqueued by + * the callback. */ + if (sentinel == POINTER_MAX) + r = sd_varlink_reply(v, NULL); + 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 (IN_SET(v->state, VARLINK_PROCESSED_METHOD, VARLINK_IDLE_SERVER)) + r = 0; + } + + if (sentinel != POINTER_MAX) + free(sentinel); + + return r; +} + +typedef struct VarlinkFiberData { + sd_varlink *link; + sd_json_variant *parameters; + sd_varlink_method_flags_t flags; + void *userdata; + sd_varlink_method_t callback; +} VarlinkFiberData; + +static VarlinkFiberData* varlink_fiber_data_free(VarlinkFiberData *d) { + if (!d) + return NULL; + + sd_json_variant_unref(d->parameters); + sd_varlink_unref(d->link); + return mfree(d); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkFiberData*, varlink_fiber_data_free); + +static void varlink_fiber_data_destroy(void *userdata) { + varlink_fiber_data_free(userdata); +} + +static int varlink_fiber_entry(void *userdata) { + VarlinkFiberData *d = ASSERT_PTR(userdata); + sd_varlink *v = d->link; + int r; + + r = d->callback(v, d->parameters, d->flags, d->userdata); + + /* The fiber runs after varlink_dispatch_method() has already transitioned the state from + * VARLINK_PROCESSING_METHOD{,_MORE} to VARLINK_PENDING_METHOD{,_MORE}, so that's what we match + * here to decide whether the call still needs a reply. Any other state (e.g. IDLE_SERVER after + * the callback replied, or DISCONNECTED after sd_varlink_close()) means no fixup is needed. */ + if (!IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + return r; + + if (r < 0) { + varlink_log_errno(v, r, "Fiber returned error: %m"); + + /* Propagate error to the client if the method call remains unanswered. */ + r = sd_varlink_error_errno(v, r); + } else if (v->sentinel) { + r = varlink_dispatch_sentinel(v); + if (r < 0) + varlink_log_errno(v, r, "Failed to process sentinel: %m"); + } else if (v->n_ref <= 2) { + /* Bare minimum refs (server + fiber data) means the connection wasn't stashed + * to reply later, so the fiber was supposed to reply itself but didn't. */ + r = varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Fiber returned without enqueuing a reply or stashing connection, failing."); + goto fail; + } else + r = 0; + + /* If we didn't manage to enqueue a response, then fail the connection completely. */ + if (r < 0 && IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + goto fail; + + return r; + +fail: + varlink_set_state(v, VARLINK_PROCESSING_FAILURE); + varlink_dispatch_local_error(v, SD_VARLINK_ERROR_PROTOCOL); + sd_varlink_close(v); + + return r; +} + +static int varlink_dispatch_fiber(sd_varlink *v, const char *method, sd_varlink_method_t callback, sd_json_variant *parameters, sd_varlink_method_flags_t flags) { + int r; + + assert(v); + assert(v->server); + assert(method); + assert(callback); + + if (!v->server->event) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EDEADLK), + "Cannot dispatch fiber method without event loop."); + + _cleanup_(varlink_fiber_data_freep) VarlinkFiberData *d = new(VarlinkFiberData, 1); + if (!d) + return log_oom_debug(); + + *d = (VarlinkFiberData) { + .link = sd_varlink_ref(v), + .parameters = sd_json_variant_ref(parameters), + .flags = flags, + .userdata = v->userdata, + .callback = callback, + }; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_fiber_new(v->server->event, method, varlink_fiber_entry, d, varlink_fiber_data_destroy, &f); + if (r < 0) + return r; + + TAKE_PTR(d); /* The fiber owns the data now. */ + + /* Run the fiber at a higher priority than the connection's quit event source, so that on event + * loop exit the fiber's exit source (which cancels it and drives its cleanup) fires before + * varlink's quit_callback closes the connection. This lets a fiber handler reply with an error + * or flush its sentinel on a still-open connection during graceful shutdown. */ + int64_t priority; + r = sd_event_source_get_priority(v->quit_event_source, &priority); + if (r < 0) + return r; + + r = sd_future_set_priority(f, priority > INT64_MIN ? priority - 1 : priority); + if (r < 0) + return r; + + /* Hand the future's lifetime over to the event loop: it'll auto-unref on resolve. */ + r = sd_fiber_set_floating(f, true); + if (r < 0) + return r; + + return 0; +} + static int varlink_dispatch_method(sd_varlink *v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; sd_varlink_method_flags_t flags = 0; @@ -1053,7 +1227,13 @@ static int varlink_dispatch_method(sd_varlink *v) { v->protocol_upgrade || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_UPGRADABLE)); /* First consult user supplied method implementations */ + bool is_fiber = false; callback = hashmap_get(v->server->methods, method); + if (!callback) { + callback = hashmap_get(v->server->fiber_methods, method); + if (callback) + is_fiber = true; + } if (!callback) { if (streq(method, "org.varlink.service.GetInfo")) callback = generic_method_get_info; @@ -1105,7 +1285,13 @@ static int varlink_dispatch_method(sd_varlink *v) { } if (!invalid) { - r = callback(v, parameters, flags, v->userdata); + if (is_fiber) + /* Spawn a fiber to run the callback. The VarlinkFiberData takes a ref on the + * connection (bumping n_ref above 2), so the post-callback logic below treats + * this as a deferred reply and moves state to PENDING_METHOD. */ + r = varlink_dispatch_fiber(v, method, callback, parameters, flags); + else + r = callback(v, parameters, flags, v->userdata); if (VARLINK_STATE_WANTS_REPLY(v->state)) { if (r < 0) { varlink_log_errno(v, r, "Callback for '%s' returned error: %m", method); @@ -1114,37 +1300,7 @@ static int varlink_dispatch_method(sd_varlink *v) { * if the method call remains unanswered. */ r = sd_varlink_error_errno(v, r); } else if (v->sentinel) { - if (v->previous) { - r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); - if (r >= 0) { - 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 { - char *sentinel = TAKE_PTR(v->sentinel); - - /* Propagate the sentinel to the client if one was configured - * and no replies were enqueued by the callback. */ - if (sentinel == POINTER_MAX) - r = sd_varlink_reply(v, NULL); - 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); - } + r = varlink_dispatch_sentinel(v); if (r < 0) varlink_log_errno(v, r, "Failed to process sentinel for method '%s': %m", method); } else { @@ -2596,8 +2752,12 @@ _public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { 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), + /* This has to be called during a callback, and not after it has exited. The PENDING states + * apply to fiber callbacks, which run after varlink_dispatch_method() has already transitioned + * the state from PROCESSING to PENDING. */ + assert_return(IN_SET(v->state, + VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE, + VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE), -EUCLEAN); char *s = NULL; @@ -2899,7 +3059,11 @@ static sd_varlink_server* varlink_server_destroy(sd_varlink_server *s) { while ((m = hashmap_steal_first_key(s->methods))) free(m); + while ((m = hashmap_steal_first_key(s->fiber_methods))) + free(m); + hashmap_free(s->methods); + hashmap_free(s->fiber_methods); hashmap_free(s->interfaces); hashmap_free(s->symbols); hashmap_free(s->by_uid); @@ -3590,23 +3754,32 @@ static bool varlink_symbol_in_interface(const char *method, const char *interfac return !strchr(p+1, '.'); } -_public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { +static int varlink_server_bind_internal(sd_varlink_server *s, Hashmap **methods, const char *method, sd_varlink_method_t callback) { _cleanup_free_ char *m = NULL; int r; - assert_return(s, -EINVAL); - assert_return(method, -EINVAL); - assert_return(callback, -EINVAL); + assert(s); + assert(methods); + assert(method); + assert(callback); if (varlink_symbol_in_interface(method, "org.varlink.service") || varlink_symbol_in_interface(method, "io.systemd")) return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); + /* Refuse to register the same method in both the regular and fiber method maps: the dispatcher + * always consults methods first and would silently ignore a shadowed fiber_methods entry (or vice + * versa), hiding the misconfiguration. */ + Hashmap *other = methods == &s->methods ? s->fiber_methods : s->methods; + if (hashmap_contains(other, method)) + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), + "Method '%s' is already bound in the other method map.", method); + m = strdup(method); if (!m) return log_oom_debug(); - r = hashmap_ensure_put(&s->methods, &string_hash_ops, m, callback); + r = hashmap_ensure_put(methods, &string_hash_ops, m, callback); if (r == -ENOMEM) return log_oom_debug(); if (r < 0) @@ -3617,13 +3790,12 @@ _public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *met return 0; } -_public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, ...) { - va_list ap; +static int varlink_server_bind_many_internal(sd_varlink_server *s, Hashmap **methods, va_list ap) { int r = 0; - assert_return(s, -EINVAL); + assert(s); + assert(methods); - va_start(ap, s); for (;;) { sd_varlink_method_t callback; const char *method; @@ -3634,10 +3806,51 @@ _public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, . callback = va_arg(ap, sd_varlink_method_t); - r = sd_varlink_server_bind_method(s, method, callback); + r = varlink_server_bind_internal(s, methods, method, callback); if (r < 0) break; } + + return r; +} + +_public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { + assert_return(s, -EINVAL); + assert_return(method, -EINVAL); + assert_return(callback, -EINVAL); + + return varlink_server_bind_internal(s, &s->methods, method, callback); +} + +_public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, ...) { + va_list ap; + int r; + + assert_return(s, -EINVAL); + + va_start(ap, s); + r = varlink_server_bind_many_internal(s, &s->methods, ap); + va_end(ap); + + return r; +} + +int varlink_server_bind_fiber(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { + assert_return(s, -EINVAL); + assert_return(method, -EINVAL); + assert_return(callback, -EINVAL); + + return varlink_server_bind_internal(s, &s->fiber_methods, method, callback); +} + +int varlink_server_bind_fiber_many_internal(sd_varlink_server *s, ...) { + va_list ap; + int r; + + assert_return(s, -EINVAL); + + va_start(ap, s); + r = varlink_server_bind_many_internal(s, &s->fiber_methods, ap); va_end(ap); return r; diff --git a/src/libsystemd/sd-varlink/test-varlink.c b/src/libsystemd/sd-varlink/test-varlink.c index cca859c2f6867..31b7cb46f9205 100644 --- a/src/libsystemd/sd-varlink/test-varlink.c +++ b/src/libsystemd/sd-varlink/test-varlink.c @@ -2,13 +2,13 @@ #include #include -#include #include #include #include #include #include "sd-event.h" +#include "sd-future.h" #include "sd-json.h" #include "sd-varlink.h" @@ -216,7 +216,12 @@ static void flood_test(const char *address) { /* Block the main event loop while we flood */ ASSERT_OK_EQ_ERRNO(write(block_write_fd, &x, sizeof(x)), (ssize_t) sizeof(x)); - ASSERT_OK(sd_event_default(&e)); + /* Create a fresh event loop for the flood test — we can't reuse the default event because the + * main test (and the fiber we're running in) is already running it, and sd_event_loop() asserts + * the event is in the INITIAL state. Exit-on-idle so the nested loop terminates once the + * overload reply has been received and all other work is quiesced. */ + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); /* Flood the server with connections */ ASSERT_NOT_NULL(connections = new0(sd_varlink*, OVERLOAD_CONNECTIONS)); @@ -251,7 +256,7 @@ static void flood_test(const char *address) { connections[k] = sd_varlink_unref(connections[k]); } -static void *thread(void *arg) { +static int client_fiber(void *arg) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *i = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *wrong = NULL; @@ -263,7 +268,7 @@ static void *thread(void *arg) { SD_JSON_BUILD_PAIR_INTEGER("b", 99)))); ASSERT_OK(sd_varlink_connect_address(&c, arg)); - ASSERT_OK(sd_varlink_set_description(c, "thread-client")); + ASSERT_OK(sd_varlink_set_description(c, "fiber-client")); ASSERT_OK(sd_varlink_set_allow_fd_passing_input(c, true)); ASSERT_OK(sd_varlink_set_allow_fd_passing_output(c, true)); @@ -321,7 +326,7 @@ static void *thread(void *arg) { ASSERT_OK(sd_varlink_send(c, "io.test.Done", NULL)); - return NULL; + return 0; } static int block_fd_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -348,8 +353,8 @@ TEST(chat) { _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; _cleanup_close_pair_ int block_fds[2] = EBADF_PAIR; - pthread_t t; const char *sp; ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); @@ -388,11 +393,11 @@ TEST(chat) { ASSERT_OK(sd_varlink_attach_event(c, e, 0)); - ASSERT_OK(-pthread_create(&t, NULL, thread, (void*) sp)); + ASSERT_OK(sd_fiber_new(e, "client", client_fiber, (void*) sp, /* destroy= */ NULL, &f)); ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(t, NULL)); + ASSERT_OK(sd_future_result(f)); } static int method_invalid(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { @@ -618,6 +623,173 @@ TEST(sentinel_oneway) { ASSERT_OK(sd_event_loop(e)); } +static int method_fiber_sentinel_error(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Set an error sentinel from a fiber callback and return without sending a reply. The sentinel + * error should still be propagated by the fiber's post-callback logic, even though the varlink + * state has already been transitioned to VARLINK_PENDING_METHOD by the time the fiber runs. */ + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); + return 0; +} + +TEST(fiber_sentinel_error) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberSentinelError", method_fiber_sentinel_error)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_sentinel_error)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberSentinelError", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int method_fiber_errno(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Return a negative errno without sending a reply. The fiber's post-callback logic should + * convert this into a SD_VARLINK_ERROR_SYSTEM reply. */ + return -ENOSYS; +} + +static int reply_fiber_errno(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_SYSTEM); + ASSERT_EQ(sd_json_variant_integer(sd_json_variant_by_key(parameters, "errno")), ENOSYS); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_errno) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberErrno", method_fiber_errno)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_errno)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberErrno", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int method_fiber_no_reply(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Return success without replying and without stashing a ref. The fiber's post-callback + * logic should detect this and fail the connection. */ + return 0; +} + +static int reply_fiber_no_reply(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_DISCONNECTED); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_no_reply) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberNoReply", method_fiber_no_reply)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_no_reply)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberNoReply", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int fiber_stashed_deferred_reply(sd_event_source *s, void *userdata) { + _cleanup_(sd_varlink_unrefp) sd_varlink *link = ASSERT_PTR(userdata); + + sd_event_source_disable_unref(s); + ASSERT_OK(sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", "stashed"))); + return 0; +} + +static int method_fiber_stash(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Stash a ref on the connection so n_ref > 2 when the fiber returns, and reply later from a + * deferred event source. The fiber's post-callback logic should see the extra ref and treat + * this as a valid deferred-reply case instead of failing the connection. */ + sd_event_source *source; + + ASSERT_OK(sd_event_add_defer(sd_varlink_get_event(link), &source, fiber_stashed_deferred_reply, sd_varlink_ref(link))); + ASSERT_OK(sd_event_source_set_enabled(source, SD_EVENT_ONESHOT)); + return 0; +} + +static int reply_fiber_stash(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_NULL(error_id); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(parameters, "result")), "stashed"); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_stash) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberStash", method_fiber_stash)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_stash)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberStash", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + static int method_with_fd_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; @@ -768,8 +940,9 @@ static int method_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - /* 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. */ + /* After upgrade, do raw I/O: read until the client shuts down its write side (giving us a clean + * EOF), reverse what we got, and write it back. Use suspending I/O so other fibers (the client) + * can make progress while we're waiting on the socket. */ char buf[64] = {}; ssize_t n = ASSERT_OK(loop_read(input_fd, buf, sizeof(buf) - 1, /* do_poll= */ true)); ASSERT_GT(n, 0); @@ -789,12 +962,10 @@ static int method_upgrade_without_flag(sd_varlink *link, sd_json_variant *parame /* 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) { +static int upgrade_client_fiber(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; @@ -827,14 +998,15 @@ static void *upgrade_thread(void *arg) { ASSERT_OK(sd_varlink_call(c2, "io.test.UpgradeWithoutFlag", /* parameters= */ NULL, &o, &error_id)); ASSERT_NULL(error_id); - return NULL; + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; } 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; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; const char *sp; ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); @@ -844,31 +1016,23 @@ TEST(upgrade) { 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)); + /* The method does raw I/O on the upgraded socket — bind it as a fiber method so it can + * suspend on loop_read()/loop_write() and the client fiber can make progress concurrently. */ + ASSERT_OK(varlink_server_bind_fiber(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)); + ASSERT_OK(sd_fiber_new(e, "upgrade-client", upgrade_client_fiber, (void*) sp, /* destroy= */ NULL, &f)); - /* Run the event loop until no more connections (the thread will disconnect when done) */ + /* Run the event loop. Exits on idle once the client fiber completes and all server connections + * have been torn down. */ ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(t, NULL)); + ASSERT_OK(sd_future_result(f)); } -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) { +static int upgrade_pipelining_client_fiber(void *arg) { union sockaddr_union sa = {}; _cleanup_close_ int fd = -EBADF; @@ -895,8 +1059,8 @@ static void *upgrade_pipelining_thread(void *arg) { /* 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. */ + /* 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); @@ -911,14 +1075,15 @@ static void *upgrade_pipelining_thread(void *arg) { ASSERT_EQ(raw_size, strlen(raw_payload)); ASSERT_STREQ(strndupa_safe(raw, raw_size), "!denilepiP"); - return NULL; + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; } 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; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; const char *sp; ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); @@ -926,25 +1091,23 @@ TEST(upgrade_pipelining) { 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_new(&s, SD_VARLINK_SERVER_UPGRADABLE)); 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)); + /* method_upgrade does raw I/O on the upgraded socket, so bind as a fiber method. */ + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.Upgrade", method_upgrade)); 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_fiber_new(e, "upgrade-pipelining-client", upgrade_pipelining_client_fiber, (void*) sp, /* destroy= */ NULL, &f)); ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(t, NULL)); + ASSERT_OK(sd_future_result(f)); } 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) { @@ -953,20 +1116,6 @@ static int method_execute_dir_ping(sd_varlink *link, sd_json_variant *parameters 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); @@ -977,51 +1126,28 @@ static int execute_dir_reply(sd_varlink *link, sd_json_variant *parameters, cons 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]; +typedef struct ExecDirClientArgs { + const char *tmpdir; + size_t n_servers; + size_t *reply_count; +} ExecDirClientArgs; - _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)); - } +static int execute_dir_client_fiber(void *arg) { + ExecDirClientArgs *a = ASSERT_PTR(arg); ASSERT_OK_EQ(varlink_execute_directory( - tmpdir, + a->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, 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); - } + a->reply_count), (ssize_t) a->n_servers); + ASSERT_EQ(*a->reply_count, a->n_servers); /* 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_OK(asprintf(&nope, "%s/does-not-exist", a->tmpdir)); ASSERT_FAIL(varlink_execute_directory( nope, "io.test.ExecDirPing", @@ -1029,13 +1155,13 @@ TEST(execute_directory) { /* more= */ false, /* timeout_usec= */ USEC_INFINITY, execute_dir_reply, - &reply_count)); + a->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")); + _cleanup_free_ char *empty = ASSERT_PTR(path_join(a->tmpdir, "empty")); ASSERT_OK_ERRNO(mkdir(empty, 0755)); - size_t count_before = reply_count; + size_t count_before = *a->reply_count; ASSERT_OK_ZERO(varlink_execute_directory( empty, "io.test.ExecDirPing", @@ -1043,8 +1169,52 @@ TEST(execute_directory) { /* more= */ false, /* timeout_usec= */ USEC_INFINITY, execute_dir_reply, - &reply_count)); - ASSERT_EQ(reply_count, count_before); + a->reply_count)); + ASSERT_EQ(*a->reply_count, count_before); + + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; +} + +TEST(execute_directory) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = 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)); + + ASSERT_OK(sd_event_new(&e)); + + 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(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_listen_address(eds->server, j, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(eds->server, e, 0)); + } + + ExecDirClientArgs args = { + .tmpdir = tmpdir, + .n_servers = ELEMENTSOF(names), + .reply_count = &reply_count, + }; + ASSERT_OK(sd_fiber_new(e, "execute-dir-client", execute_dir_client_fiber, &args, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); + + FOREACH_ELEMENT(eds, servers) + eds->server = sd_varlink_server_unref(eds->server); } #define CTRUNC_N_FDS 64U @@ -1075,7 +1245,7 @@ static int reply_ctrunc(sd_varlink *link, sd_json_variant *parameters, const cha TEST(ctrunc) { int r; - + _cleanup_(sd_event_unrefp) sd_event *e = NULL; ASSERT_OK(sd_event_default(&e)); diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 32d6d5983a75f..beec5be42c709 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -135,7 +135,8 @@ typedef struct sd_varlink_server { LIST_HEAD(VarlinkServerSocket, sockets); - Hashmap *methods; /* Fully qualified symbol name of a method → VarlinkMethod */ + Hashmap *methods; /* Fully qualified symbol name of a method → sd_varlink_method_t */ + Hashmap *fiber_methods; /* Fully qualified symbol name of a fiber method → sd_varlink_method_t */ Hashmap *interfaces; /* Fully qualified interface name → VarlinkInterface* */ Hashmap *symbols; /* Fully qualified symbol name of method/error → VarlinkSymbol* */ sd_varlink_connect_t connect_callback; diff --git a/src/libsystemd/sd-varlink/varlink-util.h b/src/libsystemd/sd-varlink/varlink-util.h index d6ecb03c54533..d5765ca2c72f1 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -19,6 +19,10 @@ int varlink_many_notifyb(Set *s, ...); int varlink_many_reply(Set *s, sd_json_variant *parameters); int varlink_many_error(Set *s, const char *error_id, sd_json_variant *parameters); +int varlink_server_bind_fiber(sd_varlink_server *s, const char *method, sd_varlink_method_t callback); +int varlink_server_bind_fiber_many_internal(sd_varlink_server *s, ...); +#define varlink_server_bind_fiber_many(s, ...) varlink_server_bind_fiber_many_internal(s, __VA_ARGS__, NULL) + int varlink_set_info_systemd(sd_varlink_server *server); int varlink_server_new( From 37679d79a8f4f18b5796f5904bdc47da4c9f29ad Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 09:49:02 +0000 Subject: [PATCH 2087/2155] qmp-client: add fiber-aware call paths The synchronous qmp_client_call() pumps the event loop until its reply arrives, pinning the parsed reply on c->current so it can hand out borrowed pointers to the caller. That model only fits one in-flight sync call: a second qmp_client_call() on the same client clears c->current before issuing its own send, invalidating the first caller's borrowed pointers. On a single-threaded event loop that was fine, but with fibers two concurrent calls on the same client can interleave through the pump (json_stream_wait() suspends the running fiber) and trample each other. To fix this, make qmp_client_call() detect when it's running on a fiber whose event loop matches the client and transparently delegate to qmp_client_call_suspend(), which makes use of a new QmpFuture to allow multiple concurrent calls to qmp_client_call(). To make this work concurrently, we also change qmp_client_call() to hand out references and copies of errors so that we don't have to store the borrowed pointers we hand out in the QmpClient struct. --- src/shared/qmp-client.c | 233 +++++++++++++++++++++++++++++++++---- src/shared/qmp-client.h | 16 ++- src/test/test-qmp-client.c | 4 +- 3 files changed, 224 insertions(+), 29 deletions(-) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index 41b0c6dd57034..8de903de1ad8c 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-event.h" +#include "sd-future.h" #include "sd-json.h" #include "alloc-util.h" @@ -226,19 +227,23 @@ static int qmp_extract_response_id(sd_json_variant *v, uint64_t *ret) { 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) { +/* Returns 0 on success (ret_result = freshly reffed "return" value), -EIO on QMP error + * (ret_error_desc set to a freshly allocated string). Caller owns both outputs. */ +static int qmp_parse_response(sd_json_variant *v, sd_json_variant **ret_result, char **reterr_error_desc) { const char *desc; desc = qmp_extract_error_description(v); if (desc) { - if (reterr_desc) - *reterr_desc = desc; + if (reterr_error_desc) { + *reterr_error_desc = strdup(desc); + if (!*reterr_error_desc) + return -ENOMEM; + } return -EIO; } if (ret_result) - *ret_result = sd_json_variant_by_key(v, "return"); + *ret_result = sd_json_variant_ref(sd_json_variant_by_key(v, "return")); return 0; } @@ -273,8 +278,8 @@ 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(QmpClient *c) { - sd_json_variant *result = NULL; - const char *desc = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *desc = NULL; uint64_t id; int error, r; @@ -318,8 +323,8 @@ static int qmp_client_dispatch(QmpClient *c) { } /* Synchronous slot (no callback): leave c->current pinned so qmp_client_call() can - * 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. */ + * pick the reply up after its pump loop. 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; @@ -574,6 +579,10 @@ static void qmp_client_clear(QmpClient *c) { qmp_client_detach_event(c); qmp_client_clear_current(c); json_stream_done(&c->stream); + /* qmp_client_handle_disconnect() above drained every entry via qmp_client_fail_pending(); + * the set is borrow-only for non-floating slots, so set_free() can't safely run a + * destructor over leftovers — enforce the drain invariant instead. */ + assert(set_isempty(c->slots)); c->slots = set_free(c->slots); } @@ -745,7 +754,7 @@ 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. If ret_slot + * a callback, so qmp_client_call() can pick the reply up after its pump loop. 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( @@ -810,21 +819,193 @@ int qmp_client_invoke( return qmp_client_send(c, command, args, callback, userdata, ret_slot); } +typedef struct QmpFuture { + QmpSlot *slot; /* owned, non-floating; NULL once disconnected */ + sd_json_variant *result; + char *error_desc; +} QmpFuture; + +static void* qmp_future_alloc(void) { + return new0(QmpFuture, 1); +} + +static void qmp_future_free(sd_future *f) { + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + qmp_slot_unref(qf->slot); + sd_json_variant_unref(qf->result); + free(qf->error_desc); + free(qf); +} + +static int qmp_future_cancel(sd_future *f) { + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + /* Drop the pending slot so dispatch_reply won't try to fire our callback (and touch + * freed memory) when the reply eventually arrives. */ + qf->slot = qmp_slot_unref(qf->slot); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops qmp_call_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = qmp_future_alloc, + .free = qmp_future_free, + .cancel = qmp_future_cancel, +}; + +static int qmp_future_callback( + QmpClient *c, + sd_json_variant *result, + const char *desc, + int error, + void *userdata) { + + sd_future *f = ASSERT_PTR(userdata); + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(f)); + int r; + + assert(result || desc || error); + + if (result) + qf->result = sd_json_variant_ref(result); + if (desc) { + qf->error_desc = strdup(desc); + if (!qf->error_desc) + /* No usable reply payload to surface — propagate as transport-style + * failure so suspend() / sd_future_result() see the OOM. */ + return sd_future_resolve(f, -ENOMEM); + } + + /* Resolve with 0 on a success reply and -EIO on a QMP-level error (matching the synchronous + * path's errno for the desc-without-ret_error_desc case), so a caller awaiting the future + * learns about call failures from the resolution value alone. The reply payload itself + * (result or error_desc) is always stashed on the QmpFuture so future_get_qmp_reply() can + * hand the description string back on top of the bare -EIO. With no reply at all (transport + * failure, disconnect), resolve with the propagated transport errno; cancellation surfaces + * as -ECANCELED via qmp_future_cancel(). */ + if (result) + r = 0; + else if (desc) + r = -EIO; + else + r = error; + + return sd_future_resolve(f, r); +} + +int qmp_client_call_future( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_future **ret) { + + int r; + + assert(c); + assert(command); + assert(ret); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&qmp_call_future_ops, &f); + if (r < 0) + return r; + + QmpFuture *qf = sd_future_get_private(f); + + r = qmp_client_send(c, command, args, qmp_future_callback, f, &qf->slot); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} + +/* Extract the reply from a resolved qmp_client_call_future(). Returns 1 on success (with + * *ret_result a fresh reference the caller unrefs), -EIO on a QMP-level error (with the detail + * description copied into *ret_error_desc when the caller passed one to receive it), and the + * future's negative resume errno when no reply landed at all (transport failure / cancellation). + */ +int future_get_qmp_reply(sd_future *f, sd_json_variant **ret_result, char **reterr_error_desc) { + assert(f); + assert(sd_future_get_ops(f) == &qmp_call_future_ops); + assert(sd_future_state(f) == SD_FUTURE_RESOLVED); + + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(f)); + + /* No reply at all: transport failure or cancellation — surface the future result. */ + if (!qf->result && !qf->error_desc) + return sd_future_result(f); + + if (qf->error_desc) { + if (reterr_error_desc) { + char *desc = strdup(qf->error_desc); + if (!desc) + return -ENOMEM; + *reterr_error_desc = desc; + } + return -EIO; + } + + if (reterr_error_desc) + *reterr_error_desc = NULL; + if (ret_result) + *ret_result = sd_json_variant_ref(qf->result); + + return 1; +} + +static int qmp_client_call_suspend( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + char **ret_error_desc) { + + int r; + + assert(c); + assert(command); + assert(sd_fiber_is_running()); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *call = NULL; + r = qmp_client_call_future(c, command, args, &call); + if (r < 0) + return r; + + r = sd_fiber_suspend(); + + /* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber + * cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract, + * so surface the resume error directly. When the future is resolved, future_get_qmp_reply() + * already encodes success (1), QMP-level error (-EIO with the desc captured if asked for), + * and no-reply (negative future result) — pass it through. */ + if (sd_future_state(call) != SD_FUTURE_RESOLVED) + return r; + + return future_get_qmp_reply(call, ret_result, ret_error_desc); +} + int qmp_client_call( QmpClient *c, const char *command, QmpClientArgs *args, sd_json_variant **ret_result, - const char **ret_error_desc) { + char **reterr_error_desc) { - _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *desc = NULL; 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); + /* If we're on a fiber sharing the QMP client's event loop, use the async + suspend path so + * multiple concurrent qmp_client_call() invocations across fibers don't deadlock each other + * on the process+wait pump. */ + if (sd_fiber_is_running() && qmp_client_get_event(c) == sd_fiber_get_event()) + return qmp_client_call_suspend(c, command, args, ret_result, reterr_error_desc); + + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; /* 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 @@ -855,18 +1036,24 @@ int qmp_client_call( return r; } - sd_json_variant *result = NULL; - const char *desc = NULL; - int error = qmp_parse_response(c->current, &result, &desc); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *current = TAKE_PTR(c->current); + r = qmp_parse_response(current, &result, &desc); + if (r < 0 && r != -EIO) + return r; - /* If caller doesn't ask for the error string, surface the error as the return code. */ - if (!ret_error_desc && error < 0) - return error; + /* QMP-level error: copy the description into *ret_error_desc when the caller asked for it, + * and surface the failure via the return value (matching qmp_client_call_suspend() / + * sd_bus_call()'s "negative on error" convention). */ + if (desc) { + if (reterr_error_desc) + *reterr_error_desc = TAKE_PTR(desc); + return -EIO; + } if (ret_result) - *ret_result = result; - if (ret_error_desc) - *ret_error_desc = desc; + *ret_result = TAKE_PTR(result); + if (reterr_error_desc) + *reterr_error_desc = NULL; return 1; } diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index 7dcd53355d06c..0bf435384caaf 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -68,15 +68,23 @@ 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); + char **reterr_error_desc); + +int qmp_client_call_future( + QmpClient *client, + const char *command, + QmpClientArgs *args, + sd_future **ret); + +int future_get_qmp_reply( + sd_future *f, + sd_json_variant **ret_result, + char **reterr_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); diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index befee02484588..e5e7ed3b735b1 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -646,7 +646,7 @@ TEST(qmp_client_call) { /* Successful call: borrowed result pointer is valid until the next call. */ sd_json_variant *result = NULL; - const char *error_desc = NULL; + _cleanup_free_ 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); @@ -658,7 +658,7 @@ TEST(qmp_client_call) { /* 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; + free(error_desc); ASSERT_EQ(qmp_client_call(client, "stop", NULL, &result, &error_desc), 1); ASSERT_NULL(result); ASSERT_STREQ(error_desc, "not running"); From 742733440fc5e5f30624e3aee27c76d311809261 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 09:49:10 +0000 Subject: [PATCH 2088/2155] test-qmp-client: run mock QMP servers as fibers on the shared event loop The mock servers used to be driven out-of-band: each test created a socketpair, forked a child, ran a hand-coded request/response script against the raw fd, and sent SIGTERM to tear it down. That worked but required pidref/process-util/signal plumbing in every test, two distinct execution contexts that couldn't share state, and a JsonStream attached to the mock side that pretended to be event-loop-driven while actually being driven manually via blocking reads. Now that JsonStream suspends when on a fiber, the mocks can live inside the same process and event loop as the client. Each mock is rewritten as an sd-fiber that runs alongside the client fiber: so the mock fiber yields on I/O and the event loop schedules the client in the meantime. Both sides progress cooperatively, no fork/SIGTERM/PID tracking, no manual phase tracking. Two cleanups fall out of the rewrite: - A QMP_TEST(name, mock_fn) { ... } macro encapsulates the per-test scaffolding (event loop, socketpair, mock fiber spawn, exit-on-idle shim) and injects an already-connected QmpClient *client into the test body. Each test now reads as a flat sequence of qmp_client_call() invocations against that client. - Repeated mock command/reply scripting is factored into mock_qmp_expect(), mock_qmp_reply(), mock_qmp_expect_and_reply(), mock_qmp_handshake(), and mock_qmp_query_status_running(). The greeting JSON is built with sd_json_buildo() instead of being parsed from a literal. The file shrinks from 756 to 494 lines, mostly through deletions. --- src/test/test-qmp-client.c | 801 +++++++++++++------------------------ 1 file changed, 274 insertions(+), 527 deletions(-) diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index e5e7ed3b735b1..948122720eb23 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -1,33 +1,27 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include +#include #include #include "sd-event.h" +#include "sd-future.h" #include "sd-json.h" #include "errno-util.h" #include "fd-util.h" #include "json-stream.h" -#include "pidref.h" -#include "process-util.h" #include "qmp-client.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. - * 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. */ +/* Mock QMP server runs as an sd-fiber alongside the client on the same event loop. Its + * JsonStream uses the suspending json_stream_wait()/json_stream_flush() helpers, so the mock + * fiber yields whenever it's blocked on I/O and the client makes progress in the meantime. */ -/* 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; } -/* 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; } @@ -43,9 +37,6 @@ static void mock_qmp_init(JsonStream *s, int fd) { ASSERT_OK(json_stream_connect_fd_pair(s, fd, fd)); } -/* 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; @@ -62,142 +53,137 @@ static void mock_qmp_recv(JsonStream *s, sd_json_variant **ret) { } } -/* 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) { +static void mock_qmp_send_greeting(JsonStream *s) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - ASSERT_OK(sd_json_parse(msg, 0, &v, NULL, NULL)); + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("QMP", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("qemu", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("micro", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("minor", 2), + SD_JSON_BUILD_PAIR_UNSIGNED("major", 9))))), + SD_JSON_BUILD_PAIR("capabilities", SD_JSON_BUILD_STRV(STRV_MAKE("oob"))))))); 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")); +/* Receive one command, assert it matches `expected_command`, return its id (borrowed from *cmd). */ +static sd_json_variant* mock_qmp_expect(JsonStream *s, const char *expected_command, sd_json_variant **cmd) { + 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); + return ASSERT_NOT_NULL(sd_json_variant_by_key(*cmd, "id")); +} - sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); +/* Send a reply for a previously-received command id. Passing NULL reply_data sends {}. */ +static void mock_qmp_reply(JsonStream *s, sd_json_variant *id, sd_json_variant *reply_data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL, *response = NULL; - if (!reply_data) - ASSERT_OK(sd_json_variant_new_object(&reply_obj, NULL, 0)); + if (!reply_data) { + ASSERT_OK(sd_json_build(&empty, SD_JSON_BUILD_EMPTY_OBJECT)); + reply_data = empty; + } - 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)))); + ASSERT_OK(sd_json_buildo(&response, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(reply_data)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); 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); +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; + mock_qmp_reply(s, mock_qmp_expect(s, expected_command, &cmd), reply_data); +} - sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); +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, *response = NULL; + sd_json_variant *id = mock_qmp_expect(s, expected_command, &cmd); - ASSERT_OK(sd_json_buildo( - &error_obj, + ASSERT_OK(sd_json_buildo(&response, + SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_OBJECT( 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)))); + SD_JSON_BUILD_PAIR_STRING("desc", error_desc))), + 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); +static void mock_qmp_handshake(JsonStream *s) { + mock_qmp_send_greeting(s); + mock_qmp_expect_and_reply(s, "qmp_capabilities", NULL); +} - /* Send QMP greeting */ - mock_qmp_send_literal(&s, - "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 2, \"major\": 9}}, \"capabilities\": [\"oob\"]}}"); +/* Reply to query-status with a running=true/status="running" payload. */ +static void mock_qmp_query_status_running(JsonStream *s) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - /* Accept qmp_capabilities */ - mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(s, "query-status", v); +} - /* 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(&s, "query-status", status_return); +/* Drive a mock+client pair on a single event loop. The client fiber runs as userdata=client, + * the mock fiber as userdata=fd (the server-side socket). */ +static void run_qmp_test(sd_fiber_func_t mock_fn, sd_fiber_func_t client_fn) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_future_unrefp) sd_future *client_f = NULL, *mock_f = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; - /* Accept stop */ - mock_qmp_expect_and_reply(&s, "stop", NULL); + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - /* Send a STOP event */ - mock_qmp_send_literal(&s, - "{\"event\": \"STOP\", \"timestamp\": {\"seconds\": 1234, \"microseconds\": 5678}}"); + ASSERT_OK(qmp_client_connect_fd(&client, TAKE_FD(qmp_fds[0]))); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); - /* Accept cont */ - mock_qmp_expect_and_reply(&s, "cont", NULL); + ASSERT_OK(sd_fiber_new(event, "mock", mock_fn, FD_TO_PTR(TAKE_FD(qmp_fds[1])), NULL, &mock_f)); + ASSERT_OK(sd_fiber_new(event, "client", client_fn, client, NULL, &client_f)); - /* json_stream_done() on cleanup closes our fd and signals EOF. */ - _exit(EXIT_SUCCESS); + ASSERT_OK(sd_event_loop(event)); + ASSERT_OK(sd_future_result(client_f)); + ASSERT_OK(sd_future_result(mock_f)); } -/* 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; -} +/* Define a test whose body runs as the client fiber on an event loop shared with `mock_fn`. + * The body receives `QmpClient *client` as its argument. */ +#define QMP_TEST(name, mock_fn) \ + static int test_##name##_body(QmpClient *client); \ + static int test_##name##_fiber(void *userdata) { \ + int r = test_##name##_body(userdata); \ + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); \ + return r; \ + } \ + TEST(name) { \ + run_qmp_test(mock_fn, test_##name##_fiber); \ + } \ + static int test_##name##_body(QmpClient *client) + +static int mock_qmp_basic_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_event = NULL; -/* Run the event loop until the test result callback fires. */ -static void qmp_test_wait(sd_event *event, QmpTestResult *t) { - assert(event); - assert(t); + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); - while (!t->done) - ASSERT_OK(sd_event_run(event, UINT64_MAX)); -} + mock_qmp_query_status_running(&s); + mock_qmp_expect_and_reply(&s, "stop", NULL); -static void qmp_test_result_done(QmpTestResult *t) { - assert(t); + ASSERT_OK(sd_json_buildo(&stop_event, + SD_JSON_BUILD_PAIR_STRING("event", "STOP"), + SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("seconds", 1234), + SD_JSON_BUILD_PAIR_UNSIGNED("microseconds", 5678))))); + mock_qmp_send(&s, stop_event); - sd_json_variant_unref(t->result); - free(t->error_desc); - *t = (QmpTestResult) {}; + mock_qmp_expect_and_reply(&s, "cont", NULL); + return 0; } static int test_event_callback( @@ -208,516 +194,283 @@ static int test_event_callback( 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. */ + /* Ignore the synthetic SHUTDOWN emitted when the mock closes the connection. */ 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 = ASSERT_OK(pidref_safe_fork("(mock-qmp)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); - - 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 */ +QMP_TEST(qmp_client_basic, mock_qmp_basic_fiber) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *error_desc = NULL; bool event_received = false; + qmp_client_bind_event(client, test_event_callback, &event_received); - /* Execute query-status */ - 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); + ASSERT_OK_POSITIVE(qmp_client_call(client, "query-status", NULL, &result, &error_desc)); + ASSERT_NULL(error_desc); - running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + sd_json_variant *running = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "running")); ASSERT_TRUE(sd_json_variant_boolean(running)); - - status = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "status")); + sd_json_variant *status = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "status")); ASSERT_STREQ(sd_json_variant_string(status), "running"); - qmp_test_result_done(&t); + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); + ASSERT_OK_POSITIVE(qmp_client_call(client, "cont", NULL, NULL, NULL)); - /* Execute stop */ - 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); + ASSERT_TRUE(event_received); + return 0; +} - /* Execute cont -- the STOP event should be dispatched by the IO callback */ - 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); +static int mock_qmp_eof_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; - /* Verify the STOP event was received */ - ASSERT_TRUE(event_received); + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + /* Return; _cleanup_ closes the fd → client sees EOF. */ + return 0; +} - /* 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); +QMP_TEST(qmp_client_eof, mock_qmp_eof_fiber) { + int r = qmp_client_call(client, "query-status", NULL, NULL, NULL); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + return 0; } -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; +static int mock_qmp_call_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; - ASSERT_OK(sd_event_new(&event)); - ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + + mock_qmp_query_status_running(&s); + mock_qmp_expect_and_reply_error(&s, "stop", "not running"); + mock_qmp_expect_and_reply_error(&s, "stop", "still not running"); + return 0; +} + +QMP_TEST(qmp_client_call, mock_qmp_call_fiber) { + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *error_desc = NULL; - r = ASSERT_OK(pidref_safe_fork("(mock-qmp-eof)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + /* Exercise qmp_client_call_future() + sd_fiber_await() + future_get_qmp_reply() + * directly — success path. */ + ASSERT_OK(qmp_client_call_future(client, "query-status", NULL, &f)); + ASSERT_OK(sd_fiber_await(f)); + ASSERT_OK(sd_future_result(f)); + ASSERT_OK(future_get_qmp_reply(f, &result, &error_desc)); - if (r == 0) { - _cleanup_(json_stream_done) JsonStream s = {}; + ASSERT_NULL(error_desc); + 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"); - safe_close(qmp_fds[0]); - mock_qmp_init(&s, qmp_fds[1]); + /* QMP-level error: future resolves with -EIO, sd_fiber_await() returns -EIO, and + * future_get_qmp_reply() also returns -EIO (mirroring future_get_bus_reply()) — with the + * detailed description captured via error_desc on top, and result left NULL. */ + f = sd_future_unref(f); + result = sd_json_variant_unref(result); + error_desc = mfree(error_desc); - /* Send greeting and accept capabilities, then die */ - mock_qmp_send_literal(&s, - "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + ASSERT_OK(qmp_client_call_future(client, "stop", NULL, &f)); + ASSERT_ERROR(sd_fiber_await(f), EIO); + ASSERT_ERROR(sd_future_result(f), EIO); + ASSERT_ERROR(future_get_qmp_reply(f, &result, &error_desc), EIO); - mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + ASSERT_NULL(result); + ASSERT_STREQ(error_desc, "not running"); - /* _exit() closes our fd via kernel teardown, signalling EOF to the peer. */ - _exit(EXIT_SUCCESS); - } + /* qmp_client_call() also surfaces QMP errors as -EIO, regardless of whether the caller + * passed ret_error_desc. */ + ASSERT_ERROR(qmp_client_call(client, "stop", NULL, NULL, NULL), EIO); + return 0; +} - safe_close(qmp_fds[1]); +static int mock_qmp_call_disconnect_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_cmd = NULL; - ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); - ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); - /* 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, /* ret_slot= */ NULL, "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); - } + /* Consume the stop command but don't reply — cleanup closes the fd and the client + * sees a disconnect while suspended. */ + mock_qmp_recv(&s, &stop_cmd); + return 0; +} - 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); +QMP_TEST(qmp_client_call_disconnect, mock_qmp_call_disconnect_fiber) { + int r = qmp_client_call(client, "stop", NULL, NULL, NULL); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + return 0; } -/* 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) { +static int mock_qmp_fd_fiber(void *userdata) { _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; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, *addfd_cmd = NULL, + *addfd_return = NULL; - mock_qmp_init(&s, fd); - ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, /* with_sockopt= */ true)); + mock_qmp_init(&s, PTR_TO_FD(userdata)); + ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, true)); - /* Greeting */ - mock_qmp_send_literal(&s, - "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + mock_qmp_send_greeting(&s); - /* Receive qmp_capabilities (may or may not carry the fd depending on coalescing). */ - mock_qmp_recv(&s, &cap_cmd); + /* The fd may ride with either command depending on AF_UNIX coalescing; count across both. */ + sd_json_variant *cap_id = mock_qmp_expect(&s, "qmp_capabilities", &cap_cmd); 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); + mock_qmp_reply(&s, cap_id, NULL); - 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_send(&s, cap_reply); - - /* Receive add-fd (fd may already have been consumed with cap's recv). */ - mock_qmp_recv(&s, &addfd_cmd); + sd_json_variant *addfd_id = mock_qmp_expect(&s, "add-fd", &addfd_cmd); 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, - 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_send(&s, addfd_reply); - - _exit(EXIT_SUCCESS); + ASSERT_OK(sd_json_buildo(&addfd_return, + SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("fd", 42))); + mock_qmp_reply(&s, addfd_id, addfd_return); + return 0; } -/* 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; +QMP_TEST(qmp_client_invoke_with_fd, mock_qmp_fd_fiber) { _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; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; - 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)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); - - if (r == 0) { - safe_close(qmp_fds[0]); - mock_qmp_server_fd(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)); + fd_to_pass = ASSERT_OK_ERRNO(eventfd(0, EFD_CLOEXEC)); ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); - 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_OK_POSITIVE(qmp_client_call(client, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + &result, NULL)); + ASSERT_NOT_NULL(result); + return 0; +} + +static int on_dead_peer_reply( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { - qmp_test_wait(event, &t); - ASSERT_EQ(t.error, 0); - ASSERT_NOT_NULL(t.result); - qmp_test_result_done(&t); + bool *fired = ASSERT_PTR(userdata); - /* 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); + /* Peer was closed before the write hit the wire; expect a disconnect. */ + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(error)); + *fired = true; + return 0; } -/* 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. */ +/* Verify caller-supplied fds passed through QMP_CLIENT_ARGS_FD() are closed on client teardown + * even when the peer is already dead: invoke enqueues, the queue item owns the fd, unref closes. */ TEST(qmp_client_invoke_failure_closes_fds) { _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]; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; int saved_fd_value; + bool callback_fired = false; ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + qmp_fds[1] = safe_close(qmp_fds[1]); - /* Close the peer end immediately so any write attempt sees EPIPE. */ - safe_close(qmp_fds[1]); - - 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 */ + fd_to_pass = ASSERT_OK_ERRNO(eventfd(0, EFD_CLOEXEC)); + saved_fd_value = fd_to_pass; ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); - ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_connect_fd(&client, TAKE_FD(qmp_fds[0]))); - /* 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", + ASSERT_OK(qmp_client_invoke(client, 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). */ + on_dead_peer_reply, &callback_fired)); + ASSERT_EQ(fd_to_pass, -EBADF); ASSERT_OK_ERRNO(fcntl(saved_fd_value, F_GETFD)); - /* 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); + ASSERT_ERROR_ERRNO(fcntl(saved_fd_value, F_GETFD), EBADF); + ASSERT_TRUE(callback_fired); } -/* 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) { +/* Shared mock for the two slot tests: the follow-up stop is what drives the event loop long + * enough to dispatch the query-status reply. */ +static int mock_qmp_slot_fiber(void *userdata) { _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_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + mock_qmp_query_status_running(&s); mock_qmp_expect_and_reply(&s, "stop", NULL); - - _exit(EXIT_SUCCESS); + return 0; } -/* 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; +static int nop_callback( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { - ASSERT_OK(sd_event_new(&event)); - ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + return 0; +} - 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]); +/* Tripwire for the cancel test: if it fires, the cancel didn't do its job. */ +static int tripwire_callback( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { - ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); - ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + bool *fired = ASSERT_PTR(userdata); + *fired = true; + return 0; +} - ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, on_test_result, &t)); +QMP_TEST(qmp_client_invoke_slot_lifecycle, mock_qmp_slot_fiber) { + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; - /* While in flight the slot still references its client. */ - ASSERT_NOT_NULL(slot); + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, nop_callback, NULL)); ASSERT_PTR_EQ(qmp_slot_get_client(slot), client); - qmp_test_wait(event, &t); - ASSERT_EQ(t.error, 0); - ASSERT_NOT_NULL(t.result); + /* Drive the loop via a follow-up stop; its suspending call lets both replies dispatch. */ + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); - /* Once dispatched, the slot is disconnected from the client but still owned by us. */ + /* After dispatch 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(). */ + /* Explicit out-of-order unref exercises the already-disconnected path in qmp_slot_free(). */ slot = qmp_slot_unref(slot); - ASSERT_NULL(slot); + return 0; } -/* 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 = {}; +QMP_TEST(qmp_client_invoke_slot_cancel, mock_qmp_slot_fiber) { QmpSlot *slot = NULL; - int qmp_fds[2]; - int r; + bool fired = false; - 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, tripwire_callback, &fired)); - 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. */ + /* Drop our sole ref → slot disconnects from the client's pending set. The enqueued + * query-status is still on the wire; its reply lands on an unknown id and is discarded. */ 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). */ -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_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_error(&s, "stop", "not running"); - mock_qmp_expect_and_reply_error(&s, "stop", "still not running"); - - _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; - _cleanup_free_ 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 */ - free(error_desc); - 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_(json_stream_done) JsonStream s = {}; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_cmd = 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); - - /* 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); - _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])); + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); - /* 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)); + ASSERT_FALSE(fired); + return 0; } 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. */ + /* QEMU introspection uses opaque numeric type ids ("0", "1", ...); only member names + * are the real QAPI strings. Verify we walk all object entries to find members by name. */ ASSERT_OK(sd_json_build(&schema, SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( @@ -747,10 +500,4 @@ TEST(qmp_schema_has_member) { 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); +DEFINE_TEST_MAIN(LOG_DEBUG); From 56bc502f6fc80569a2611a6bc4e279af4984c0b0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 12:37:15 +0000 Subject: [PATCH 2089/2155] math-util: round to declared FP precision consistently across architectures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add -fexcess-precision=standard so gcc inserts ISO C99 conformant rounding at assignments, casts, and returns — without it, double values on x87 happily stay at 80-bit extended precision across operations and diverge from the SSE/x86_64 behavior, making strict equality comparisons architecture-dependent. The flag doesn't fully cover x87: per gcc PR#323 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323), a function return value carried in ST(0) can arrive at the caller still at 80-bit, so a double that ought to compare equal to a same-magnitude literal picks up extra mantissa bits and doesn't. Wrap fp_equal in volatile-double temporaries to force a memory roundtrip — the only operation that reliably truncates on x87 — so its callers get consistent results regardless of how the operands were produced. Add a TEST(fp_equal) case that exercises the previously-broken pattern: a runtime 1.0/10.0 computed inside a noinline helper, returned across the function ABI boundary, then compared against the literal 0.1. Without the volatile truncation this assertion fails on 32-bit gcc. --- meson.build | 1 + src/basic/math-util.h | 18 ++++++++++++++++-- src/test/test-math-util.c | 11 +++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index c5af3b107c155..a95cf80412840 100644 --- a/meson.build +++ b/meson.build @@ -421,6 +421,7 @@ possible_common_cc_flags = [ '-Wno-string-plus-int', # clang '-fdiagnostics-show-option', + '-fexcess-precision=standard', '-fno-common', '-fstack-protector', '-fstack-protector-strong', diff --git a/src/basic/math-util.h b/src/basic/math-util.h index cac5fc31311d5..9bcd994ae25db 100644 --- a/src/basic/math-util.h +++ b/src/basic/math-util.h @@ -10,8 +10,22 @@ #define iszero_safe(x) (fpclassify(x) == FP_ZERO) /* To avoid x == y and triggering compile warning -Wfloat-equal. This returns false if one of the argument is - * NaN or infinity. One of the argument must be a floating point. */ -#define fp_equal(x, y) iszero_safe((x) - (y)) + * NaN or infinity. One of the argument must be a floating point. + * + * The volatile temporaries force a memory roundtrip, truncating any excess precision (e.g. x87's + * 80-bit register width for double arithmetic) down to the declared type. -fexcess-precision=standard + * doesn't fully cover this on x87 — a function return value carried in ST(0) can still arrive at the + * caller in 80-bit precision (see gcc PR#323), so a value that should compare equal to a + * same-magnitude literal picks up extra mantissa bits and doesn't. The memory store-and-reload is + * the one operation guaranteed to truncate. The temporaries inherit the type of the subtraction + * expression so the macro stays generic over float / double / long double rather than silently + * truncating wider arguments. */ +#define fp_equal(x, y) \ + ({ \ + volatile __typeof__((x) - (y)) _fp_x = (x); \ + volatile __typeof__((x) - (y)) _fp_y = (y); \ + iszero_safe(_fp_x - _fp_y); \ + }) /* 10^n. Exact for |n| ≤ 22; otherwise multiplies and may accumulate rounding error. Saturates to * 0.0 or +Inf outside binary64's exponent range; large |n| is capped internally so untrusted diff --git a/src/test/test-math-util.c b/src/test/test-math-util.c index 3e8a9d5ba321a..805351761f353 100644 --- a/src/test/test-math-util.c +++ b/src/test/test-math-util.c @@ -5,6 +5,15 @@ #include "math-util.h" #include "tests.h" +/* Computed at runtime via a noinline + volatile combination so the result crosses the function ABI + * boundary at the FPU's current precision (80-bit on i386/x87). Used to probe fp_equal's handling + * of excess precision — see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for why + * -fexcess-precision=standard doesn't fully cover the caller side of a function return on x87. */ +static double _noinline_ one_tenth_via_division(void) { + volatile double ten = 10.0; + return 1.0 / ten; +} + TEST(iszero_safe) { /* zeros */ assert_se(iszero_safe(0.0)); @@ -105,6 +114,8 @@ TEST(fp_equal) { assert_se( fp_equal(0, 1 / INFINITY)); assert_se( fp_equal(42 / INFINITY, 1 / -INFINITY)); assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); + + assert_se( fp_equal(one_tenth_via_division(), 0.1)); } TEST(xexp10i) { From 05a4f7755aa53e67c870c6b441d896c2bbf8fd93 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 20 May 2026 20:46:40 +0000 Subject: [PATCH 2090/2155] test-btrfs: skip info test when GET_SUBVOL_INFO ioctl is unsupported On 32-bit userspace running against a 64-bit kernel BTRFS_IOC_GET_SUBVOL_INFO returns -ENOTTY: struct btrfs_ioctl_get_subvol_info_args embeds four btrfs_ioctl_timespec values, and that timespec struct (__u64 sec; __u32 nsec) packs to 12 bytes on i386 but 16 on x86_64 due to differing __u64 alignment. sizeof(struct) is part of the ioctl cmd number via _IOR(), so the cmd emitted by 32-bit userspace doesn't match the case label compiled by the 64-bit kernel and the switch falls through to -ENOTTY. btrfs already handles this exact class of bug for BTRFS_IOC_SET_RECEIVED_SUBVOL via a btrfs_ioctl_timespec_32 struct plus a _32 cmd alias in fs/btrfs/ioctl.c, but GET_SUBVOL_INFO (added in 2018, four years after that fix) didn't get the same treatment. Until a kernel patch lands the test can't exercise the ioctl on 32-bit, so convert TEST(info) to TEST_RET(info) and return EXIT_TEST_SKIP with a clear message when -ENOTTY comes back. The other tests in the file use ioctls that already have working compat paths and remain unaffected. --- src/test/test-btrfs.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index 66cb10a4c2534..fe3cfbfd8f886 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -52,14 +52,18 @@ static int open_test_subvol(char **ret_path) { return fd; } -TEST(info) { +TEST_RET(info) { _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); BtrfsSubvolInfo info; BtrfsQuotaInfo quota; int r; - ASSERT_OK(btrfs_subvol_get_info_fd(dir_fd, 0, &info)); + r = btrfs_subvol_get_info_fd(dir_fd, 0, &info); + if (r == -ENOTTY) + return log_tests_skipped("BTRFS_IOC_GET_SUBVOL_INFO not supported " + "(missing 32-bit compat handler in kernel?)"); + ASSERT_OK(r); log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); log_info("read-only (search): %s", yes_no(info.read_only)); @@ -75,6 +79,8 @@ TEST(info) { r = ASSERT_OK(btrfs_subvol_get_read_only_fd(dir_fd)); log_info("read-only (ioctl): %s", yes_no(r)); + + return EXIT_SUCCESS; } TEST(subvol) { From 705a705e850f7671b8d420f4e6fd13cdcf42645e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 May 2026 13:29:28 +0200 Subject: [PATCH 2091/2155] include/uapi: add linux/openat2.h (#42220) We include the header through src/include/override/fcntl.h, hence we should have the latest copy of the header. (E.g. compilation fails with musl-libc-1.2.5-6.fc44.x86_64 and older kernel headers.) --- src/include/uapi/linux/openat2.h | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/include/uapi/linux/openat2.h diff --git a/src/include/uapi/linux/openat2.h b/src/include/uapi/linux/openat2.h new file mode 100644 index 0000000000000..4759c471676cc --- /dev/null +++ b/src/include/uapi/linux/openat2.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_OPENAT2_H +#define _LINUX_OPENAT2_H + +#include + +/* + * Arguments for how openat2(2) should open the target path. If only @flags and + * @mode are non-zero, then openat2(2) operates very similarly to openat(2). + * + * However, unlike openat(2), unknown or invalid bits in @flags result in + * -EINVAL rather than being silently ignored. @mode must be zero unless one of + * {O_CREAT, O_TMPFILE} are set. + * + * @flags: O_* flags. + * @mode: O_CREAT/O_TMPFILE file mode. + * @resolve: RESOLVE_* flags. + */ +struct open_how { + __u64 flags; + __u64 mode; + __u64 resolve; +}; + +/* how->resolve flags for openat2(2). */ +#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings + (includes bind-mounts). */ +#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style + "magic-links". */ +#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks + (implies OEXT_NO_MAGICLINKS) */ +#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like + "..", symlinks, and absolute + paths which escape the dirfd. */ +#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." + be scoped inside the dirfd + (similar to chroot(2)). */ +#define RESOLVE_CACHED 0x20 /* Only complete if resolution can be + completed through cached lookup. May + return -EAGAIN if that's not + possible. */ + +#endif /* _LINUX_OPENAT2_H */ From b6d1dd4ddb6556242e4abd81dbbd790ca7e76a6d Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 21 May 2026 11:40:19 +0100 Subject: [PATCH 2092/2155] updatectl: Apply --no-ask-password argument to polkit agent Accidentally missed out of commit 8c0f9073c7da808461acfc016cc291e4aba9c1a2. Signed-off-by: Philip Withnall Fixes: 8c0f9073c7da808461acfc016cc291e4aba9c1a2 --- src/sysupdate/updatectl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index bafc41c6ae0c7..cd41aeb9007f9 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1757,8 +1757,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - if (arg_transport == BUS_TRANSPORT_LOCAL) - polkit_agent_open(); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); From 2de1322dc2c312aa7f65ae08040e70c2fe613faa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:19:42 +0200 Subject: [PATCH 2093/2155] tpm2-setup: introduce a 'priority' concept for NvPCRs --- src/analyze/analyze-nvpcrs.c | 5 ++-- src/shared/tpm2-util.c | 18 ++++++++++--- src/shared/tpm2-util.h | 9 +++++-- src/tpm2-setup/tpm2-setup.c | 52 ++++++++++++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c index 56b5c9a204945..c3967ca01a581 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -16,6 +16,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { _cleanup_free_ char *h = NULL; uint32_t nv_index = 0; + uint64_t priority = 0; if (c) { if (!*c) { r = tpm2_context_new_or_warn(/* device= */ NULL, c); @@ -24,7 +25,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { } _cleanup_(iovec_done) struct iovec digest = {}; - r = tpm2_nvpcr_read(*c, /* session= */ NULL, name, &digest, &nv_index); + r = tpm2_nvpcr_read(*c, /* session= */ NULL, name, &digest, &nv_index, &priority); if (r < 0) return log_error_errno(r, "Failed to read NvPCR '%s': %m", name); if (r > 0) { /* set? */ @@ -33,7 +34,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { return log_oom(); } } else { - r = tpm2_nvpcr_get_index(name, &nv_index); + r = tpm2_nvpcr_get_index(name, &nv_index, &priority); if (r < 0) return log_error_errno(r, "Failed to get NV index of NvPCR '%s': %m", name); } diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 533cdd630650e..26ce8d72464bf 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6991,6 +6991,7 @@ typedef struct NvPCRData { char *name; uint16_t algorithm; uint32_t nv_index; + uint64_t priority; } NvPCRData; static void nvpcr_data_done(NvPCRData *d) { @@ -7030,11 +7031,13 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NvPCRData, name), SD_JSON_MANDATORY }, { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 }, { "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY }, + { "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(NvPCRData, priority), 0 }, {}, }; _cleanup_(nvpcr_data_done) NvPCRData p = { .algorithm = TPM2_ALG_SHA256, + .priority = TPM2_NVPCR_PRIORITY_DEFAULT, }; r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) @@ -7047,7 +7050,7 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) { return 0; } -int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) { +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret_nv_index, uint64_t *ret_priority) { int r; _cleanup_(nvpcr_data_done) NvPCRData p = {}; @@ -7055,8 +7058,10 @@ int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) { if (r < 0) return r; - if (ret) - *ret = p.nv_index; + if (ret_nv_index) + *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return 0; } @@ -7748,7 +7753,8 @@ int tpm2_nvpcr_read( const Tpm2Handle *session, const char *name, struct iovec *ret_value, - uint32_t *ret_nv_index) { + uint32_t *ret_nv_index, + uint64_t *ret_priority) { #if HAVE_OPENSSL int r; @@ -7775,6 +7781,8 @@ int tpm2_nvpcr_read( *ret_value = (struct iovec) {}; if (ret_nv_index) *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return 0; } @@ -7811,6 +7819,8 @@ int tpm2_nvpcr_read( if (ret_nv_index) *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return r; #else /* HAVE_OPENSSL */ diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index f2f63a94a43fc..bf2321a514aab 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -158,11 +158,16 @@ typedef enum Tpm2UserspaceEventType { DECLARE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); int tpm2_pcr_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description); -int tpm2_nvpcr_get_index(const char *name, uint32_t *ret); + +/* Default allocation priority for NvPCRs that do not specify one explicitly. Lower values are more + * important and are allocated first when the TPM's NV index space is constrained. */ +#define TPM2_NVPCR_PRIORITY_DEFAULT UINT64_C(1000) + +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret_nv_index, uint64_t *ret_priority); int tpm2_nvpcr_extend_bytes(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description); int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary); int tpm2_nvpcr_initialize(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *anchor_secret); -int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index); +int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index, uint64_t *ret_priority); uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index bb08e31a81c87..ac294f7fae46a 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -23,6 +23,7 @@ #include "parse-util.h" #include "pretty-print.h" #include "set.h" +#include "sort-util.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" @@ -434,6 +435,26 @@ static int setup_nvpcr_one( return 0; } +typedef struct NvPCREntry { + const char *name; /* points into the strv 'l' in setup_nvpcr() */ + uint64_t priority; +} NvPCREntry; + +static int nvpcr_entry_compare(const NvPCREntry *a, const NvPCREntry *b) { + int r; + + assert(a); + assert(b); + + /* Lower priority value means more important, hence allocate it first. Break ties by name + * (ascending) so the resulting order is fully deterministic. */ + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + return strcmp(a->name, b->name); +} + static int setup_nvpcr(void) { _cleanup_(setup_nvpcr_context_done) SetupNvPCRContext c = {}; int r; @@ -448,8 +469,33 @@ static int setup_nvpcr(void) { if (r < 0) return log_error_errno(r, "Failed to find .nvpcr files: %m"); + /* Pair each NvPCR name with its allocation priority, then sort, so that we allocate the most + * important NvPCRs first. This matters when the TPM's NV index space is too small to fit all of + * them: we then degrade gracefully, skipping the least important NvPCRs. */ + size_t n = strv_length(l); + _cleanup_free_ NvPCREntry *entries = new(NvPCREntry, n); + if (!entries) + return log_oom(); + + for (size_t i = 0; i < n; i++) { + uint64_t priority = TPM2_NVPCR_PRIORITY_DEFAULT; + + r = tpm2_nvpcr_get_index(l[i], /* ret_nv_index= */ NULL, &priority); + if (r < 0) + /* If we can't read the definition, assume the default priority and let the + * per-NvPCR error path in setup_nvpcr_one() report the actual problem. */ + log_warning_errno(r, "Failed to read priority of NvPCR '%s', assuming default priority %" PRIu64 ": %m", l[i], priority); + + entries[i] = (NvPCREntry) { + .name = l[i], + .priority = priority, + }; + } + + typesafe_qsort(entries, n, nvpcr_entry_compare); + int ret = 0; - STRV_FOREACH(i, l) { + FOREACH_ARRAY(e, entries, n) { if (!c.tpm2_context) { /* Inability to contact the TPM shall be fatal for us */ r = tpm2_context_new_or_warn(arg_tpm2_device, &c.tpm2_context); @@ -457,8 +503,10 @@ static int setup_nvpcr(void) { return r; } + log_debug("Setting up NvPCR '%s' (priority %" PRIu64 ").", e->name, e->priority); + /* But if we fail to initialize some NvPCR, we go on */ - RET_GATHER(ret, setup_nvpcr_one(&c, *i)); + RET_GATHER(ret, setup_nvpcr_one(&c, e->name)); } if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) From 2fca7519a422ea8d71efb93595a24f6f1f4a0826 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:20:37 +0200 Subject: [PATCH 2094/2155] analyze: show priority in 'nvpcrs' output --- src/analyze/analyze-nvpcrs.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c index c3967ca01a581..dde31be0732c4 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -43,6 +43,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { t, TABLE_STRING, name, TABLE_UINT32_HEX_0x, nv_index, + TABLE_UINT64, priority, TABLE_STRING, h); if (r < 0) return table_log_add_error(r); @@ -62,16 +63,17 @@ int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!have_tpm2) log_notice("System lacks full TPM2 support, not showing NvPCR state."); - table = table_new("name", "nvindex", "value"); + table = table_new("name", "nvindex", "priority", "value"); if (!table) return log_oom(); (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100); + (void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100); table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - (void) table_set_sort(table, (size_t) 0); + (void) table_set_sort(table, (size_t) 2, (size_t) 0); if (!have_tpm2) - (void) table_hide_column_from_display(table, (size_t) 2); + (void) table_hide_column_from_display(table, (size_t) 3); if (strv_isempty(strv_skip(argv, 1))) { _cleanup_strv_free_ char **l = NULL; From 533aa457f6d9e01b6e0f76ab26ccfecb1dafb579 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:21:02 +0200 Subject: [PATCH 2095/2155] nvpcr: define some reasonable priorities for the nvpcrs we already defined --- src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in | 3 ++- src/tpm2-setup/nvpcr/hardware.nvpcr.in | 3 ++- src/tpm2-setup/nvpcr/verity.nvpcr.in | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in index acec95206f807..3f01ef34eb4d7 100644 --- a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in +++ b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "cryptsetup", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 1}} + "nvIndex" : {{TPM2_NVPCR_BASE + 1}}, + "priority" : 700 } diff --git a/src/tpm2-setup/nvpcr/hardware.nvpcr.in b/src/tpm2-setup/nvpcr/hardware.nvpcr.in index ddeb7f99e505f..bb4a3afa6957a 100644 --- a/src/tpm2-setup/nvpcr/hardware.nvpcr.in +++ b/src/tpm2-setup/nvpcr/hardware.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "hardware", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 0}} + "nvIndex" : {{TPM2_NVPCR_BASE + 0}}, + "priority" : 500 } diff --git a/src/tpm2-setup/nvpcr/verity.nvpcr.in b/src/tpm2-setup/nvpcr/verity.nvpcr.in index b4fb62bd76244..7d1630d6cd15c 100644 --- a/src/tpm2-setup/nvpcr/verity.nvpcr.in +++ b/src/tpm2-setup/nvpcr/verity.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "verity", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 2}} + "nvIndex" : {{TPM2_NVPCR_BASE + 2}}, + "priority" : 300 } From 101a7af63fec5dd5f80282c2739e6186a1b079a2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:21:28 +0200 Subject: [PATCH 2096/2155] tpm2-util: be more graceful with nvindex space exhaustion issues Let's tone down the language, and say how many we skipped. --- src/tpm2-setup/tpm2-setup.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index ac294f7fae46a..eb25d7fd7f54e 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -370,7 +370,8 @@ static int setup_srk(void) { typedef struct SetupNvPCRContext { Tpm2Context *tpm2_context; struct iovec anchor_secret; - size_t n_already, n_anchored, n_failed; + size_t n_already, n_anchored, n_failed, n_skipped; + bool nv_space_exhausted; /* Set once the TPM ran out of NV index space, so we skip the rest. */ Set *done; } SetupNvPCRContext; @@ -394,6 +395,15 @@ static int setup_nvpcr_one( if (set_contains(c->done, name)) return 0; + /* If the TPM already ran out of NV index space, don't even try to allocate further NvPCRs. As + * NvPCRs are processed in order of priority (most important first), everything still pending is + * less important than what already didn't fit. */ + if (c->nv_space_exhausted) { + log_debug("TPM NV index space already exhausted, skipping allocation of NvPCR '%s'.", name); + c->n_skipped++; + return 0; + } + r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret); if (r == -EUNATCH) { assert(!iovec_is_set(&c->anchor_secret)); @@ -414,9 +424,13 @@ static int setup_nvpcr_one( LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR)); } if (r == -ENOSPC) { - 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), + /* The TPM's NV index space is exhausted. Remember this so we skip the remaining (less + * important) NvPCRs, and report it gracefully at the end rather than failing the boot. + * Logged at notice level, not error. */ + c->nv_space_exhausted = true; + c->n_skipped++; + return log_struct_errno(LOG_NOTICE, r, + LOG_MESSAGE("The TPM's NV index space is exhausted, skipping allocation of NvPCR '%s' and any less important ones: %m", name), LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR)); } if (r < 0) { @@ -515,6 +529,9 @@ static int setup_nvpcr(void) { * 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.nv_space_exhausted) + log_notice("Skipped %zu lowest-priority NvPCR(s) because the TPM's NV index space is exhausted, proceeding anyway.", c.n_skipped); + if (c.n_failed > 0) log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed); @@ -525,7 +542,7 @@ static int setup_nvpcr(void) { log_info("%zu NvPCRs initialized. (%zu NvPCRs were already initialized.)", c.n_anchored, c.n_already); } else if (c.n_already > 0) log_info("%zu NvPCRs already initialized.", c.n_already); - else if (c.n_failed == 0) + else if (c.n_failed == 0 && c.n_skipped == 0) log_debug("No NvPCRs defined, nothing initialized."); /* Turn some errors into recognizable ones, which we can catch with From 89c837a5f867ae408e69c683cf92738443b742ba Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:22:03 +0200 Subject: [PATCH 2097/2155] docs: document the .nvpcr file format superficially --- docs/TPM2_PCR_MEASUREMENTS.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index 571a4c1077201..5d0801a9073ec 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -58,7 +58,22 @@ away there's a naming concept, so that nvindexes are referenced by name string rather than number. NvPCRs are defined in little JSON snippets in `/usr/lib/nvpcr/*.nvpcr`, that -match up index number and name, as well as pick a hash algorithm. +match up index number and name, as well as pick a hash algorithm. The recognized +fields are: + +* `name` — the NvPCR name (string), which must match the file name (without the + `.nvpcr` suffix). Mandatory. +* `nvIndex` — the fixed TPM2 NV index handle (number) to allocate for this NvPCR. + Mandatory. +* `algorithm` — the hash algorithm to use (string), e.g. `sha256` (the default). +* `priority` — an unsigned integer allocation priority, defaulting to `1000`. + Lower values are considered more important and are allocated first. This only + affects the order in which `systemd-tpm2-setup.service` attempts allocation at + boot: if the TPM's NV index space is too small to fit all NvPCRs, the most + important ones (lowest `priority` value) win the available space, and the + least important ones are skipped gracefully rather than the allocation failing + arbitrarily. Ties are broken by name. Priority does not affect the NV index, + the algorithm, or anything measured into the NvPCR. There's one complication: these NV indexes (like any NV indexes) can be deleted by anyone with access to the TPM, and then be recreated. This could be used to From f4c5fabbf311f429282e8507bc09c66a14de120e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 14:22:29 +0200 Subject: [PATCH 2098/2155] ci: add a CI test for nvpcr priorization --- test/units/TEST-70-TPM2.nvpcr.sh | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh index 05ae378d849e5..d84bb1feb38fc 100755 --- a/test/units/TEST-70-TPM2.nvpcr.sh +++ b/test/units/TEST-70-TPM2.nvpcr.sh @@ -21,7 +21,8 @@ at_exit() { fi rm -rf /run/nvpcr /tmp/nvpcr - rm -f /var/tmp/nvpcr.raw /run/verity.d/test-70-nvpcr.crt /run/systemd/nvpcr/test.anchor + rm -f /var/tmp/nvpcr.raw /run/verity.d/test-70-nvpcr.crt + rm -f /run/systemd/nvpcr/test.anchor /run/systemd/nvpcr/test2.anchor /run/systemd/nvpcr/aaa.anchor /run/systemd/nvpcr/zzz.anchor } trap at_exit EXIT @@ -54,6 +55,33 @@ DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -hex -r | cut -d' ' DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1)" test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2" +# Verify the 'priority' field round-trips through the JSON definition. The 'test' NvPCR above sets no +# priority, so it must report the default (1000). +PRIORITY_DEFAULT="$(systemd-analyze nvpcrs test --json=pretty | jq -r '.[] | select(.name=="test") | .priority')" +test "$PRIORITY_DEFAULT" = "1000" + +# A definition with an explicit priority must report exactly that value. +cat >/run/nvpcr/test2.nvpcr </run/nvpcr/aaa.nvpcr </run/nvpcr/zzz.nvpcr < Date: Thu, 21 May 2026 18:29:24 +0800 Subject: [PATCH 2099/2155] test: cover lingering users surviving a soft-reboot in TEST-82 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression coverage for the soft-reboot linger bug fixed in 9f25feb4ed18 ("logind: keep lingering users at startup-time GC", #41789): a lingering user's user@.service was started after a hardware reboot but not after `systemctl soft-reboot`, because logind GC'd the user at startup before user_start() ran. On the first boot, enable lingering for the pre-existing testuser and wait for its user@UID.service to come up. After the first soft-reboot (which keeps the same persistent rootfs), assert the service is active again — this is the regression check. Disable lingering again before the nextroot switch in the second boot so the lingering user doesn't leak into the later boots' minimal overlay rootfs. Verified locally in a qemu/KVM VM: passes with the fix present, and with the fix reverted the second-boot assertion times out (the lingering user is GC'd in logind startup and user@UID.service never comes back), confirming the test exercises the bug. Co-developed-by: Claude Opus 4.7 --- test/units/TEST-82-SOFTREBOOT.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/units/TEST-82-SOFTREBOOT.sh b/test/units/TEST-82-SOFTREBOOT.sh index 03f6b78e57fb1..778bdbd7f8dc4 100755 --- a/test/units/TEST-82-SOFTREBOOT.sh +++ b/test/units/TEST-82-SOFTREBOOT.sh @@ -190,6 +190,17 @@ elif [ -f /run/TEST-82-SOFTREBOOT.touch ]; then test "$(systemctl show -P ActiveState TEST-82-SOFTREBOOT-nosurvive-sigterm.service)" != "active" test "$(systemctl show -P ActiveState TEST-82-SOFTREBOOT-nosurvive.service)" != "active" + # Regression test for #41789: the lingering user enabled on the first boot must + # have its user@.service started again after the soft reboot. Before the fix it + # was GC'd at logind startup and never restarted. Disable lingering again here, + # before the nextroot switch below (the third boot runs on a minimal overlay + # rootfs that does not have this user). terminate-user is best-effort: it can + # race the asynchronous disable-linger/UserStopDelaySec teardown. + linger_uid=$(id -u testuser) + timeout 30 bash -c "until systemctl is-active --quiet user@${linger_uid}.service; do sleep 1; done" + loginctl disable-linger testuser + loginctl terminate-user testuser || true + # This time we test the /run/nextroot/ root switching logic. (We synthesize a new rootfs from the old via overlayfs) mkdir -p /run/nextroot /tmp/nextroot-lower /original-root mount -t tmpfs tmpfs /tmp/nextroot-lower @@ -313,6 +324,18 @@ EOF systemd-run --wait false || true done + # Regression test for #41789: a lingering user's user@.service must come back + # after a soft reboot, the same way it does after a hardware reboot. It used + # to be garbage-collected at logind startup (before user_start() ran), because + # /run/systemd/users is preserved across soft-reboot and fed the user into the + # GC queue while its units were not active yet. Enable lingering for the + # pre-existing testuser here; the second boot asserts the service is active + # again. testuser lives in the persistent rootfs, so it survives the + # (non-nextroot) soft reboot into the second boot. + loginctl enable-linger testuser + linger_uid=$(id -u testuser) + timeout 30 bash -c "until systemctl is-active --quiet user@${linger_uid}.service; do sleep 1; done" + trigger_uevent # Now issue the soft reboot. We should be right back soon. From 9f016b3810385761bf08c6056d167875e9950dc3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 16:05:52 +0100 Subject: [PATCH 2100/2155] Update NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 42705e21f6d5a..5297923421cfa 100644 --- a/NEWS +++ b/NEWS @@ -166,6 +166,10 @@ CHANGES WITH 261 in spe: is inserted in the last part of shutdown, in order to avoid tight boot loops. + * The FileDescriptorStorePreserve= unit setting can now take a new option + 'on-success', which preserves the FD Store when the unit is stopped, + but only if it exited successfully, and discards it otherwise. + New IMDS (Cloud "Instance Metadata Service") Subsystem: * The hardware database now contains a new database hwdb.d/40-imds.hwdb From dd76dfa4a0efbea8a6d415f6f7f0ce037a2ba8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=B8=EC=88=98?= Date: Thu, 21 May 2026 15:01:50 +0000 Subject: [PATCH 2101/2155] 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% (270 of 270 strings) Co-authored-by: 김인수 Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ko/ Translation: systemd/main --- po/ko.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/ko.po b/po/ko.po index c3109e54f154f..9cc79bf910c43 100644 --- a/po/ko.po +++ b/po/ko.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" +"PO-Revision-Date: 2026-05-21 15:01+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.2\n" +"X-Generator: Weblate 2026.5\n" "X-Poedit-SourceCharset: UTF-8\n" #: src/core/org.freedesktop.systemd1.policy.in:22 @@ -831,12 +831,12 @@ msgstr "로컬 가상 머신 및 컨테이너를 관리하려면 인증이 필 #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "로컬 가상 장비 및 컨테이너를 검사합니다" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." -msgstr "" +msgstr "인증은 로컬 가상 장비 및 컨테이너를 검사하기 위해 필요합니다." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -868,13 +868,13 @@ msgstr "로컬 가상 머신 및 컨테이너 이미지를 관리하려면 인 #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "로컬 가상 장비 및 컨테이너 이미지를 검사합니다" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." -msgstr "" +msgstr "인증은 로컬 가상 장비 및 컨터에이너를 검사하기 위해 필요합니다." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From 38c0073d438c92246e945ebe1f957b7e24dd4c01 Mon Sep 17 00:00:00 2001 From: joo es Date: Thu, 21 May 2026 15:01:53 +0000 Subject: [PATCH 2102/2155] po: Translated using Weblate (Arabic) Currently translated at 100.0% (270 of 270 strings) Co-authored-by: joo es Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ar/ Translation: systemd/main --- po/ar.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/ar.po b/po/ar.po index 6ac6707498c87..347d605393389 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-05-18 12:18+0100\n" -"PO-Revision-Date: 2026-04-11 19:58+0000\n" +"PO-Revision-Date: 2026-05-21 15:01+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.2\n" +"X-Generator: Weblate 2026.5\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -802,12 +802,12 @@ msgstr "الاستيثاق مطلوب لإدارة الأجهزة الافترا #: src/machine/org.freedesktop.machine1.policy:95 msgid "Inspect local virtual machines and containers" -msgstr "" +msgstr "افحص الأجهزة الافتراضية والحاويات المحلية" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" "Authentication is required to inspect local virtual machines and containers." -msgstr "" +msgstr "الاستيثاق مطلوب لفحص الأجهزة الافتراضية والحاويات المحلية." #: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" @@ -839,13 +839,13 @@ msgstr "الاستيثاق مطلوب لإدارة الأجهزة الافترا #: src/machine/org.freedesktop.machine1.policy:137 msgid "Inspect local virtual machine and container images" -msgstr "" +msgstr "افحص صور الأجهزة الافتراضية والحاويات المحلية" #: src/machine/org.freedesktop.machine1.policy:138 msgid "" "Authentication is required to inspect local virtual machine and container " "images." -msgstr "" +msgstr "الاستيثاق مطلوب لفحص صور الأجهزة الافتراضية والحاويات المحلية." #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" From 3d0a05dcc36eef148b95651bdc537f64bd9aa204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 May 2026 18:01:29 +0200 Subject: [PATCH 2103/2155] core/exec-invoke: fix bogus error code r was bogus here. getusername_malloc() can only fail on oom, so we can simplify this. --- src/core/exec-invoke.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 9004ad88d0689..7370bc965fb25 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -5430,7 +5430,7 @@ int exec_invoke( own_user = getusername_malloc(); if (!own_user) { *exit_status = EXIT_USER; - return log_error_errno(r, "Failed to determine my own user ID: %m"); + return log_oom(); } u = own_user; } else From 3ecd6b69cff6e7209ff97f7e7e04ea37268b22c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 May 2026 17:59:57 +0200 Subject: [PATCH 2104/2155] user-util: stop mangling groupname in get_group_creds The param was input/output, which is unexpected and confusing and actually made most callers much more complicated than they needed to be. The function was playing fast and loose with the return value. In some cases it was returning a static string, which would be completely fine. But in other case it was returning a pointer into the getgrgid static buffer, i.e. that return value could be overwritten. AFAICT, this didn't matter in any of the callers, but we shouldn't do that anyway. So use a separate output param with an allocated string that the caller is responsible for. It turns out that all callers pass NULL (outside of tests) and zero for flags. So the function _could_ be simplified. But get_user_creds is called with all the params actually used, and I think having the the functions so different wouldn't be nice. I first wrote a commit to drop the unused params, but then I discarded it. The error message in exec-invoke.c is fixed. --- src/basic/user-util.c | 102 ++++++++++++++++---------------------- src/basic/user-util.h | 2 +- src/core/exec-invoke.c | 29 ++--------- src/core/scope.c | 6 +-- src/core/socket.c | 6 +-- src/run/run.c | 9 ++-- src/test/test-user-util.c | 15 +++--- src/tmpfiles/tmpfiles.c | 2 +- 8 files changed, 65 insertions(+), 106 deletions(-) diff --git a/src/basic/user-util.c b/src/basic/user-util.c index b735d27474272..b531761800f76 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -352,91 +352,75 @@ int get_user_creds( return 0; } -static int synthesize_group_creds( - const char **groupname, - gid_t *ret_gid) { - +static int synthesize_group_creds(const char *groupname, char **ret_name, gid_t *ret_gid) { assert(groupname); - assert(*groupname); - - if (STR_IN_SET(*groupname, "root", "0")) { - *groupname = "root"; - - if (ret_gid) - *ret_gid = 0; - - return 0; - } - - if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") && - synthesize_nobody()) { - *groupname = NOBODY_GROUP_NAME; - - if (ret_gid) - *ret_gid = GID_NOBODY; - return 0; - } + gid_t id; + const char *n; + int r; - return -ENOMEDIUM; + if (STR_IN_SET(groupname, "root", "0")) { + id = 0; + n = "root"; + } else if (STR_IN_SET(groupname, NOBODY_GROUP_NAME, "65534") && + synthesize_nobody()) { + id = GID_NOBODY; + n = NOBODY_GROUP_NAME; + } else + return -ENOMEDIUM; + + r = strdup_to_full(ret_name, n); + if (r < 0) + return r; + if (ret_gid) + *ret_gid = id; + return 0; } -int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) { - bool patch_groupname = false; - struct group *g; +int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid) { + _cleanup_free_ struct group *gr = NULL; gid_t id; int r; assert(groupname); - assert(*groupname); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { - r = synthesize_group_creds(groupname, ret_gid); + r = synthesize_group_creds(groupname, ret_name, ret_gid); if (r >= 0) return 0; if (r != -ENOMEDIUM) /* not a groupname we can synthesize */ return r; } - if (parse_gid(*groupname, &id) >= 0) { - errno = 0; - g = getgrgid(id); - - if (g) - patch_groupname = true; + if (parse_gid(groupname, &id) >= 0) { + r = getgrgid_malloc(id, &gr); + if (r >= 0) + groupname = gr->gr_name; else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) { if (ret_gid) *ret_gid = id; - + if (ret_name) + *ret_name = NULL; return 0; } - } else { - errno = 0; - g = getgrnam(*groupname); - } - - if (!g) { - /* getgrnam() may fail with ENOENT if /etc/group is missing. - * For us that is equivalent to the name not being defined. */ - r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; - - if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) - if (synthesize_group_creds(groupname, ret_gid) >= 0) - return 0; + } else + r = getgrnam_malloc(groupname, &gr); + if (r < 0) { + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) && + synthesize_group_creds(groupname, ret_name, ret_gid) >= 0) + return 0; return r; } - if (ret_gid) { - if (!gid_is_valid(g->gr_gid)) - return -EBADMSG; - - *ret_gid = g->gr_gid; - } - - if (patch_groupname) - *groupname = g->gr_name; + if (ret_gid && !gid_is_valid(gr->gr_gid)) + return -EBADMSG; + r = strdup_to_full(ret_name, groupname); + if (r < 0) + return r; + if (ret_gid) + *ret_gid = gr->gr_gid; return 0; } @@ -521,7 +505,7 @@ int in_group(const char *name) { int r; gid_t gid; - r = get_group_creds(&name, &gid, 0); + r = get_group_creds(name, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) return r; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index a8902aca6150b..939d108ceb620 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -63,7 +63,7 @@ typedef enum UserCredsFlags { } UserCredsFlags; int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); -int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags); +int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid); char* uid_to_name(uid_t uid); char* gid_to_name(gid_t gid); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 7370bc965fb25..1c883ff1643f8 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -902,26 +902,6 @@ static int get_fixed_user( return 0; } -static int get_fixed_group( - const char *group_or_gid, - const char **ret_groupname, - gid_t *ret_gid) { - - int r; - - assert(group_or_gid); - assert(ret_groupname); - - r = get_group_creds(&group_or_gid, ret_gid, /* flags= */ 0); - if (r < 0) - return r; - - /* group_or_gid is normalized by get_group_creds to groupname */ - *ret_groupname = group_or_gid; - - return 0; -} - static int get_supplementary_groups( const ExecContext *c, const char *user, @@ -989,8 +969,7 @@ static int get_supplementary_groups( if (k >= ngroups_max) return -E2BIG; - const char *g = *i; - r = get_group_creds(&g, l_gids + k, /* flags= */ 0); + r = get_group_creds(*i, /* flags= */ 0, /* ret_name= */ NULL, l_gids + k); if (r < 0) return r; @@ -5182,7 +5161,7 @@ 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; + const char *username = 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; @@ -5454,11 +5433,11 @@ int exec_invoke( } if (context->group) { - r = get_fixed_group(context->group, &groupname, &gid); + r = get_group_creds(context->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { *exit_status = EXIT_GROUP; log_error_errno(r, "Failed to determine credentials for group '%s': %s", - u, STRERROR_GROUP(r)); + context->group, STRERROR_GROUP(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } } diff --git a/src/core/scope.c b/src/core/scope.c index 167d3a37bd9b5..f75c6008299b8 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -356,13 +356,11 @@ static int scope_enter_start_chown(Scope *s) { } if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid, 0); + r = get_group_creds(s->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve group '%s': %s", - group, STRERROR_GROUP(r)); + s->group, STRERROR_GROUP(r)); _exit(EXIT_GROUP); } } diff --git a/src/core/socket.c b/src/core/socket.c index 2cdb34c4be656..c31e83fefea50 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2057,13 +2057,11 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { } if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid, 0); + r = get_group_creds(s->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve group '%s': %s", - group, STRERROR_GROUP(r)); + s->group, STRERROR_GROUP(r)); _exit(EXIT_GROUP); } } diff --git a/src/run/run.c b/src/run/run.c index 60f3bf7405ebd..d1f82001999f5 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -2571,7 +2571,7 @@ static int start_transient_scope(sd_bus *bus) { if (arg_exec_group) { gid_t gid; - r = get_group_creds(&arg_exec_group, &gid, 0); + r = get_group_creds(arg_exec_group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) return log_error_errno(r, "Failed to resolve group '%s': %s", arg_exec_group, STRERROR_GROUP(r)); @@ -2615,10 +2615,9 @@ static int start_transient_scope(sd_bus *bus) { if (r < 0) return log_oom(); - if (!arg_exec_group) { - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } + if (!arg_exec_group && + setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); if (setresuid(uid, uid, uid) < 0) return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index 47bd01f9d0f80..f5bb24f32af2f 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -317,21 +317,22 @@ TEST(get_user_creds) { test_get_user_creds_one("65534", NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN); } -static void test_get_group_creds_one(const char *id, const char *name, gid_t gid) { +static void test_get_group_creds_one(const char *id, const char *expected_name, gid_t expected_gid) { gid_t rgid = GID_INVALID; + _cleanup_free_ char *rnam = NULL; int r; - log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, name, gid); + log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, expected_name, expected_gid); - r = get_group_creds(&id, &rgid, 0); - log_info_errno(r, "got \"%s\", "GID_FMT": %m", id, rgid); - if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) { + r = get_group_creds(id, /* flags= */ 0, &rnam, &rgid); + log_info_errno(r, "→ \"%s\", "GID_FMT": %m", strnull(rnam), rgid); + if (!synthesize_nobody() && streq(expected_name, NOBODY_GROUP_NAME)) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } ASSERT_OK(r); - ASSERT_STREQ(id, name); - ASSERT_EQ(rgid, gid); + ASSERT_STREQ(rnam, expected_name); + ASSERT_EQ(rgid, expected_gid); } TEST(get_group_creds) { diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 8b7b676598702..09ab88931e983 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -3796,7 +3796,7 @@ static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) { /* Second: pass to NSS if we are running "online" */ if (!arg_root) - return get_group_creds(&group, ret_gid, 0); + return get_group_creds(group, /* flags= */ 0, /* ret_name= */ NULL, ret_gid); /* Third, synthesize "root" unconditionally */ if (streq(group, "root")) { From c83ff0e58df1c352eb41b4ac6cf8f6670d3865bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 May 2026 23:22:06 +0200 Subject: [PATCH 2105/2155] user-util: return malloc'ed strings in get_user_creds get_user_creds would use getpwnam() and then returns strings pointing into the static buffer. This seems very iffy. Let's duplicate the strings properly --- src/basic/user-util.c | 186 +++++++++++---------- src/basic/user-util.h | 8 +- src/core/exec-invoke.c | 81 ++++----- src/core/scope.c | 6 +- src/core/socket.c | 6 +- src/coredump/coredump-submit.c | 6 +- src/creds/creds.c | 9 +- src/login/loginctl.c | 12 +- src/login/user-runtime-dir.c | 2 +- src/mount/mount-tool.c | 7 +- src/network/networkd-routing-policy-rule.c | 2 +- src/network/networkd.c | 5 +- src/notify/notify.c | 2 +- src/resolve/resolved.c | 5 +- src/run/run.c | 11 +- src/shared/condition.c | 3 +- src/shared/logs-show.c | 4 +- src/test/test-acl-util.c | 3 +- src/test/test-ipcrm.c | 2 +- src/test/test-user-util.c | 9 +- src/tmpfiles/tmpfiles.c | 2 +- 21 files changed, 172 insertions(+), 199 deletions(-) diff --git a/src/basic/user-util.c b/src/basic/user-util.c index b531761800f76..aa629083c2378 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -204,67 +204,97 @@ const char* default_root_shell(const char *root) { return default_root_shell_at(rfd); } -static int synthesize_user_creds( - const char **username, +static int return_user_creds( + const char *username, + uid_t uid, gid_t gid, + const char *home, + const char *shell, + char **ret_username, uid_t *ret_uid, gid_t *ret_gid, - const char **ret_home, - const char **ret_shell, - UserCredsFlags flags) { - - assert(username); - assert(*username); + char **ret_home, + char **ret_shell) { + /* Helper function to help with the strdups and atomic setting of return params. */ - /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode - * their user record data. */ + _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL; + int r; - if (STR_IN_SET(*username, "root", "0")) { - *username = "root"; + if (ret_username) { + r = strdup_to(&s1, username); + if (r < 0) + return r; + } - if (ret_uid) - *ret_uid = 0; - if (ret_gid) - *ret_gid = 0; - if (ret_home) - *ret_home = "/root"; - if (ret_shell) - *ret_shell = default_root_shell(NULL); + if (ret_home) { + r = strdup_to(&s2, home); + if (r < 0) + return r; + } - return 0; + if (ret_shell) { + r = strdup_to(&s3, shell); + if (r < 0) + return r; } - if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") && - synthesize_nobody()) { - *username = NOBODY_USER_NAME; + if (ret_username) + *ret_username = TAKE_PTR(s1); + if (ret_uid) + *ret_uid = uid; + if (ret_gid) + *ret_gid = gid; + if (ret_home) + *ret_home = TAKE_PTR(s2); + if (ret_shell) + *ret_shell = TAKE_PTR(s3); + return 0; +} - if (ret_uid) - *ret_uid = UID_NOBODY; - if (ret_gid) - *ret_gid = GID_NOBODY; - if (ret_home) - *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/"; - if (ret_shell) - *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN; +static int synthesize_user_creds( + const char *username, + UserCredsFlags flags, + char **ret_username, + uid_t *ret_uid, gid_t *ret_gid, + char **ret_home, + char **ret_shell) { + assert(username); - return 0; - } + /* We enforce some special rules for uid=0 and uid=65534: in order to avoid nss lookups for root we + * hardcode their user record data. */ + if (STR_IN_SET(username, "root", "0")) + return return_user_creds("root", 0, 0, + "/root", + ret_shell ? default_root_shell(NULL) : NULL, + ret_username, + ret_uid, ret_gid, + ret_home, + ret_shell); + + if (STR_IN_SET(username, NOBODY_USER_NAME, "65534") && + synthesize_nobody()) + return return_user_creds(NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, + FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/", + FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN, + ret_username, + ret_uid, ret_gid, + ret_home, + ret_shell); return -ENOMEDIUM; } int get_user_creds( - const char **username, + const char *username, + UserCredsFlags flags, + char **ret_username, uid_t *ret_uid, gid_t *ret_gid, - const char **ret_home, - const char **ret_shell, - UserCredsFlags flags) { + char **ret_home, + char **ret_shell) { - bool patch_username = false; uid_t u = UID_INVALID; - struct passwd *p; + _cleanup_free_ struct passwd *pw = NULL; int r; assert(username); - assert(*username); assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN))); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || @@ -279,77 +309,63 @@ int get_user_creds( * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't * support. */ - r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags); + r = synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell); if (r >= 0) return 0; if (r != -ENOMEDIUM) /* not a username we can synthesize */ return r; } - if (parse_uid(*username, &u) >= 0) { - errno = 0; - p = getpwuid(u); + if (parse_uid(username, &u) >= 0) { + r = getpwuid_malloc(u, &pw); /* If there are multiple users with the same id, make sure to leave $USER to the configured value * instead of the first occurrence in the database. However if the uid was configured by a numeric uid, * then let's pick the real username from /etc/passwd. */ - if (p) - patch_username = true; - else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { + if (r >= 0) + username = pw->pw_name; + else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { /* If the specified user is a numeric UID and it isn't in the user database, and the caller * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that * and don't complain. */ - + if (ret_username) + *ret_username = NULL; if (ret_uid) *ret_uid = u; - return 0; } - } else { - errno = 0; - p = getpwnam(*username); - } - if (!p) { - /* getpwnam() may fail with ENOENT if /etc/passwd is missing. - * For us that is equivalent to the name not being defined. */ - r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + } else + r = getpwnam_malloc(username, &pw); + if (r < 0) { /* If the user requested that we only synthesize as fallback, do so now */ - if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) - if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0) - return 0; + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) && + synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell) >= 0) + return 0; return r; } - if (ret_uid && !uid_is_valid(p->pw_uid)) + if (ret_uid && !uid_is_valid(pw->pw_uid)) return -EBADMSG; - if (ret_gid && !gid_is_valid(p->pw_gid)) + if (ret_gid && !gid_is_valid(pw->pw_gid)) return -EBADMSG; - if (ret_uid) - *ret_uid = p->pw_uid; - - if (ret_gid) - *ret_gid = p->pw_gid; + /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ + const char *h = + (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(pw->pw_dir)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_dir) || !path_is_absolute(pw->pw_dir))) + ? NULL : pw->pw_dir; - if (ret_home) - /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ - *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) || - (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir))) - ? NULL : p->pw_dir; + const char *s = + (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(pw->pw_shell)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_shell) || !path_is_absolute(pw->pw_shell))) + ? NULL : pw->pw_shell; - if (ret_shell) - *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) || - (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell))) - ? NULL : p->pw_shell; - - if (patch_username) - *username = p->pw_name; - - return 0; + return return_user_creds(username, pw->pw_uid, pw->pw_gid, h, s, + ret_username, ret_uid, ret_gid, ret_home, ret_shell); } static int synthesize_group_creds(const char *groupname, char **ret_name, gid_t *ret_gid) { @@ -1055,7 +1071,7 @@ int is_this_me(const char *username) { /* Checks if the specified username is our current one. Passed string might be a UID or a user name. */ - r = get_user_creds(&username, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + r = get_user_creds(username, /* flags= */ USER_CREDS_ALLOW_MISSING, NULL, &uid, NULL, NULL, NULL); if (r < 0) return r; @@ -1063,10 +1079,8 @@ int is_this_me(const char *username) { } const char* get_home_root(void) { - const char *e; - /* For debug purposes allow overriding where we look for home dirs */ - e = secure_getenv("SYSTEMD_HOME_ROOT"); + const char *e = secure_getenv("SYSTEMD_HOME_ROOT"); if (e && path_is_absolute(e) && path_is_normalized(e)) return e; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 939d108ceb620..641bdc06b0999 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -62,7 +62,13 @@ typedef enum UserCredsFlags { USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */ } UserCredsFlags; -int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); +int get_user_creds( + const char *username, + UserCredsFlags flags, + char **ret_username, + uid_t *ret_uid, gid_t *ret_gid, + char **ret_home, + char **ret_shell); int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid); char* uid_to_name(uid_t uid); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 1c883ff1643f8..4f935c1439b95 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -877,31 +877,6 @@ static int ask_for_confirmation(const ExecContext *context, const ExecParameters return r; } -static int get_fixed_user( - const char *user_or_uid, - bool prefer_nss, - const char **ret_username, - uid_t *ret_uid, - gid_t *ret_gid, - const char **ret_home, - const char **ret_shell) { - - int r; - - assert(user_or_uid); - assert(ret_username); - - r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, - USER_CREDS_CLEAN|(prefer_nss ? USER_CREDS_PREFER_NSS : 0)); - if (r < 0) - return r; - - /* user_or_uid is normalized by get_user_creds to username */ - *ret_username = user_or_uid; - - return 0; -} - static int get_supplementary_groups( const ExecContext *c, const char *user, @@ -2015,6 +1990,7 @@ static int build_environment( char ***ret) { _cleanup_strv_free_ char **e = NULL; + _cleanup_free_ char *_username = NULL, *_home = NULL, *_shell = NULL; size_t n = 0; pid_t exec_pid; int r; @@ -2079,12 +2055,16 @@ static int build_environment( if (!username && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { assert(!c->user); - r = get_fixed_user("root", /* prefer_nss= */ false, &username, NULL, NULL, &home, &shell); + r = get_user_creds("root", USER_CREDS_CLEAN, &_username, NULL, NULL, &_home, &_shell); if (r < 0) { log_debug_errno(r, "Failed to determine credentials for user root: %s", STRERROR_USER(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } + + username = _username; + home = _home; + shell = _shell; } bool set_user_login_env = exec_context_get_set_login_environment(c); @@ -4347,16 +4327,13 @@ static int send_user_lookup( return 0; } -static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) { - int r; - +static int acquire_home(const ExecContext *c, char **home) { assert(c); assert(home); - assert(ret_buf); /* If WorkingDirectory=~ is set, try to acquire a usable home directory. */ - if (*home) /* Already acquired from get_fixed_user()? */ + if (*home) /* Already acquired from get_user_creds()? */ return 0; if (!c->working_directory_home) @@ -4365,12 +4342,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0)) return -EADDRNOTAVAIL; - r = get_home_dir(ret_buf); - if (r < 0) - return r; - - *home = *ret_buf; - return 1; + return get_home_dir(home); } static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p, char ***ret) { @@ -5162,9 +5134,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; - _cleanup_free_ char *home_buffer = NULL, *own_user = NULL; + _cleanup_free_ char *pwent_home = NULL, *shell = NULL, *_own_user = NULL, *_username = 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; bool needs_sandboxing, /* Do we need to set up full sandboxing? (i.e. all namespacing, all MAC stuff, caps, yadda yadda */ @@ -5400,36 +5371,40 @@ int exec_invoke( username = runtime->dynamic_creds->user->name; } else { - const char *u; - if (context->user) - u = context->user; + username = context->user; else if (context->pam_name || FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) { /* If PAM is enabled but no user name is explicitly selected, then use our own one. */ - own_user = getusername_malloc(); - if (!own_user) { + username = _own_user = getusername_malloc(); + if (!username) { *exit_status = EXIT_USER; return log_oom(); } - u = own_user; - } else - u = NULL; + } - if (u) { + if (username) { /* We can't use nss unconditionally for root without risking deadlocks if some IPC services * will be started by pid1 and are ordered after us. But if SetLoginEnvironment= is * enabled *explicitly* (i.e. no exec_context_get_set_login_environment() here), * or PAM shall be invoked, let's consult NSS even for root, so that the user * gets accurate $SHELL in session(-like) contexts. */ - r = get_fixed_user(u, - /* prefer_nss= */ context->set_login_environment > 0 || context->pam_name, - &username, &uid, &gid, &pwent_home, &shell); + bool prefer_nss = context->set_login_environment > 0 || context->pam_name; + + r = get_user_creds(username, + USER_CREDS_CLEAN|(prefer_nss ? USER_CREDS_PREFER_NSS : 0), + &_username, + &uid, + &gid, + &pwent_home, + &shell); if (r < 0) { *exit_status = EXIT_USER; log_error_errno(r, "Failed to determine credentials for user '%s': %s", - u, STRERROR_USER(r)); + username, STRERROR_USER(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } + + username = _username; } if (context->group) { @@ -5460,7 +5435,7 @@ int exec_invoke( params->user_lookup_fd = safe_close(params->user_lookup_fd); } - r = acquire_home(context, &pwent_home, &home_buffer); + r = acquire_home(context, &pwent_home); if (r < 0) { *exit_status = EXIT_CHDIR; return log_error_errno(r, "Failed to determine $HOME for the invoking user: %m"); diff --git a/src/core/scope.c b/src/core/scope.c index f75c6008299b8..5e116a6f314ec 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -344,13 +344,11 @@ static int scope_enter_start_chown(Scope *s) { gid_t gid = GID_INVALID; if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds(s->user, /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve user '%s': %s", - user, STRERROR_USER(r)); + s->user, STRERROR_USER(r)); _exit(EXIT_USER); } } diff --git a/src/core/socket.c b/src/core/socket.c index c31e83fefea50..a33a8d6359c98 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2045,13 +2045,11 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { /* Child */ if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds(s->user, /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve user '%s': %s", - user, STRERROR_USER(r)); + s->user, STRERROR_USER(r)); _exit(EXIT_USER); } } diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index a108fe3eb5fb7..fe1042ce4afa8 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -584,11 +584,9 @@ static int change_uid_gid(const CoredumpContext *context) { gid_t gid = context->gid; if (uid_is_system(uid)) { - const char *user = "systemd-coredump"; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-coredump", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { - log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user); + log_warning_errno(r, "Cannot resolve systemd-coredump user. Proceeding to dump core as root: %m"); uid = gid = 0; } } diff --git a/src/creds/creds.c b/src/creds/creds.c index 77f7442ab57aa..c382a0cf80a76 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -988,15 +988,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { else if (streq(opts.arg, "self")) arg_uid = getuid(); else { - const char *name = opts.arg; - r = get_user_creds( - &name, + opts.arg, + /* flags= */ 0, + /* ret_username= */ NULL, &arg_uid, /* ret_gid= */ NULL, /* ret_home= */ NULL, - /* ret_shell= */ NULL, - /* flags= */ 0); + /* ret_shell= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", opts.arg, STRERROR_USER(r)); diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 63eafdceb4aed..19ee4c5b75b71 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1184,7 +1184,7 @@ static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdat const char *path; uid_t uid; - r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); @@ -1244,7 +1244,7 @@ static int verb_enable_linger(int argc, char *argv[], uintptr_t _data, void *use if (isempty(argv[i])) uid = UID_INVALID; else { - r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } @@ -1279,9 +1279,7 @@ static int verb_terminate_user(int argc, char *argv[], uintptr_t _data, void *us if (isempty(argv[i])) uid = getuid(); else { - const char *u = argv[i]; - - r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } @@ -1314,9 +1312,7 @@ static int verb_kill_user(int argc, char *argv[], uintptr_t _data, void *userdat if (isempty(argv[i])) uid = getuid(); else { - const char *u = argv[i]; - - r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index cb623d98e5274..bc522bcbca283 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -183,7 +183,7 @@ static int do_umount(const char *user) { /* The user may be already removed. So, first try to parse the string by parse_uid(), * and if it fails, fall back to get_user_creds(). */ if (parse_uid(user, &uid) < 0) { - r = get_user_creds(&user, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(user, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, r == -ESRCH ? "No such user \"%s\"" : diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 0869fe27e15e6..c35769e56a7f5 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -244,17 +244,14 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { return r; break; - OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): { - const char *user = opts.arg; - - r = get_user_creds(&user, &arg_uid, &arg_gid, NULL, NULL, 0); + OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): + r = get_user_creds(opts.arg, /* flags= */ 0, NULL, &arg_uid, &arg_gid, NULL, NULL); if (r < 0) return log_error_errno(r, r == -EBADMSG ? "UID or GID of user %s are invalid." : "Cannot use \"%s\" as owner: %m", opts.arg); break; - } OPTION_LONG("fsck", "BOOL", "Run a file system check before mount"): r = parse_boolean_argument("--fsck=", opts.arg, &arg_fsck); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 1cefad29de50e..98794bd736fae 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -1492,7 +1492,7 @@ static int config_parse_routing_policy_rule_uid_range( assert(rvalue); - if (get_user_creds(&rvalue, &p->start, NULL, NULL, NULL, 0) >= 0) { + if (get_user_creds(rvalue, /* flags= */ 0, NULL, &p->start, NULL, NULL, NULL) >= 0) { p->end = p->start; return 1; } diff --git a/src/network/networkd.c b/src/network/networkd.c index 73e03c2a25d99..230339ba0c7f3 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -42,13 +42,12 @@ static int run(int argc, char *argv[]) { /* 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 and we can't create our runtime directory. */ if (geteuid() == 0) { - const char *user = "systemd-network"; uid_t uid; gid_t gid; - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-network", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); + return log_error_errno(r, "Cannot resolve user name %s: %m", "systemd-network"); /* Create runtime directory. This is not necessary when networkd is * started with "RuntimeDirectory=systemd/netif", or after diff --git a/src/notify/notify.c b/src/notify/notify.c index 0058eb9d3b961..667a7a64477a5 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -196,7 +196,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_LONG("uid", "USER", "Set user to send from"): - r = get_user_creds(&opts.arg, &arg_uid, &arg_gid, NULL, NULL, 0); + r = get_user_creds(opts.arg, /* flags= */ 0, NULL, &arg_uid, &arg_gid, NULL, NULL); if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ r = parse_uid(opts.arg, &arg_uid); if (r < 0) diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index aaf206ac2c7e9..66171e9b34983 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -44,13 +44,12 @@ static int run(int argc, char *argv[]) { /* Drop privileges, but only if we have been started as root. If we are not running as root we assume most * privileges are already dropped and we can't create our directory. */ if (getuid() == 0) { - const char *user = "systemd-resolve"; uid_t uid; gid_t gid; - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-resolve", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); + return log_error_errno(r, "Cannot resolve user name %s: %m", "systemd-resolve"); /* As we're root, we can create the directory where resolv.conf will live */ r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid, MKDIR_WARN_MODE); diff --git a/src/run/run.c b/src/run/run.c index d1f82001999f5..9c85874db1b4e 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -2581,19 +2581,18 @@ static int start_transient_scope(sd_bus *bus) { } if (arg_exec_user) { - const char *un = arg_exec_user, *home, *shell; + _cleanup_free_ char *user = NULL, *home = NULL, *shell = NULL; uid_t uid; gid_t gid; - r = get_user_creds(&un, &uid, &gid, &home, &shell, - USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS); + r = get_user_creds(arg_exec_user, + USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS, + &user, &uid, &gid, &home, &shell); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", arg_exec_user, STRERROR_USER(r)); - r = free_and_strdup_warn(&arg_exec_user, un); - if (r < 0) - return r; + free_and_replace(arg_exec_user, user); if (home) { r = strv_extendf(&user_env, "HOME=%s", home); diff --git a/src/shared/condition.c b/src/shared/condition.c index af5093a6a3132..e0405eb7dfbf1 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -396,8 +396,7 @@ static int condition_test_user(Condition *c, char **env) { if (streq(username, c->parameter)) return 1; - const char *u = c->parameter; - r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + r = get_user_creds(c->parameter, USER_CREDS_ALLOW_MISSING, NULL, &id, NULL, NULL, NULL); if (r < 0) return 0; diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d125662410ba2..b67fbd57d55db 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1704,9 +1704,7 @@ static int add_matches_for_coredump_uid(sd_journal *j, MatchUnitFlag flags, cons return 0; if (cached_uid == 0) { - const char *user = "systemd-coredump"; - - r = get_user_creds(&user, &cached_uid, NULL, NULL, NULL, 0); + r = get_user_creds("systemd-coredump", /* flags= */ 0, NULL, &cached_uid, NULL, NULL, NULL); if (r < 0) { log_debug_errno(r, "Failed to resolve systemd-coredump user, ignoring: %m"); cached_uid = UID_INVALID; diff --git a/src/test/test-acl-util.c b/src/test/test-acl-util.c index 5967c4b406160..6bf63d1e4faba 100644 --- a/src/test/test-acl-util.c +++ b/src/test/test-acl-util.c @@ -41,8 +41,7 @@ TEST_RET(add_acls_for_user) { ASSERT_OK_ZERO_ERRNO(system(cmd)); if (getuid() == 0 && !userns_has_single_user()) { - const char *nobody = NOBODY_USER_NAME; - r = get_user_creds(&nobody, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(NOBODY_USER_NAME, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) uid = 0; } else diff --git a/src/test/test-ipcrm.c b/src/test/test-ipcrm.c index 238f0bf666987..057dc0ae05a72 100644 --- a/src/test/test-ipcrm.c +++ b/src/test/test-ipcrm.c @@ -13,7 +13,7 @@ static int run(int argc, char *argv[]) { test_setup_logging(LOG_INFO); - r = get_user_creds(&name, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(name, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r == -ESRCH) return log_tests_skipped("Failed to resolve user"); if (r < 0) diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index f5bb24f32af2f..53ad9422f3406 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -287,8 +287,7 @@ TEST(valid_shell) { } static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, gid_t gid, const char *home, const char *shell) { - const char *rhome = NULL; - const char *rshell = NULL; + _cleanup_free_ char *rname = NULL, *rhome = NULL, *rshell = NULL; uid_t ruid = UID_INVALID; gid_t rgid = GID_INVALID; int r; @@ -296,15 +295,15 @@ static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, log_info("/* %s(\"%s\", \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\") */", __func__, id, name, uid, gid, home, shell); - r = get_user_creds(&id, &ruid, &rgid, &rhome, &rshell, 0); + r = get_user_creds(id, /* flags= */ 0, &rname, &ruid, &rgid, &rhome, &rshell); log_info_errno(r, "got \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\": %m", - id, ruid, rgid, strnull(rhome), strnull(rshell)); + strnull(rname), ruid, rgid, strnull(rhome), strnull(rshell)); if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } ASSERT_OK(r); - ASSERT_STREQ(id, name); + ASSERT_STREQ(rname, name); ASSERT_EQ(ruid, uid); ASSERT_EQ(rgid, gid); ASSERT_TRUE(path_equal(rhome, home)); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 09ab88931e983..fa2b21f73a0f0 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -3771,7 +3771,7 @@ static int find_uid(const char *user, uid_t *ret_uid, Hashmap **cache) { /* Second: pass to NSS if we are running "online" */ if (!arg_root) - return get_user_creds(&user, ret_uid, NULL, NULL, NULL, 0); + return get_user_creds(user, /* flags= */ 0, NULL, ret_uid, NULL, NULL, NULL); /* Third, synthesize "root" unconditionally */ if (streq(user, "root")) { From ecaac1c0a2fa9961c5acee024419f0740f44e95b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 21 May 2026 10:25:03 +0000 Subject: [PATCH 2106/2155] TEST-08-INITRD: Set up exitrd on boot rather than including in image Just copy the initramfs from the VM rather than doing it during the image build. --- mkosi/mkosi.conf | 1 - .../systemd/system/initrd-run-initramfs.service | 14 ++++++++++++++ test/integration-tests/TEST-08-INITRD/meson.build | 1 + test/units/TEST-08-INITRD.sh | 6 +++--- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index a6966de2058fb..d539b65e00d73 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -55,7 +55,6 @@ ExtraTrees= %O/minimal-1.root-%a-verity.raw:/usr/share/minimal_1.verity %O/minimal-1.root-%a-verity-sig.raw:/usr/share/minimal_1.verity.sig %O/minimal-base:/usr/share/TEST-13-NSPAWN-container-template - %O/initrd:/exitrd KernelInitrdModules=default diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service new file mode 100644 index 0000000000000..b95397fa23687 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Unit] +Description=Copy initrd contents to /run/initramfs to serve as exitrd +DefaultDependencies=no +AssertPathExists=/etc/initrd-release +After=initrd.target +Before=initrd-cleanup.service initrd-switch-root.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=mkdir -p /run/initramfs +ExecStart=cp -a --one-file-system /. /run/initramfs/ diff --git a/test/integration-tests/TEST-08-INITRD/meson.build b/test/integration-tests/TEST-08-INITRD/meson.build index 5425096a56352..db4a2c6da2dd4 100644 --- a/test/integration-tests/TEST-08-INITRD/meson.build +++ b/test/integration-tests/TEST-08-INITRD/meson.build @@ -5,6 +5,7 @@ integration_tests += [ 'name' : fs.name(meson.current_source_dir()), 'cmdline' : integration_test_template['cmdline'] + [ 'rd.systemd.wants=initrd-run-mount.service', + 'rd.systemd.wants=initrd-run-initramfs.service', ], 'exit-code' : 124, 'vm' : true, diff --git a/test/units/TEST-08-INITRD.sh b/test/units/TEST-08-INITRD.sh index b59a5b99ffe63..7aa2a7e60d353 100755 --- a/test/units/TEST-08-INITRD.sh +++ b/test/units/TEST-08-INITRD.sh @@ -22,8 +22,8 @@ test -d /run/initrd-mount-target mountpoint /run/initrd-mount-target [[ -e /run/initrd-mount-target/hello-world ]] -# Copy the prepared exitrd to its intended location. -mkdir -p /run/initramfs -unzstd --stdout /exitrd | cpio --extract --make-directories --directory /run/initramfs/ +# The initrd-run-initramfs.service in the initrd should have populated /run/initramfs +# from the initrd's own contents before switch-root. +test -x /run/initramfs/shutdown touch /testok From 740a5ce9473cc7570d4a6b9dc0a6c54c0da0e25c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 15:32:24 +0200 Subject: [PATCH 2107/2155] core: introduce ConditionFraction= that is true on a certain percantage of the fleet --- man/systemd.unit.xml | 33 +++++++++++ src/core/load-fragment-gperf.gperf.in | 2 + src/shared/condition.c | 81 +++++++++++++++++++++++++++ src/shared/condition.h | 1 + src/test/test-condition.c | 80 ++++++++++++++++++++++++++ 5 files changed, 197 insertions(+) diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 2f1467b720af4..5c3439d193bfe 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1467,6 +1467,38 @@ + + ConditionFraction= + + ConditionFraction= may be used to enable a unit on a stable, + pseudo-random subset of a fleet of machines. It is primarily useful for staged rollouts: the same + unit (or drop-in) is distributed to every machine in a fleet, but only the configured fraction of + them will actually have it enabled. The decision is derived locally from the machine ID (see + machine-id5), so + it requires no central coordination and is stable over time: a given machine always lands on the + same side of the threshold. + + The argument consists of an optional tag followed by a percentage, + separated by whitespace, for example 30% or + myrollout 30%. The percentage may include up to two decimal places (e.g. + 0.5%). The condition is satisfied on approximately the configured percentage of + all machines; 0% matches no machine and 100% matches every + machine. + + The optional tag is an arbitrary string (not containing whitespace) that is mixed into the + derivation, so that independent rollouts select independent subsets of the + fleet. Without it, all untagged ConditionFraction= checks would select the very + same machines (the same machines would always be picked first). Use distinct tags for unrelated + rollouts, and a shared tag to deliberately target the same machines across several units. + + The test may be negated by prepending an exclamation mark, in which case it is satisfied on the + complementary fraction of machines (e.g. !myrollout 30% matches the other ≈70%). + If the machine ID cannot be determined, the condition fails. + + + + + ConditionKernelCommandLine= @@ -2065,6 +2097,7 @@ AssertArchitecture= AssertVirtualization= AssertHost= + AssertFraction= AssertKernelCommandLine= AssertKernelVersion= AssertVersion= diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 0e2d679f97805..be074230ce5c1 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -382,6 +382,7 @@ Unit.ConditionArchitecture, config_parse_unit_condition_string Unit.ConditionFirmware, config_parse_unit_condition_string, CONDITION_FIRMWARE, offsetof(Unit, conditions) Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) +Unit.ConditionFraction, config_parse_unit_condition_string, CONDITION_FRACTION, offsetof(Unit, conditions) Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) Unit.ConditionKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions) Unit.ConditionVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions) @@ -417,6 +418,7 @@ Unit.AssertFirstBoot, config_parse_unit_condition_string Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) +Unit.AssertFraction, config_parse_unit_condition_string, CONDITION_FRACTION, offsetof(Unit, asserts) Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) Unit.AssertKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts) Unit.AssertVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts) diff --git a/src/shared/condition.c b/src/shared/condition.c index af5093a6a3132..d0e41bbf71688 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -33,6 +33,7 @@ #include "fileio.h" #include "fs-util.h" #include "glob-util.h" +#include "hmac.h" #include "hostname-setup.h" #include "id128-util.h" #include "ima-util.h" @@ -62,6 +63,7 @@ #include "tomoyo-util.h" #include "tpm2-util.h" #include "uid-classification.h" +#include "unaligned.h" #include "user-util.h" #include "virt.h" @@ -690,6 +692,82 @@ static int condition_test_host(Condition *c, char **env) { return true; } +static uint32_t condition_fraction_value(sd_id128_t machine_id, const char *text) { + /* Maps (machine ID, text) to a value uniformly distributed over [0, UINT32_MAX], via + * HMAC-SHA256 keyed by the machine ID over 'text'. The machine ID keys the hash, so each + * machine lands at a stable but unpredictable point; 'text' selects the subset, so that + * distinct rollouts (different texts) pick independent subsets of a fleet. */ + assert(text); + + uint8_t res[SHA256_DIGEST_SIZE]; + hmac_sha256(&machine_id, sizeof(machine_id), text, strlen(text), res); + + return unaligned_read_le32(res); +} + +static int condition_test_fraction(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FRACTION); + + /* Expected syntax: "[TAG ]PERCENT". The percentage is mandatory; the optional leading tag is used as + * a hash salt, so that distinct staged rollouts select independent subsets of a fleet. The condition + * is true for approximately PERCENT of all machines — on a given machine when its derived value + * falls below the threshold. This is useful for staged rollouts: hand the same unit to a whole fleet + * and only a fraction of it will be enabled, with each machine deciding locally and stably (no + * coordination required). */ + + const char *p = c->parameter; + _cleanup_free_ char *first = NULL, *second = NULL; + + r = extract_first_word(&p, &first, /* separators=*/ NULL, /* flags= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to parse ConditionFraction=%s: %m", c->parameter); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ConditionFraction= value is empty."); + + r = extract_first_word(&p, &second, /* separators= */ NULL, /* flags= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to parse ConditionFraction=%s: %m", c->parameter); + + const char *tag, *percent; + if (r == 0) { + /* A single field: it's the percentage, with no tag. */ + tag = NULL; + percent = first; + } else if (isempty(p)) { + /* Two fields: TAG followed by PERCENT. */ + tag = first; + percent = second; + } else + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ConditionFraction=%s has trailing garbage.", c->parameter); + + int permyriad = parse_permyriad(percent); + if (permyriad < 0) + return log_debug_errno(permyriad, "Failed to parse percentage in ConditionFraction=%s: %m", c->parameter); + if (permyriad == 0) + return false; /* 0% → matches no machine */ + if (permyriad >= 10000) + return true; /* 100% → matches every machine */ + + /* Implicitly prefix the tag with a fixed namespace string, so that an absent or empty tag + * still yields a well-defined, non-trivial value (never the HMAC of the empty string), and + * so this value space is domain-separated from other uses of the machine ID. */ + _cleanup_free_ char *text = strjoin("systemd-fraction-", strempty(tag)); + if (!text) + return log_oom_debug(); + + sd_id128_t mid; + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_debug_errno(r, "Failed to get machine ID for ConditionFraction=: %m"); + + return condition_fraction_value(mid, text) < UINT32_SCALE_FROM_PERMYRIAD(permyriad); +} + static int condition_test_ac_power(Condition *c, char **env) { int r; @@ -1252,6 +1330,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_SECURITY] = condition_test_security, [CONDITION_CAPABILITY] = condition_test_capability, [CONDITION_HOST] = condition_test_host, + [CONDITION_FRACTION] = condition_test_fraction, [CONDITION_AC_POWER] = condition_test_ac_power, [CONDITION_ARCHITECTURE] = condition_test_architecture, [CONDITION_FIRMWARE] = condition_test_firmware, @@ -1364,6 +1443,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_FIRMWARE] = "ConditionFirmware", [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", [CONDITION_HOST] = "ConditionHost", + [CONDITION_FRACTION] = "ConditionFraction", [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", [CONDITION_VERSION] = "ConditionVersion", [CONDITION_CREDENTIAL] = "ConditionCredential", @@ -1420,6 +1500,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_FIRMWARE] = "AssertFirmware", [CONDITION_VIRTUALIZATION] = "AssertVirtualization", [CONDITION_HOST] = "AssertHost", + [CONDITION_FRACTION] = "AssertFraction", [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", [CONDITION_VERSION] = "AssertVersion", [CONDITION_CREDENTIAL] = "AssertCredential", diff --git a/src/shared/condition.h b/src/shared/condition.h index d2274522f4f3b..91af2027df615 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -9,6 +9,7 @@ typedef enum ConditionType { CONDITION_FIRMWARE, CONDITION_VIRTUALIZATION, CONDITION_HOST, + CONDITION_FRACTION, CONDITION_KERNEL_COMMAND_LINE, CONDITION_VERSION, CONDITION_CREDENTIAL, diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 234041d1268a1..bee1014c1cbf2 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -34,6 +34,7 @@ #include "rm-rf.h" #include "selinux-util.h" #include "smack-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "tests.h" @@ -243,6 +244,85 @@ TEST(condition_test_host) { condition_free(condition); } +TEST(condition_test_fraction) { + Condition *condition; + int r; + + /* The 0%/100% boundaries are deterministic and short-circuit before the machine ID is even + * read, so these run everywhere. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "0%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "100%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* A tag does not change the boundary behaviour. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "sometag 0%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "sometag 100%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Negation flips the boundaries. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "100%", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "0%", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Malformed values must propagate an error rather than silently passing or failing. */ + FOREACH_STRING(bad, + "", /* empty */ + "abc", /* not a number */ + "30", /* missing percent sign */ + "30 %", /* percent token is just '%' */ + "150%", /* out of range */ + "tag 30% extra", /* trailing garbage */ + "a b 30%") { /* unquoted multi-word tag → trailing garbage */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, bad, /* trigger= */ false, /* negate= */ false))); + ASSERT_FAIL(condition_test(condition, environ)); + condition_free(condition); + } + + sd_id128_t id; + r = sd_id128_get_machine(&id); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return (void) log_tests_skipped("/etc/machine-id missing"); + ASSERT_OK(r); + + /* Distribution check: for a fixed machine ID, varying the tag spreads results uniformly, so + * about PERCENT of the tags match. This is the statistical dual of the production scenario, + * where the tag is fixed and the machine ID varies across a fleet, and it also pins down the + * direction of the comparison (a higher percentage matches more, not fewer). The result is + * deterministic for a given machine ID, and the slack is many standard deviations wide, so + * this does not flake. */ + static const unsigned percentages[] = { 1, 20, 50, 80 }; + FOREACH_ELEMENT(pct, percentages) { + const unsigned n = 10000, slack = 3 * n / 100; /* ±3 percentage points */ + unsigned match = 0; + + for (unsigned i = 0; i < n; i++) { + char param[64]; + xsprintf(param, "tag-%u %u%%", i, *pct); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, param, /* trigger= */ false, /* negate= */ false))); + r = ASSERT_OK(condition_test(condition, environ)); + match += r > 0; + condition_free(condition); + } + + unsigned expected = *pct * n / 100; + ASSERT_TRUE(match + slack >= expected); + ASSERT_TRUE(expected + slack >= match); + } +} + TEST(condition_test_architecture) { Condition *condition; const char *sa; From 5e82a8d6ee6563e5b1b405dab64b1514c02eb8e5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 17:03:39 +0200 Subject: [PATCH 2108/2155] hostnamed: port to regular string_is_safe() --- src/hostname/hostnamed.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 2292c07661350..f8c20ff9c75a7 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -321,18 +321,6 @@ static int context_acquire_device_tree(Context *c) { return 1; } -static bool string_is_safe_for_dbus(const char *s) { - assert(s); - - /* Do some superficial validation: do not allow CCs and make sure D-Bus won't kick us off the bus - * because we send invalid UTF-8 data */ - - if (string_has_cc(s, /* ok= */ NULL)) - return false; - - return utf8_is_valid(s); -} - static int get_dmi_property(Context *c, const char *key, char **ret) { const char *s; int r; @@ -348,7 +336,7 @@ static int get_dmi_property(Context *c, const char *key, char **ret) { if (r < 0) return r; - if (!string_is_safe_for_dbus(s)) + if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) return -ENXIO; return strdup_to(ret, s); @@ -447,7 +435,7 @@ static int get_sysattr(sd_device *device, const char *key, char **ret) { if (r < 0) return log_device_debug_errno(device, r, "Failed to read '%s' attribute: %m", key); - if (!string_is_safe_for_dbus(s)) + if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENXIO), "'%s' attribute is not safe for exposing through DBus: %s", key, s); From 1b94530b14b07b0429099a125ea04f70d6812a50 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 18:45:12 +0200 Subject: [PATCH 2109/2155] hostnamed: introduce "machine tags" concept For management purposes it's useful to be able to "tag" a machine with various labels. Let's add a field for that to /etc/machine-info and make it settable. Fixes: #38591 --- man/machine-info.xml | 24 +++++- man/org.freedesktop.hostname1.xml | 29 +++++++- src/basic/hostname-util.c | 69 +++++++++++++++++ src/basic/hostname-util.h | 6 ++ src/hostname/hostnamed.c | 120 ++++++++++++++++++++++++++++-- src/test/test-hostname-util.c | 64 ++++++++++++++++ 6 files changed, 299 insertions(+), 13 deletions(-) diff --git a/man/machine-info.xml b/man/machine-info.xml index 53c9e64ec49f1..252f341bba655 100644 --- a/man/machine-info.xml +++ b/man/machine-info.xml @@ -139,6 +139,27 @@ + + TAGS= + + A colon-separated list of tags attached to this machine. Tags are short labels that + may be used to classify and group machines for management purposes, for example to identify the role + a machine plays in a deployment (webserver, database), the + fleet or organizational unit it belongs to, or any other administrator-defined attribute. Example: + TAGS=webserver:frontend:berlin. + + Each individual tag must be 1…255 characters long and may consist only of the ASCII + alphanumeric characters, - and .. + + The configured tags may be matched against with the + ConditionMachineTag= and AssertMachineTag= unit settings, see + systemd.unit5 + for details. They may be queried and changed with the tags command of + hostnamectl1. + + + + HARDWARE_VENDOR= @@ -195,7 +216,8 @@ PRETTY_HOSTNAME="Lennart's Tablet" ICON_NAME=computer-tablet CHASSIS=tablet -DEPLOYMENT=production +DEPLOYMENT=production +TAGS=demo:berlin diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index 3d98b88ebc177..d5571de207ff6 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -56,6 +56,7 @@ node /org/freedesktop/hostname1 { in b interactive); SetLocation(in s location, in b interactive); + SetTags(in as tags); GetProductUUID(in b interactive, out ay uuid); GetHardwareSerial(out s serial); @@ -73,6 +74,7 @@ node /org/freedesktop/hostname1 { readonly s Chassis = '...'; readonly s Deployment = '...'; readonly s Location = '...'; + readonly as Tags = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KernelName = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -142,6 +144,8 @@ node /org/freedesktop/hostname1 { + + @@ -168,6 +172,8 @@ node /org/freedesktop/hostname1 { + + @@ -268,6 +274,15 @@ node /org/freedesktop/hostname1 { configure the chassis type if it could not be auto-detected. Set this property to the empty string to reenable the automatic detection of the chassis type from firmware information. + The Tags property exposes a list of machine tags: short + labels that may be used to classify and group machines for management purposes, for example to identify + the role a machine plays in a deployment, the fleet or organizational unit it belongs to, or any other + administrator-defined attribute. Each individual tag is 1…255 characters long and consists only of the + ASCII alphanumeric characters, - and .. The tags are stored as a + colon-separated list in the TAGS= field of /etc/machine-info, see + machine-info5 for + details. If no tags are configured this property is the empty list. + Note that systemd-hostnamed starts only on request and terminates after a short idle period. This effectively means that PropertyChanged messages are not sent out for changes made directly on the files (as in: administrator edits the files with vi). This is @@ -365,8 +380,13 @@ node /org/freedesktop/hostname1 { deployment environment), and Location (physical system location), respectively. + SetTags() sets the machine tags exposed by the Tags + property. It takes a list of strings, each of which must be a valid machine tag (1…255 ASCII + alphanumeric characters, - and .). Passing an empty list removes + the TAGS= field from /etc/machine-info. + PrettyHostname, IconName, Chassis, - Deployment, and Location are stored in + Deployment, Location, and Tags are stored in /etc/machine-info. See machine-info5 for the semantics of those settings. @@ -400,8 +420,8 @@ node /org/freedesktop/hostname1 { org.freedesktop.hostname1.set-hostname. For SetStaticHostname() and SetPrettyHostname() it is org.freedesktop.hostname1.set-static-hostname. For - SetIconName(), SetChassis(), SetDeployment() - and SetLocation() it is + SetIconName(), SetChassis(), SetDeployment(), + SetLocation() and SetTags() it is org.freedesktop.hostname1.set-machine-info. @@ -505,7 +525,8 @@ 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. + GetMachineInfo(), Tags and + SetTags() were added in version 261. diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index 01434d3d641d2..baa735ab10c01 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -253,3 +253,72 @@ int machine_spec_valid(const char *s) { return true; } + +bool machine_tag_is_valid(const char *s) { + size_t n = strlen_ptr(s); + if (n <= 0 || n >= 256) + return false; + + return in_charset(s, ALPHANUMERICAL "-."); +} + +bool machine_tag_list_is_valid(char **l) { + size_t n = 0; + STRV_FOREACH(i, l) { + n++; + if (n > MACHINE_TAGS_MAX) + return false; + + if (!machine_tag_is_valid(*i)) + return false; + } + + return true; +} + +int machine_tags_from_string(const char *s, bool graceful, char ***ret) { + int r; + + assert(ret); + + /* Parses the colon-separated TAGS= machine-info field into a sorted, deduplicated strv. Each tag is + * validated: if 'graceful' is true invalid tags are silently dropped, otherwise an invalid tag makes + * us fail with -EINVAL. The result is NULL if no (valid) tags remain. */ + + if (isempty(s)) { + *ret = NULL; + return 0; + } + + _cleanup_strv_free_ char **l = strv_split(s, ":"); + if (!l) + return -ENOMEM; + + strv_sort_uniq(l); + + if (!graceful) { + if (!machine_tag_list_is_valid(l)) + return -EINVAL; + + *ret = strv_isempty(l) ? NULL : TAKE_PTR(l); + return 0; + } + + size_t n = 0; + _cleanup_strv_free_ char **cleaned = NULL; + STRV_FOREACH(i, l) { + if (!machine_tag_is_valid(*i)) + continue; + + n++; + if (n > MACHINE_TAGS_MAX) + return -E2BIG; + + r = strv_extend(&cleaned, *i); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(cleaned); + return 0; +} diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index 73cd83fbb8d6e..f3d904a1bbf71 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -47,3 +47,9 @@ int get_pretty_hostname(char **ret); int machine_spec_valid(const char *s); int split_user_at_host(const char *s, char **ret_user, char **ret_host); + +#define MACHINE_TAGS_MAX 1024U + +bool machine_tag_is_valid(const char *s); +bool machine_tag_list_is_valid(char **l); +int machine_tags_from_string(const char *s, bool graceful, char ***ret); diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index f8c20ff9c75a7..a39bf5a57eac5 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -43,7 +43,6 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "utf8.h" #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" @@ -58,12 +57,15 @@ typedef enum { PROP_STATIC_HOSTNAME, PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS, - /* Read from /etc/machine-info */ + /* Read from /etc/machine-info (with fallbacks) */ PROP_PRETTY_HOSTNAME, + _PROP_MACHINE_INFO_SETTABLE_FIRST = PROP_PRETTY_HOSTNAME, PROP_ICON_NAME, PROP_CHASSIS, PROP_DEPLOYMENT, PROP_LOCATION, + PROP_TAGS, + _PROP_MACHINE_INFO_SETTABLE_LAST = PROP_TAGS, PROP_HARDWARE_VENDOR, PROP_HARDWARE_MODEL, PROP_HARDWARE_SKU, @@ -77,6 +79,7 @@ typedef enum { PROP_OS_SUPPORT_END, PROP_OS_IMAGE_ID, PROP_OS_IMAGE_VERSION, + _PROP_MAX, _PROP_INVALID = -EINVAL, } HostProperty; @@ -173,6 +176,7 @@ static void context_read_machine_info(Context *c) { PROP_CHASSIS, PROP_DEPLOYMENT, PROP_LOCATION, + PROP_TAGS, PROP_HARDWARE_VENDOR, PROP_HARDWARE_MODEL, PROP_HARDWARE_SKU, @@ -184,6 +188,7 @@ static void context_read_machine_info(Context *c) { "CHASSIS", &c->data[PROP_CHASSIS], "DEPLOYMENT", &c->data[PROP_DEPLOYMENT], "LOCATION", &c->data[PROP_LOCATION], + "TAGS", &c->data[PROP_TAGS], "HARDWARE_VENDOR", &c->data[PROP_HARDWARE_VENDOR], "HARDWARE_MODEL", &c->data[PROP_HARDWARE_MODEL], "HARDWARE_SKU", &c->data[PROP_HARDWARE_SKU], @@ -840,11 +845,12 @@ static int context_write_data_static_hostname(Context *c) { static int context_write_data_machine_info(Context *c) { _cleanup_(unset_statp) struct stat *s = NULL; static const char * const name[_PROP_MAX] = { - [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", - [PROP_ICON_NAME] = "ICON_NAME", - [PROP_CHASSIS] = "CHASSIS", - [PROP_DEPLOYMENT] = "DEPLOYMENT", - [PROP_LOCATION] = "LOCATION", + [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", + [PROP_ICON_NAME] = "ICON_NAME", + [PROP_CHASSIS] = "CHASSIS", + [PROP_DEPLOYMENT] = "DEPLOYMENT", + [PROP_LOCATION] = "LOCATION", + [PROP_TAGS] = "TAGS", }; _cleanup_strv_free_ char **l = NULL; int r; @@ -859,7 +865,7 @@ static int context_write_data_machine_info(Context *c) { if (r < 0 && r != -ENOENT) return r; - for (HostProperty p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { + for (HostProperty p = _PROP_MACHINE_INFO_SETTABLE_FIRST; p <= _PROP_MACHINE_INFO_SETTABLE_LAST; p++) { assert(name[p]); r = strv_env_assign(&l, name[p], empty_to_null(c->data[p])); @@ -1163,6 +1169,29 @@ static int property_get_machine_info_field( return sd_bus_message_append(reply, "s", *(char**) userdata); } +static int property_get_tags( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Context *c = ASSERT_PTR(userdata); + int r; + + context_read_machine_info(c); + + /* Silently drop any invalid tags that might have been written into the file by hand */ + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, &l); + if (r < 0) + log_warning_errno(r, "Failed to parse machine tags '%s', ignoring: %m", strnull(c->data[PROP_TAGS])); + + return sd_bus_message_append_strv(reply, l); +} + static int property_get_os_release_field( sd_bus *bus, const char *path, @@ -1561,6 +1590,74 @@ static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error * return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error); } +static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(m); + + _cleanup_strv_free_ char **tags = NULL; + r = sd_bus_message_read_strv(m, &tags); + if (r < 0) + return r; + + strv_sort_uniq(tags); + + if (strv_length(tags) > MACHINE_TAGS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Too many machine tags specified."); + + _cleanup_free_ char *j = strv_join(tags, ":"); + if (!j) + return log_oom(); + + if (!machine_tag_list_is_valid(tags)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags '%s'", j); + + context_read_machine_info(c); + + if (streq_ptr(empty_to_null(j), empty_to_null(c->data[PROP_TAGS]))) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async_full( + m, + "org.freedesktop.hostname1.set-machine-info", + /* details= */ NULL, + /* good_user= */ UID_INVALID, + /* flags= */ 0, + &c->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 (strv_isempty(tags)) + c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]); + else + free_and_replace(c->data[PROP_TAGS], j); + + r = context_write_data_machine_info(c); + if (r < 0) { + log_error_errno(r, "Failed to write machine info: %m"); + if (ERRNO_IS_PRIVILEGE(r)) + return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info."); + if (r == -EROFS) + return sd_bus_error_set(error, BUS_ERROR_READ_ONLY_FILESYSTEM, "/etc/machine-info is in a read-only filesystem."); + return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m"); + } + + log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS])); + + (void) sd_bus_emit_properties_changed( + sd_bus_message_get_bus(m), + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "Tags", + NULL); + + return sd_bus_reply_method_return(m, NULL); +} + static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Context *c = ASSERT_PTR(userdata); @@ -1645,6 +1742,7 @@ static int method_get_machine_info(sd_bus_message *m, void *userdata, sd_bus_err { "CHASSIS", PROP_CHASSIS }, { "DEPLOYMENT", PROP_DEPLOYMENT }, { "LOCATION", PROP_LOCATION }, + { "TAGS", PROP_TAGS }, { "HARDWARE_VENDOR", PROP_HARDWARE_VENDOR }, { "HARDWARE_MODEL", PROP_HARDWARE_MODEL }, { "HARDWARE_SKU", PROP_HARDWARE_SKU }, @@ -1853,6 +1951,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Deployment", "s", property_get_machine_info_field, offsetof(Context, data[PROP_DEPLOYMENT]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Location", "s", property_get_machine_info_field, offsetof(Context, data[PROP_LOCATION]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Tags", "as", property_get_tags, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("KernelName", "s", property_get_uname_field, offsetof(struct utsname, sysname), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), @@ -1910,6 +2009,11 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_NO_RESULT, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("SetTags", + SD_BUS_ARGS("as", tags), + SD_BUS_NO_RESULT, + method_set_tags, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetProductUUID", SD_BUS_ARGS("b", interactive), SD_BUS_RESULT("ay", uuid), diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index b3e3b1b84a753..1a3cb1dfff552 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "alloc-util.h" #include "hostname-util.h" #include "string-util.h" +#include "strv.h" #include "tests.h" TEST(hostname_is_valid) { @@ -117,4 +119,66 @@ TEST(split_user_at_host) { test_split_user_at_host_one("aa@@@bb", "aa", "@@bb", 1); } +TEST(machine_tag_is_valid) { + assert_se(machine_tag_is_valid("foo")); + assert_se(machine_tag_is_valid("foo-bar.baz")); + assert_se(machine_tag_is_valid("Webserver01")); + assert_se(machine_tag_is_valid("a")); + + assert_se(!machine_tag_is_valid(NULL)); + assert_se(!machine_tag_is_valid("")); + assert_se(!machine_tag_is_valid("foo:bar")); /* colon is the separator */ + assert_se(!machine_tag_is_valid("foo bar")); + assert_se(!machine_tag_is_valid("fööbar")); /* non-ASCII */ + assert_se(!machine_tag_is_valid("foo/bar")); + assert_se(!machine_tag_is_valid("foo_bar")); + + /* Length boundary: 255 characters is fine, 256 is too long */ + _cleanup_free_ char *max = strrep("a", 255), *over = strrep("a", 256); + assert_se(max); + assert_se(over); + assert_se(machine_tag_is_valid(max)); + assert_se(!machine_tag_is_valid(over)); +} + +TEST(machine_tag_list_is_valid) { + assert_se(machine_tag_list_is_valid(NULL)); /* empty list is valid */ + assert_se(machine_tag_list_is_valid(STRV_MAKE("a"))); + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "bar", "c-d.e"))); + + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "b:c"))); + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", ""))); +} + +TEST(machine_tags_from_string) { + _cleanup_strv_free_ char **l = NULL; + + ASSERT_OK(machine_tags_from_string(NULL, /* graceful= */ false, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + ASSERT_OK(machine_tags_from_string("", /* graceful= */ true, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + /* Sorted and deduplicated */ + ASSERT_OK(machine_tags_from_string("foo:bar:foo:baz", /* graceful= */ false, &l)); + assert_se(strv_equal(l, STRV_MAKE("bar", "baz", "foo"))); + l = strv_free(l); + + /* Graceful: invalid tags are dropped, valid ones kept (sorted/deduplicated) */ + ASSERT_OK(machine_tags_from_string("foo:in valid:bar:foo", /* graceful= */ true, &l)); + assert_se(strv_equal(l, STRV_MAKE("bar", "foo"))); + l = strv_free(l); + + /* Graceful: all tags invalid → empty list */ + ASSERT_OK(machine_tags_from_string("in valid:also invalid", /* graceful= */ true, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + /* Fatal: a single invalid tag fails the whole parse */ + ASSERT_ERROR(machine_tags_from_string("foo:in valid:bar", /* graceful= */ false, &l), EINVAL); + assert_se(!l); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 290a417699d0db081378da5dbe210f30db803035 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 18:32:59 +0200 Subject: [PATCH 2110/2155] hostnamectl: port to new --help API --- src/hostname/hostnamectl.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 282a16d83cb37..cc074967b5915 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -9,6 +9,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-common-errors.h" #include "bus-error.h" @@ -18,6 +19,7 @@ #include "bus-util.h" #include "errno-util.h" #include "format-table.h" +#include "help-util.h" #include "hostname-setup.h" #include "hostname-util.h" #include "log.h" @@ -26,7 +28,6 @@ #include "os-util.h" #include "parse-argument.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "string-util.h" #include "time-util.h" @@ -720,14 +721,9 @@ 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(); - r = option_parser_get_help_table(&options); if (r < 0) return r; @@ -738,22 +734,20 @@ static int help(void) { (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()); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Query or change system hostname."); + + help_section("Commands"); r = table_print_or_warn(verbs); if (r < 0) return r; - printf("\nOptions:\n"); + 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("hostnamectl", "1"); return 0; } From 71eac88e5f69cd9432000dd69bd31da8cc34b3c0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 18:44:45 +0200 Subject: [PATCH 2111/2155] hostnamectl: add support for tagging the machine --- man/hostnamectl.xml | 26 ++++++++++ shell-completion/bash/hostnamectl | 2 +- shell-completion/zsh/_hostnamectl | 6 +++ src/hostname/hostnamectl.c | 81 ++++++++++++++++++++++++++++++- 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml index 8ac18349c6718..878837bee1a1c 100644 --- a/man/hostnamectl.xml +++ b/man/hostnamectl.xml @@ -165,6 +165,32 @@ + + + tags [TAG…] + + If no argument is given, print the machine tags currently configured, as a + colon-separated list. If one or more TAG arguments are provided then the + command replaces the configured tags with the specified ones. Each argument may itself be a + colon-separated list of tags, so that the tags may be specified either as multiple arguments or as a + single colon-separated argument, or any combination thereof. Duplicate tags are removed and the + resulting list is sorted before being stored. To remove all tags, invoke the command with a single + empty string argument. + + Machine tags are short labels that may be used to classify and group machines for management + purposes, for example to identify the role a machine plays in a deployment, the fleet or + organizational unit it belongs to, or any other administrator-defined attribute. Each individual tag + must be 1…255 characters long and consist only of ASCII alphanumeric characters, + - and .. The tags are stored in the TAGS= + field of /etc/machine-info; see + machine-info5 for + details. They may also be matched against with the + ConditionMachineTag=/AssertMachineTag= unit settings, see + systemd.unit5. + + + + diff --git a/shell-completion/bash/hostnamectl b/shell-completion/bash/hostnamectl index 0baffeed9c14a..a4ebe2591277e 100644 --- a/shell-completion/bash/hostnamectl +++ b/shell-completion/bash/hostnamectl @@ -67,7 +67,7 @@ _hostnamectl() { local -A VERBS=( [STANDALONE]='status' [ICONS]='icon-name' - [NAME]='hostname deployment location' + [NAME]='hostname deployment location tags' [CHASSIS]='chassis' ) diff --git a/shell-completion/zsh/_hostnamectl b/shell-completion/zsh/_hostnamectl index 76473937a0040..30facfb134c39 100644 --- a/shell-completion/zsh/_hostnamectl +++ b/shell-completion/zsh/_hostnamectl @@ -47,6 +47,11 @@ _hostnamectl_location() { fi } +(( $+functions[_hostnamectl_tags] )) || +_hostnamectl_tags() { + _message "machine tag" +} + (( $+functions[_hostnamectl_commands] )) || _hostnamectl_commands() { local -a _hostnamectl_cmds @@ -57,6 +62,7 @@ _hostnamectl_commands() { "chassis:Get/set chassis type for host" "deployment:Get/set deployment environment for host" "location:Get/set location for host" + "tags:Get/set machine tags for host" ) if (( CURRENT == 1 )); then _describe -t commands 'hostnamectl commands' _hostnamectl_cmds || compadd "$@" diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index cc074967b5915..d52580fbf585b 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -30,6 +30,7 @@ #include "polkit-agent.h" #include "runtime-scope.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "verbs.h" @@ -50,6 +51,7 @@ typedef struct StatusInfo { const char *chassis_asset_tag; const char *deployment; const char *location; + char **tags; const char *kernel_name; const char *kernel_release; const char *os_pretty_name; @@ -197,6 +199,18 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } + if (!strv_isempty(i->tags)) { + _cleanup_free_ char *j = strv_join(i->tags, ":"); + if (!j) + return log_oom(); + + r = table_add_many(table, + TABLE_FIELD, "Tags", + TABLE_STRING, j); + if (r < 0) + return table_log_add_error(r); + } + if (!sd_id128_is_null(i->machine_id)) { r = table_add_many(table, TABLE_FIELD, "Machine ID", @@ -412,8 +426,14 @@ static int get_one_name(sd_bus *bus, const char* attr, char **ret) { return 0; } +static void status_info_done(StatusInfo *info) { + assert(info); + + info->tags = strv_free(info->tags); +} + static int show_all_names(sd_bus *bus) { - StatusInfo info = { + _cleanup_(status_info_done) StatusInfo info = { .vsock_cid = VMADDR_CID_ANY, .os_support_end = USEC_INFINITY, .firmware_date = USEC_INFINITY, @@ -428,6 +448,7 @@ static int show_all_names(sd_bus *bus) { { "ChassisAssetTag", "s", NULL, offsetof(StatusInfo, chassis_asset_tag)}, { "Deployment", "s", NULL, offsetof(StatusInfo, deployment) }, { "Location", "s", NULL, offsetof(StatusInfo, location) }, + { "Tags", "as", NULL, offsetof(StatusInfo, tags) }, { "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) }, { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, @@ -720,6 +741,64 @@ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, voi set_simple_string(userdata, "location", "SetLocation", argv[1]); } +VERB(verb_get_or_set_tags, "tags", "[TAG …]", VERB_ANY, VERB_ANY, 0, "Get/set machine tags for host"); +static int verb_get_or_set_tags(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; + + if (argc == 1) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + + _cleanup_free_ char *j = NULL; + r = bus_get_property(bus, bus_hostname, "Tags", &error, &reply, "as"); + if (r < 0) { + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY)) + return log_error_errno(r, "Could not get property: %s", bus_error_message(&error, r)); + + /* Old hostnamed didn't know the tags concept, hence such a machine has no tags. */ + } else { + _cleanup_strv_free_ char **l = NULL; + r = sd_bus_message_read_strv(reply, &l); + if (r < 0) + return bus_log_parse_error(r); + + j = strv_join(l, ":"); + if (!j) + return log_oom(); + } + + printf("%s\n", strempty(j)); + return 0; + } + + _cleanup_strv_free_ char **l = NULL; + for (int i = 1; i < argc; i++) { + r = strv_split_and_extend(&l, argv[i], ":", /* filter_duplicates= */ true); + if (r < 0) + return log_oom(); + } + + strv_sort(l); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_hostname, "SetTags"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, l); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, /* usec= */ 0, &error, /* ret_reply= */ NULL); + if (r < 0) + return log_error_errno(r, "Could not set tags: %s", bus_error_message(&error, r)); + + return 0; +} + static int help(void) { _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; From 461ec6facc4291cbdb3264d473bcab3e1d88e13a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 May 2026 23:23:15 +0200 Subject: [PATCH 2112/2155] condition: add a condition that matches against the machine tags --- man/systemd.unit.xml | 20 ++++++ src/core/load-fragment-gperf.gperf.in | 2 + src/shared/condition.c | 35 +++++++++++ src/shared/condition.h | 1 + src/test/test-condition.c | 63 +++++++++++++++++++ .../fuzz-unit-file/directives-all.service | 2 + 6 files changed, 123 insertions(+) diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 5c3439d193bfe..2990e65891152 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -2058,6 +2058,25 @@ + + ConditionMachineTag= + + ConditionMachineTag= may be used to match against the tags + assigned to the local machine. Machine tags are short labels that classify and group machines for + management purposes; they are configured in the TAGS= field of + machine-info5 and + may be queried and changed with the tags command of + hostnamectl1. The + argument is a single tag pattern, which is compared against each of the configured tags using + shell-style globbing (*, ?, []). The + condition is satisfied if at least one of the configured tags matches the pattern. The test may be + negated by prepending an exclamation mark, in which case it is satisfied if none of the configured + tags matches. + + + + + ConditionMemoryPressure= ConditionCPUPressure= @@ -2126,6 +2145,7 @@ AssertCPUs= AssertCPUFeature= AssertOSRelease= + AssertMachineTag= AssertMemoryPressure= AssertCPUPressure= AssertIOPressure= diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index be074230ce5c1..60fec56f46a7b 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -398,6 +398,7 @@ Unit.ConditionUser, config_parse_unit_condition_string Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions) Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions) Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions) +Unit.ConditionMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, conditions) Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions) Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions) Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions) @@ -434,6 +435,7 @@ Unit.AssertUser, config_parse_unit_condition_string Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts) Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts) Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts) +Unit.AssertMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, asserts) Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts) Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts) Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts) diff --git a/src/shared/condition.c b/src/shared/condition.c index d0e41bbf71688..ae33c18ade04c 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -35,6 +35,7 @@ #include "glob-util.h" #include "hmac.h" #include "hostname-setup.h" +#include "hostname-util.h" #include "id128-util.h" #include "ima-util.h" #include "initrd-util.h" @@ -316,6 +317,37 @@ static int condition_test_osrelease(Condition *c, char **env) { return true; } +static int condition_test_machine_tag(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_MACHINE_TAG); + + _cleanup_free_ char *tags = NULL; + r = parse_env_file( + /* f= */ NULL, etc_machine_info(), + "TAGS", &tags); + if (r < 0 && r != -ENOENT) { + log_debug_errno(r, "Failed to read /etc/machine-info, ignoring: %m"); + return false; + } + + /* Silently ignore invalid tags, matching the Tags D-Bus property */ + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(tags, /* graceful= */ true, &l); + if (r < 0) { + log_debug_errno(r, "Failed to parse machine tags '%s', ignoring: %m", tags); + return false; + } + + STRV_FOREACH(i, l) + if (fnmatch(c->parameter, *i, /* flags= */ 0) == 0) + return true; + + return false; +} + static int condition_test_memory(Condition *c, char **env) { CompareOperator operator; uint64_t m, k; @@ -1344,6 +1376,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_ENVIRONMENT] = condition_test_environment, [CONDITION_CPU_FEATURE] = condition_test_cpufeature, [CONDITION_OS_RELEASE] = condition_test_osrelease, + [CONDITION_MACHINE_TAG] = condition_test_machine_tag, [CONDITION_MEMORY_PRESSURE] = condition_test_psi, [CONDITION_CPU_PRESSURE] = condition_test_psi, [CONDITION_IO_PRESSURE] = condition_test_psi, @@ -1471,6 +1504,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "ConditionEnvironment", [CONDITION_CPU_FEATURE] = "ConditionCPUFeature", [CONDITION_OS_RELEASE] = "ConditionOSRelease", + [CONDITION_MACHINE_TAG] = "ConditionMachineTag", [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure", [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure", [CONDITION_IO_PRESSURE] = "ConditionIOPressure", @@ -1528,6 +1562,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "AssertEnvironment", [CONDITION_CPU_FEATURE] = "AssertCPUFeature", [CONDITION_OS_RELEASE] = "AssertOSRelease", + [CONDITION_MACHINE_TAG] = "AssertMachineTag", [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure", [CONDITION_CPU_PRESSURE] = "AssertCPUPressure", [CONDITION_IO_PRESSURE] = "AssertIOPressure", diff --git a/src/shared/condition.h b/src/shared/condition.h index 91af2027df615..1f5c33284c3fe 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -21,6 +21,7 @@ typedef enum ConditionType { CONDITION_ENVIRONMENT, CONDITION_CPU_FEATURE, CONDITION_OS_RELEASE, + CONDITION_MACHINE_TAG, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE, diff --git a/src/test/test-condition.c b/src/test/test-condition.c index bee1014c1cbf2..4f450ab12d8d4 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -1414,6 +1414,69 @@ TEST(condition_test_os_release) { condition_free(condition); } +TEST(condition_test_machine_tag) { + Condition *condition; + + /* etc_machine_info() caches the path on first use, so redirect it before anything reads it and + * rewrite the same file (truncating) for each scenario rather than switching paths. */ + _cleanup_free_ char *saved = NULL; + ASSERT_OK(free_and_strdup(&saved, getenv("SYSTEMD_ETC_MACHINE_INFO"))); + + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + ASSERT_OK(mkdtemp_malloc(NULL, &d)); + + _cleanup_free_ char *f = path_join(d, "machine-info"); + ASSERT_NOT_NULL(f); + ASSERT_OK_ERRNO(setenv("SYSTEMD_ETC_MACHINE_INFO", f, /* overwrite= */ true)); + + ASSERT_OK(write_string_file(f, "TAGS=\"webserver:frontend:berlin\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + + /* Exact match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Glob match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "front*", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Negation matches when the tag is absent, ... */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* ... and does not match when the tag is present */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Invalid tags in the file are ignored (matching the Tags D-Bus property) */ + ASSERT_OK(write_string_file(f, "TAGS=\"in valid:good\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "in valid", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "good", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No tags configured at all → never matches */ + ASSERT_OK(write_string_file(f, "PRETTY_HOSTNAME=\"x\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_OK(set_unset_env("SYSTEMD_ETC_MACHINE_INFO", saved, /* overwrite= */ true)); +} + TEST(condition_test_psi) { Condition *condition; CGroupMask mask; diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 9fedef5a63a9e..0d6fadcc967ed 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -24,6 +24,7 @@ AssertHost= AssertIOPressure= AssertKernelCommandLine= AssertKernelVersion= +AssertMachineTag= AssertMemoryPressure= AssertNeedsUpdate= AssertOSRelease= @@ -72,6 +73,7 @@ ConditionHost= ConditionIOPressure= ConditionKernelCommandLine= ConditionKernelVersion= +ConditionMachineTag= ConditionMemoryPressure= ConditionNeedsUpdate= ConditionOSRelease= From b6d9a987f7fbc82cf0f09f6f6b4cc9e1bb26ee78 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 08:39:23 +0200 Subject: [PATCH 2113/2155] firstboot: allow configuring machine tags via firstboot --- man/systemd-firstboot.xml | 35 +++++++++++++ man/systemd.system-credentials.xml | 15 ++++++ src/firstboot/firstboot.c | 81 ++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index db6f2569a8d1f..252ec73feddc3 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -67,6 +67,8 @@ The system hostname + The machine tags + The kernel command line used when installing kernel images The root user's password and shell @@ -198,6 +200,27 @@ + + + + Set the machine tags to the specified colon-separated list. Machine tags are short + labels that may be used to classify and group machines for management purposes, for example to + identify the role a machine plays in a deployment, the fleet or organizational unit it belongs to, or + any other administrator-defined attribute. Each individual tag must be 1…255 characters long and + consist only of ASCII alphanumeric characters, - and .. This + controls the TAGS= field of + machine-info5. The + tags may later be queried and changed with the tags command of + hostnamectl1, and + matched against with the ConditionMachineTag=/AssertMachineTag= + unit settings, see + systemd.unit5. + + Unlike most other settings, machine tags are not prompted for interactively. + + + + @@ -458,6 +481,18 @@ + + + firstboot.machine-tags + + This credential specifies the machine tags to set during first boot, as a + colon-separated list, equivalent to the switch described above. The + tags are written into the TAGS= field of /etc/machine-info + (if that file is not already present), and only have an effect on first boot. If the list contains + invalid tags it is ignored in its entirety. + + + 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 fb1377c560c75..f316961cd728e 100644 --- a/man/systemd.system-credentials.xml +++ b/man/systemd.system-credentials.xml @@ -91,6 +91,21 @@ + + firstboot.machine-tags + + The machine tags to set, as a colon-separated list (e.g. + webserver:frontend:berlin). Read by + systemd-firstboot1, + and only honoured if /etc/machine-info has not been configured before. Written + into the TAGS= field of that file, see + machine-info5 for + details. + + + + + login.issue diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index e328c8550015c..e922abdd63cd3 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -67,6 +67,7 @@ static char *arg_keymap = NULL; static char *arg_timezone = NULL; static char *arg_hostname = NULL; static sd_id128_t arg_machine_id = {}; +static char **arg_machine_tags = NULL; static char *arg_root_password = NULL; static char *arg_root_shell = NULL; static char *arg_kernel_cmdline = NULL; @@ -98,6 +99,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_locale_messages, freep); STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep); STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep); STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_machine_tags, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); @@ -778,6 +780,71 @@ static int process_machine_id(int rfd) { return 0; } +static int process_machine_tags(int rfd) { + int r; + + assert(rfd >= 0); + + _cleanup_free_ char *f = NULL; + _cleanup_close_ int pfd = chase_and_open_parent_at( + /* root_fd= */ rfd, + /* dir_fd= */ rfd, + "/etc/machine-info", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + &f); + if (pfd < 0) + return log_error_errno(pfd, "Failed to chase /etc/machine-info parent: %m"); + + r = should_configure(pfd, f); + if (r == 0) + log_debug("Found /etc/machine-info, assuming machine tags have been configured."); + if (r <= 0) + return r; + + if (!arg_machine_tags) { + _cleanup_free_ char *tags = NULL; + r = read_credential("firstboot.machine-tags", (void**) &tags, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential firstboot.machine-tags, ignoring: %m"); + else { + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(tags, /* graceful= */ false, &l); + if (r < 0) + log_warning_errno(r, "Failed to parse machine tags '%s', ignoring credential: %m", tags); + else { + strv_free_and_replace(arg_machine_tags, l); + log_debug("Acquired machine tags list from credentials."); + } + } + } + + /* NB: We do not prompt for machine tags, at least not for now */ + + if (!arg_machine_tags) { + log_debug("Initialization of machine tags was not requested, skipping."); + return 0; + } + + _cleanup_free_ char *j = strv_join(arg_machine_tags, ":"); + if (!j) + return log_oom(); + + _cleanup_free_ char *c = strjoin("TAGS=\"", j, "\"\n"); + if (!c) + return log_oom(); + + r = write_string_file_at( + pfd, + "machine-info", + c, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); + if (r < 0) + return log_error_errno(r, "Failed to write /etc/machine-info: %m"); + + log_info("/etc/machine-info written."); + return 0; +} + static int prompt_root_password(int rfd, sd_varlink **mute_console_link) { const char *msg1, *msg2; int r; @@ -1363,6 +1430,16 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse machine id %s.", opts.arg); break; + OPTION_LONG("machine-tags", "TAG[:…]", "Set machine tags"): { + _cleanup_strv_free_ char **tags = NULL; + r = machine_tags_from_string(opts.arg, /* graceful= */ false, &tags); + if (r < 0) + return log_error_errno(r, "Failed to parse machine tags '%s': %m", opts.arg); + + strv_free_and_replace(arg_machine_tags, tags); + break; + } + OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): r = free_and_strdup_warn(&arg_root_password, opts.arg); if (r < 0) @@ -1693,6 +1770,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = process_machine_tags(rfd); + if (r < 0) + return r; + return 0; } From 9a28e54b546950c5729cd73a8e6fd86145b856c4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 09:47:16 +0200 Subject: [PATCH 2114/2155] ci: add test for new machine-tags concept --- test/units/TEST-74-AUX-UTILS.machine-tags.sh | 139 +++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100755 test/units/TEST-74-AUX-UTILS.machine-tags.sh diff --git a/test/units/TEST-74-AUX-UTILS.machine-tags.sh b/test/units/TEST-74-AUX-UTILS.machine-tags.sh new file mode 100755 index 0000000000000..5bd421e4898e4 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.machine-tags.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC1091 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if ! command -v hostnamectl >/dev/null; then + echo "hostnamectl not found, skipping the test" + exit 0 +fi + +at_exit() { + set +e + + # Restore the original /etc/machine-info (if any) and make hostnamed read it again + if [[ -e /tmp/machine-info.bak ]]; then + mv /tmp/machine-info.bak /etc/machine-info + else + rm -f /etc/machine-info + fi + systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service + systemctl reset-failed systemd-hostnamed.service + + rm -fr "${ROOT:-}" +} + +trap at_exit EXIT + +# Read the TAGS= field out of /etc/machine-info (robust against the file being +# absent and against TAGS= being unset). +get_tags_from_file() ( + set +u + [[ -f /etc/machine-info ]] && . /etc/machine-info + echo "${TAGS:-}" +) + +if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak +fi + +# Start from a clean slate, and make sure hostnamed re-reads the (now missing) file. +rm -f /etc/machine-info +systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service || : +systemctl reset-failed systemd-hostnamed.service || : + +# -------------------------------------------------------------------------------------------------- +# hostnamectl tags <-> /etc/machine-info +# -------------------------------------------------------------------------------------------------- + +# No tags are configured initially. +assert_eq "$(hostnamectl tags)" "" + +# Set some tags. Multiple arguments, colon-separated arguments, duplicates and arbitrary order are +# all accepted and normalized into a sorted, deduplicated, colon-separated list. +hostnamectl tags webserver:frontend frontend berlin +assert_eq "$(hostnamectl tags)" "berlin:frontend:webserver" + +# The very same list must show up in the TAGS= field of /etc/machine-info. +grep -qE '^TAGS="?berlin:frontend:webserver"?$' /etc/machine-info +assert_eq "$(get_tags_from_file)" "berlin:frontend:webserver" + +# Setting tags again replaces the previous list rather than extending it. +hostnamectl tags database +assert_eq "$(hostnamectl tags)" "database" +assert_eq "$(get_tags_from_file)" "database" + +# Invalid tags (only ASCII alphanumerics, '-' and '.' are allowed) are refused, leaving the +# previously configured tags untouched. +(! hostnamectl tags "invalid tag") +(! hostnamectl tags "invalid/tag") +assert_eq "$(hostnamectl tags)" "database" + +# Clearing all tags via a single empty string argument. When TAGS= was the only field, hostnamed +# removes /etc/machine-info altogether. +hostnamectl tags "" +assert_eq "$(hostnamectl tags)" "" +assert_eq "$(get_tags_from_file)" "" + +# -------------------------------------------------------------------------------------------------- +# ConditionMachineTag=/AssertMachineTag= in a transient unit +# -------------------------------------------------------------------------------------------------- + +hostnamectl tags alpha:beta:gamma +assert_eq "$(hostnamectl tags)" "alpha:beta:gamma" + +# When the condition matches, the unit is started and 'false' actually runs, so systemd-run fails. +# When the condition does not match, the unit is skipped (not failed) and systemd-run succeeds. +(! systemd-run --wait --pipe -p ConditionMachineTag=beta false) +systemd-run --wait --pipe -p ConditionMachineTag=delta false +# Globs are matched against each individual tag. +(! systemd-run --wait --pipe -p ConditionMachineTag='al*' false) +systemd-run --wait --pipe -p ConditionMachineTag='z*' false +# Negation inverts the result. +systemd-run --wait --pipe -p ConditionMachineTag='!beta' false +(! systemd-run --wait --pipe -p ConditionMachineTag='!delta' false) + +# Asserts behave like conditions, except a failing assert puts the unit into a failed state, which +# systemd-run propagates as a non-zero exit code. +systemd-run -p AssertMachineTag=beta -p Type=oneshot true +(! systemd-run -p AssertMachineTag=delta -p Type=oneshot true) + +# -------------------------------------------------------------------------------------------------- +# systemd-firstboot --machine-tags= and the firstboot.machine-tags credential +# -------------------------------------------------------------------------------------------------- + +if command -v systemd-firstboot >/dev/null; then + # The firstboot.machine-tags credential is split on ':', deduplicated and sorted, and written + # into /etc/machine-info underneath the target root. + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:webserver:frontend:webserver:berlin \ + systemd-firstboot --root="$ROOT" + grep -qE '^TAGS="?berlin:frontend:webserver"?$' "$ROOT/etc/machine-info" + + # An invalid tag anywhere in the credential causes the whole list to be ignored, so no + # machine-info file is written. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:'good:bad/tag' \ + systemd-firstboot --root="$ROOT" + test ! -e "$ROOT/etc/machine-info" + + # The --machine-tags= switch is normalized the same way and takes precedence over the credential. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:ignored \ + systemd-firstboot --root="$ROOT" --machine-tags=database:cache:database + grep -qE '^TAGS="?cache:database"?$' "$ROOT/etc/machine-info" + + # An invalid tag passed on the command line is a hard error. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + (! systemd-firstboot --root="$ROOT" --machine-tags='good:bad/tag') +fi From 9990c032956f81b811621f2fa4bef8750cd06608 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 08:42:43 +0200 Subject: [PATCH 2115/2155] update TODO --- TODO.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/TODO.md b/TODO.md index 5c8f92ac42172..6e7cc16112d78 100644 --- a/TODO.md +++ b/TODO.md @@ -159,8 +159,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - sysupdate: add concept for enabling/disabling specific components explicitly, just like features. -- machine-info: add a TAGS concept that can be used to categorize a machine - - udev: add a MACHINE_TAGS field, that augments /etc/machine-info configured tags. @@ -180,13 +178,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - pcrextend: we should measure something when we enter developer mode, by some definition of developer mode. -- /etc/machine-info should have a concept of a "role" that we can put a machine - into, which can be consumed by sysupdate and similar. A role should be - something we can set once (i.e. the initial setting should be protected by - polkit and be somewhat loosely access control, and later settings should use a - different/tougher polkit authorization, so that people can implement a - no-way-back mechanism) - - firstboot: optionally accept credentials at firstboot without authentication - firstboot/sysinstall: add simple interface for prompting users to enable @@ -788,11 +779,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - sysext: make systemd-{sys,conf}ext-sysroot.service work in the split `/var` configuration. -- 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: add fully async modes of the protocol upgrade stuff - repart: maybe remove iso9660/eltorito superblock from disk when booting via From 29c6d1c12549f21c9f39dda09504c2e0bc827df5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 17:26:02 +0200 Subject: [PATCH 2116/2155] boot: measure select SMBIOS objects explicitly --- man/systemd-boot.xml | 13 ++++ man/systemd-stub.xml | 14 +++++ src/boot/boot.c | 6 ++ src/boot/measure-smbios.c | 96 +++++++++++++++++++++++++++++ src/boot/measure-smbios.h | 8 +++ src/boot/meson.build | 1 + src/boot/smbios.c | 122 +++++++++++++++++++++++-------------- src/boot/smbios.h | 25 ++++++++ src/boot/stub.c | 6 ++ src/fundamental/efivars.h | 2 + src/fundamental/tpm2-pcr.h | 9 +++ 11 files changed, 257 insertions(+), 45 deletions(-) create mode 100644 src/boot/measure-smbios.c create mode 100644 src/boot/measure-smbios.h diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 1acf5d083e580..2162c7ffb2bba 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -622,6 +622,19 @@ extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/baz.confext.raw + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). Set by the boot loader if a measurement was successfully completed, and remains + unset otherwise. (Note that systemd-stub performs the same measurement when booted + directly, bypassing the boot loader.) + + + + LoaderImageIdentifier diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 95f62ca66b56a..a5172fed1cc5c 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -664,6 +664,20 @@ + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). This variable is set if a measurement was successfully completed, and remains + unset otherwise. systemd-stub performs this measurement only if it has not already + been done in the same boot (e.g. by the boot loader), as indicated by this variable being set + already. + + + + LoaderBootSecret diff --git a/src/boot/boot.c b/src/boot/boot.c index 9f833f02b1b0f..3ba430a844206 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -17,6 +17,7 @@ #include "iovec-util.h" #include "line-edit.h" #include "measure.h" +#include "measure-smbios.h" #include "memory-util.h" #include "part-discovery.h" #include "pe.h" @@ -3267,6 +3268,7 @@ static void export_loader_variables( EFI_LOADER_FEATURE_TYPE1_UKI_URL | EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS | EFI_LOADER_FEATURE_KEYBOARD_LAYOUT | + EFI_LOADER_FEATURE_SMBIOS_MEASURED | 0; assert(loaded_image); @@ -3434,6 +3436,10 @@ static EFI_STATUS run(EFI_HANDLE image) { export_common_variables(loaded_image); export_loader_variables(loaded_image, init_usec); + /* Measure SMBIOS data into PCR 1. This is done early, and suppressed if sd-stub later runs in + * the same boot (and vice versa), via the LoaderPcrSMBIOS EFI variable. */ + measure_smbios(); + (void) load_drivers(image, loaded_image, root_dir); _cleanup_free_ char16_t *loaded_image_path = NULL; diff --git a/src/boot/measure-smbios.c b/src/boot/measure-smbios.c new file mode 100644 index 0000000000000..2379f4de9e0b8 --- /dev/null +++ b/src/boot/measure-smbios.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-efivars.h" +#include "efi-log.h" +#include "measure.h" +#include "measure-smbios.h" +#include "smbios.h" +#include "tpm2-pcr.h" +#include "util.h" + +static void measure_smbios_raw( + const void *p, + size_t size, + uint32_t event_id, + const char16_t *description, + bool *measured) { + + EFI_STATUS err; + bool m = false; + + assert(p); + assert(description); + assert(measured); + + err = tpm_log_tagged_event( + TPM2_PCR_PLATFORM_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(p), + size, + event_id, + description, + &m); + if (err != EFI_SUCCESS) + log_error_status(err, "Unable to measure SMBIOS structure (%ls), ignoring: %m", description); + + *measured = *measured || m; +} + +static void measure_smbios_type1(const SmbiosHeader *header, size_t size, bool *measured) { + assert(header); + assert(measured); + + /* The wake-up type field varies depending on how the machine was powered on (cold boot, resume + * from sleep, AC restore, …), which would make the measurement non-reproducible. Hence measure a + * copy with that field zeroed out. */ + + assert(size >= sizeof(SmbiosTableType1)); + + _cleanup_free_ SmbiosTableType1 *copy = xmemdup(header, size); + copy->wake_up_type = 0; + + measure_smbios_raw(copy, size, SMBIOS_TYPE1_EVENT_TAG_ID, u"smbios:type1", measured); +} + +static bool measure_smbios_object(const SmbiosHeader *header, size_t size, void *userdata) { + bool *measured = ASSERT_PTR(userdata); + + switch (header->type) { + + case 1: /* System Information */ + measure_smbios_type1(header, size, measured); + break; + + case 2: /* Baseboard Information */ + measure_smbios_raw(header, size, SMBIOS_TYPE2_EVENT_TAG_ID, u"smbios:type2", measured); + break; + + case 11: /* OEM Strings */ + measure_smbios_raw(header, size, SMBIOS_TYPE11_EVENT_TAG_ID, u"smbios:type11", measured); + break; + + default: + break; + } + + return true; /* Keep iterating: there may be more than one matching structure (e.g. type 11). */ +} + +void measure_smbios(void) { + bool measured = false; + + if (!tpm_present()) + return; + + /* If the measurement was already done this boot (e.g. by sd-boot before it chainloaded us), don't + * do it again — re-extending PCR 1 would invalidate the value. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", /* ret_data= */ NULL, /* ret_size= */ NULL) == EFI_SUCCESS) + return; + + /* Measure SMBIOS type 1 (system information), type 2 (baseboard information) and type 11 (OEM + * strings) into PCR 1, in a single pass over the SMBIOS table. */ + smbios_foreach(measure_smbios_object, &measured); + + /* If we measured something, tell the OS which PCR we used (and suppress a second pass). */ + if (measured) + (void) efivar_set_uint64_str16(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", TPM2_PCR_PLATFORM_CONFIG, /* flags= */ 0); +} diff --git a/src/boot/measure-smbios.h b/src/boot/measure-smbios.h new file mode 100644 index 0000000000000..758b2c45b43bb --- /dev/null +++ b/src/boot/measure-smbios.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Measures SMBIOS type 1 (system information, with the volatile "Wake-up Type" field masked) and all + * type 11 (OEM strings) structures into PCR 1, and records the PCR index in the transient + * LoaderPcrSMBIOS EFI variable. Called by both sd-boot and sd-stub; the presence of LoaderPcrSMBIOS + * suppresses a redundant second measurement when both run during the same boot. */ +void measure_smbios(void); diff --git a/src/boot/meson.build b/src/boot/meson.build index 2dc9c64256305..d2524145d0163 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -323,6 +323,7 @@ libefi_sources = files( 'hii.c', 'initrd.c', 'measure.c', + 'measure-smbios.c', 'part-discovery.c', 'pe.c', 'random-seed.c', diff --git a/src/boot/smbios.c b/src/boot/smbios.c index 0bbcf30123d18..46e9817acc88d 100644 --- a/src/boot/smbios.c +++ b/src/boot/smbios.c @@ -39,12 +39,6 @@ typedef struct { uint64_t table_address; } _packed_ Smbios3EntryPoint; -typedef struct { - uint8_t type; - uint8_t length; - uint8_t handle[2]; -} _packed_ SmbiosHeader; - typedef struct { SmbiosHeader header; uint8_t vendor; @@ -56,18 +50,6 @@ typedef struct { uint8_t bios_characteristics_ext[2]; } _packed_ SmbiosTableType0; -typedef struct { - SmbiosHeader header; - uint8_t manufacturer; - uint8_t product_name; - uint8_t version; - uint8_t serial_number; - EFI_GUID uuid; - uint8_t wake_up_type; - uint8_t sku_number; - uint8_t family; -} _packed_ SmbiosTableType1; - typedef struct { SmbiosHeader header; uint8_t manufacturer; @@ -103,6 +85,44 @@ static const void* find_smbios_configuration_table(uint64_t *ret_size) { return NULL; } +/* Given 'p' pointing at a structure header with 'size' bytes left in the table from there onwards, + * returns a pointer just past the end of this structure (i.e. the start of the next one), accounting + * for the formatted area and the trailing string set (terminated by a double NUL byte). Returns NULL + * if the structure is malformed or runs past the end of the table. */ +static const uint8_t* smbios_structure_end(const uint8_t *p, uint64_t size) { + assert(p); + + if (size < sizeof(SmbiosHeader)) + return NULL; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + if (size < header->length) + return NULL; + + /* Skip over formatted area. */ + const uint8_t *q = p + header->length; + size -= header->length; + + /* Special case: if there are no strings appended, we'll see two NUL bytes. */ + if (size >= 2 && q[0] == 0 && q[1] == 0) + return q + 2; + + /* Skip over a populated string table. */ + bool first = true; + for (;;) { + const uint8_t *e = memchr(q, 0, size); + if (!e) + return NULL; + + if (!first && e == q) /* Double NUL byte means we've reached the end of the string table. */ + return q + 1; + + size -= e + 1 - q; + q = e + 1; + first = false; + } +} + static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint64_t *ret_size_left) { uint64_t size; const uint8_t *p = find_smbios_configuration_table(&size); @@ -132,34 +152,12 @@ static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint6 return header; /* Yay! */ } - /* Skip over formatted area. */ - size -= header->length; - p += header->length; - - /* Special case: if there are no strings appended, we'll see two NUL bytes, skip over them */ - if (size >= 2 && p[0] == 0 && p[1] == 0) { - size -= 2; - p += 2; - continue; - } - - /* Skip over a populated string table. */ - bool first = true; - for (;;) { - const uint8_t *e = memchr(p, 0, size); - if (!e) - goto not_found; - - if (!first && e == p) {/* Double NUL byte means we've reached the end of the string table. */ - p++; - size--; - break; - } + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + goto not_found; - size -= e + 1 - p; - p = e + 1; - first = false; - } + size -= next - p; + p = next; } not_found: @@ -169,6 +167,40 @@ static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint6 return NULL; } +void smbios_foreach(SmbiosForeachFunc func, void *userdata) { + assert(func); + + uint64_t size; + const uint8_t *p = find_smbios_configuration_table(&size); + if (!p) + return; + + /* Walks the SMBIOS table exactly once, invoking 'func' for every structure. 'func' receives the + * structure's header (which carries its type) and the structure's total length (formatted area + + * trailing string set); returning false stops the iteration early. */ + + for (;;) { + if (size < sizeof(SmbiosHeader)) + return; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + + /* End of table. */ + if (header->type == 127) + return; + + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + return; + + if (!func(header, next - p, userdata)) + return; + + size -= next - p; + p = next; + } +} + bool smbios_in_hypervisor(void) { /* Look up BIOS Information (Type 0). */ const SmbiosTableType0 *type0 = (const SmbiosTableType0 *) get_smbios_table(0, sizeof(SmbiosTableType0), /* ret_size_left= */ NULL); diff --git a/src/boot/smbios.h b/src/boot/smbios.h index 694ef568e6818..226ebf758f129 100644 --- a/src/boot/smbios.h +++ b/src/boot/smbios.h @@ -3,10 +3,35 @@ #include "efi.h" +typedef struct { + uint8_t type; + uint8_t length; + uint8_t handle[2]; +} _packed_ SmbiosHeader; + +typedef struct { + SmbiosHeader header; + uint8_t manufacturer; + uint8_t product_name; + uint8_t version; + uint8_t serial_number; + EFI_GUID uuid; + uint8_t wake_up_type; + uint8_t sku_number; + uint8_t family; +} _packed_ SmbiosTableType1; + bool smbios_in_hypervisor(void); const char* smbios_find_oem_string(const char *name, const char *after); +/* Invoked by smbios_foreach() for each SMBIOS structure. 'header' points at the structure (which + * carries its type), and 'size' is the structure's total length (formatted area + trailing string + * set). Returning false stops the iteration. */ +typedef bool (*SmbiosForeachFunc)(const SmbiosHeader *header, size_t size, void *userdata); + +void smbios_foreach(SmbiosForeachFunc func, void *userdata); + typedef struct RawSmbiosInfo { const char *manufacturer; const char *product_name; diff --git a/src/boot/stub.c b/src/boot/stub.c index 6411102ec435e..712a82e739850 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -14,6 +14,7 @@ #include "iovec-util.h" #include "linux.h" #include "measure.h" +#include "measure-smbios.h" #include "memory-util.h" #include "part-discovery.h" #include "pe.h" @@ -116,6 +117,7 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsig EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */ EFI_STUB_FEATURE_REPORT_STUB_PARTITION | /* We set StubDevicePartUUID + StubImageIdentifier */ EFI_STUB_FEATURE_REPORT_URL | /* We set StubDeviceURL + LoaderDeviceURL */ + EFI_STUB_FEATURE_SMBIOS_MEASURED | /* We measure SMBIOS data into PCR 1 */ 0; assert(loaded_image); @@ -1252,6 +1254,10 @@ static EFI_STATUS run(EFI_HANDLE image) { export_common_variables(loaded_image); export_stub_variables(loaded_image, profile); + /* Measure SMBIOS data into PCR 1, unless sd-boot already did so in the same boot (tracked via + * the LoaderPcrSMBIOS EFI variable). */ + measure_smbios(); + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ install_embedded_devicetree(loaded_image, sections, &dt_state); install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); diff --git a/src/fundamental/efivars.h b/src/fundamental/efivars.h index eddda7c23a0c8..88bc97d75d6c3 100644 --- a/src/fundamental/efivars.h +++ b/src/fundamental/efivars.h @@ -30,6 +30,7 @@ #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS (UINT64_C(1) << 18) #define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19) #define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20) +#define EFI_LOADER_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 21) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) @@ -44,6 +45,7 @@ #define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9) #define EFI_STUB_FEATURE_REPORT_STUB_PARTITION (UINT64_C(1) << 10) #define EFI_STUB_FEATURE_REPORT_URL (UINT64_C(1) << 11) +#define EFI_STUB_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 12) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, diff --git a/src/fundamental/tpm2-pcr.h b/src/fundamental/tpm2-pcr.h index 1d39ba59596f3..72835e886b78d 100644 --- a/src/fundamental/tpm2-pcr.h +++ b/src/fundamental/tpm2-pcr.h @@ -56,3 +56,12 @@ enum { /* The tag used for EV_EVENT_TAG event log records covering the selected UKI profile */ #define UKI_PROFILE_EVENT_TAG_ID UINT32_C(0x13aed6db) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 1 (system information) structure */ +#define SMBIOS_TYPE1_EVENT_TAG_ID UINT32_C(0xd5cb7cbc) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 2 (baseboard information) structure */ +#define SMBIOS_TYPE2_EVENT_TAG_ID UINT32_C(0xe0d47bc8) + +/* The tag used for EV_EVENT_TAG event log records covering SMBIOS type 11 (OEM strings) structures */ +#define SMBIOS_TYPE11_EVENT_TAG_ID UINT32_C(0xc0b3bd23) From a52e0f87f59b1ee2752ec22160d999838e9d0508 Mon Sep 17 00:00:00 2001 From: Rocker Zhang Date: Thu, 21 May 2026 23:47:48 +0800 Subject: [PATCH 2117/2155] systemctl: also attempt kexec image extraction on EINVAL load_kexec_kernel() retries kexec_file_load() with an extracted kernel (decompressed Image / ZBOOT PE / UKI) when the kernel rejects the image, but it only does so when kexec_file_load() failed with ENOEXEC. On arm64 that retry never happens: arm64's image_probe() (arch/arm64/kernel/kexec_image.c) returns -EINVAL on an ARM64_IMAGE_MAGIC mismatch, whereas x86's bzImage64_probe() and the generic kexec_image_probe_default() return -ENOEXEC. So `systemctl kexec` of a UKI on arm64 skips the extraction path and falls back to the /usr/sbin/kexec binary, which is no longer a dependency since e107c7ead0 ("systemctl: replace kexec-tools dependency with direct kexec_file_load() syscall") -- leaving kexec broken. Accept EINVAL in addition to ENOEXEC. This is safe: the extraction in kexec_maybe_decompress_kernel() re-gates on the actual file magic (MZ / compression headers) and is a no-op returning 0 for anything else, so an EINVAL that is not a format mismatch just falls through to the existing fallback as before. Fixing this in systemd (rather than only in the kernel) is appropriate: systemd must keep working with already-shipped arm64 kernels whose kexec_file_load() returns EINVAL for an unrecognized image magic. Relates to: https://github.com/systemd/systemd/issues/28538 Co-developed-by: Claude Opus 4.7 --- src/systemctl/systemctl-start-special.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index fd38c678ea69a..a8702ad438edf 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -113,9 +113,14 @@ static int load_kexec_kernel(void) { int saved_errno = errno; - if (saved_errno == ENOEXEC) { + if (IN_SET(saved_errno, ENOEXEC, EINVAL)) { /* 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. */ + * kernel (e.g. compressed Image, ZBOOT PE, or UKI) and loading again. + * + * Most architectures' kexec_file_load() returns ENOEXEC when no image loader matches + * (the default behavior of kexec_image_probe_default()), but arm64's image_probe() + * returns EINVAL for an unrecognized magic. Accept both, otherwise `systemctl kexec` + * of a UKI never reaches the extraction path on arm64. */ log_debug_errno(saved_errno, "Kernel rejected image, trying decompression/extraction: %m"); _cleanup_close_ int extracted_kernel_fd = -EBADF, extracted_initrd_fd = -EBADF; From a2ba538ead5630394a0407ef7b4317c12e1b6b84 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 19:33:58 -0700 Subject: [PATCH 2118/2155] bootctl: fix removing variables on uninstall remove_variables looks up the EFI boot entry by matching both the path and the partition UUID and it wasn't actually removing any entries because verb_remove was passing SD_ID128_NULL, so the lookup never matched and Boot#### entries were left behind on uninstall. Fixes 38433a6 --- src/bootctl/bootctl-install.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 77d03e72b6c5c..535019e1e9f15 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1859,7 +1859,6 @@ static int remove_loader_variables(void) { } int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_id128_t uuid = SD_ID128_NULL; int r; _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL; @@ -1927,7 +1926,7 @@ int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { } char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); - RET_GATHER(r, remove_variables(uuid, path, /* in_order= */ true)); + RET_GATHER(r, remove_variables(c.esp_uuid, path, /* in_order= */ true)); return RET_GATHER(r, remove_loader_variables()); } From 1027cbd8ae6a01799e8f4c002a2d46ee3a8043a9 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Tue, 12 May 2026 10:47:17 -0700 Subject: [PATCH 2119/2155] bootctl: rename install_variables/remove_variables to install/remove_boot_option These functions install or remove a single EFI Boot#### entry, not "all variables," so this renames them to better reflect what they do. --- src/bootctl/bootctl-install.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 535019e1e9f15..4478f78eb354c 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1352,7 +1352,7 @@ static int pick_efi_boot_option_description(int esp_fd, char **ret) { return 0; } -static int install_variables( +static int install_boot_option( InstallContext *c, const char *path) { @@ -1632,7 +1632,7 @@ static int run_install(InstallContext *c) { } char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); - return install_variables(c, path); + return install_boot_option(c, path); } int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) { @@ -1810,7 +1810,7 @@ static int remove_binaries(InstallContext *c) { return RET_GATHER(r, remove_boot_efi(c)); } -static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { +static int remove_boot_option(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; @@ -1926,7 +1926,7 @@ int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { } char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); - RET_GATHER(r, remove_variables(c.esp_uuid, path, /* in_order= */ true)); + RET_GATHER(r, remove_boot_option(c.esp_uuid, path, /* in_order= */ true)); return RET_GATHER(r, remove_loader_variables()); } From bb520fd6a578263cf6394733a5f316d0255a787a Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 17:10:05 -0700 Subject: [PATCH 2120/2155] bootctl: add description and ret_slot parameters to install_boot_option() This moves creation of the EFI boot option description out of install_boot_option and into the caller, and adds a ret_slot output parameter for capturing the assigned BootOrder slot. This allows reusing the function for installing variables with different descriptions. --- src/bootctl/bootctl-install.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 4478f78eb354c..db5d982a58382 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1354,12 +1354,16 @@ static int pick_efi_boot_option_description(int esp_fd, char **ret) { static int install_boot_option( InstallContext *c, - const char *path) { + const char *path, + const char *description, + uint16_t *ret_slot) { uint16_t slot; int r; assert(c); + assert(path); + assert(description); if (c->esp_fd < 0) return c->esp_fd; @@ -1396,12 +1400,6 @@ static int install_boot_option( bool existing = r > 0; if (c->operation == INSTALL_NEW || !existing) { - _cleanup_free_ char *description = NULL; - - r = pick_efi_boot_option_description(c->esp_fd, &description); - if (r < 0) - return r; - r = efi_add_boot_option( slot, description, @@ -1424,7 +1422,14 @@ static int install_boot_option( description); } - return insert_into_order(c, slot); + r = insert_into_order(c, slot); + if (r < 0) + return r; + + if (ret_slot) + *ret_slot = slot; + + return 0; } static int are_we_installed(InstallContext *c) { @@ -1632,7 +1637,13 @@ static int run_install(InstallContext *c) { } char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); - return install_boot_option(c, path); + + _cleanup_free_ char *description = NULL; + r = pick_efi_boot_option_description(c->esp_fd, &description); + if (r < 0) + return r; + + return install_boot_option(c, path, description, /* ret_slot= */ NULL); } int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) { From 6c356b6a8bbb6248533a471acc6852a06eb64e47 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 17:35:05 -0700 Subject: [PATCH 2121/2155] bootctl: add after_slot parameter to insert_into_order() This adds an after_slot parameter that, when not set to UINT16_MAX, requests that the new slot be placed immediately after the given slot in BootOrder. When after_slot is set and the new slot already exists in BootOrder, it will leave its position alone. This is so that if a user reorders it, we don't stomp on their changes. --- src/bootctl/bootctl-install.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index db5d982a58382..cb46877e6e1e7 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1233,7 +1233,7 @@ static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { return 0; } -static int insert_into_order(InstallContext *c, uint16_t slot) { +static int insert_into_order(InstallContext *c, uint16_t slot, uint16_t after_slot) { _cleanup_free_ uint16_t *order = NULL; uint16_t *t; int n; @@ -1255,7 +1255,8 @@ static int insert_into_order(InstallContext *c, uint16_t slot) { continue; /* we do not require to be the first one, all is fine */ - if (c->operation != INSTALL_NEW) + /* if after_slot is set, leave existing position alone to preserve user reordering. */ + if (i == 0 || c->operation != INSTALL_NEW || after_slot != UINT16_MAX) return 0; /* move us to the first slot */ @@ -1264,6 +1265,27 @@ static int insert_into_order(InstallContext *c, uint16_t slot) { return efi_set_boot_order(order, n); } + /* slot is not yet in the order, so insert after a specific slot if requested */ + if (after_slot != UINT16_MAX) { + t = reallocarray(order, n + 1, sizeof(uint16_t)); + if (!t) + return -ENOMEM; + order = t; + + for (int i = 0; i < n; i++) { + if (order[i] != after_slot) + continue; + + memmove(order + i + 2, order + i + 1, (n - i - 1) * sizeof(uint16_t)); + order[i + 1] = slot; + return efi_set_boot_order(order, n + 1); + } + + log_warning("Boot entry %04" PRIx16 " not found in BootOrder, appending new entry at the end.", after_slot); + order[n] = slot; + return efi_set_boot_order(order, n + 1); + } + /* extend array */ t = reallocarray(order, n + 1, sizeof(uint16_t)); if (!t) @@ -1422,7 +1444,7 @@ static int install_boot_option( description); } - r = insert_into_order(c, slot); + r = insert_into_order(c, slot, /* after_slot= */ UINT16_MAX); if (r < 0) return r; From 037b9a0680b6dd07dec5c2ef1a13da6eabd398d2 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 18:24:21 -0700 Subject: [PATCH 2122/2155] bootctl: back up sd-boot binary to fallback path on update When a primary sd-boot binary already exists on the ESP and is being updated, it is copied to systemd-boot-fallback{arch}.efi before installing the new version. This gives firmware a fallback Boot#### entry pointing to the previous binary in case the new one fails to load. The fallback is preserved (not overwritten) when its product and version match the currently booted bootloader (read from the LoaderInfo EFI variable), since that means it already holds the known good binary that booted this session. In all other cases it is overwritten with the current primary, when no fallback exists yet, when LoaderInfo is unavailable, or when the fallback's product or version differs from what booted. This also moves the version_check() call up so its result determines both the rotation decision and the main copy, and avoids a duplicate check (and duplicate "Skipping..." log) when the binary is already current. --- src/bootctl/bootctl-install.c | 54 ++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index cb46877e6e1e7..b20857375f0bd 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -731,11 +731,57 @@ static int copy_one_file( if (dest_fd < 0 && dest_fd != -ENOENT) return log_error_errno(dest_fd, "Failed to open '%s' under '%s/EFI/systemd' directory: %m", dest_name, j); - /* Note that if this fails we do the second copy anyway, but return this error code, - * so we stash it away in a separate variable. */ - ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, force); - const char *e = startswith(dest_name, "systemd-boot"); + + /* If a primary sd-boot binary already exists and the source is a newer version, copy + * the existing primary to systemd-boot-fallback{arch}.efi before installing the new + * one, so firmware has a fallback to the previous binary. The fallback is left alone + * when its product and version match the currently booted bootloader (from LoaderInfo), + * so a known good binary stays as the fallback. In all other cases, like no fallback yet, + * LoaderInfo is unavailable, or product/version differs from what booted, it is + * overwritten with the current primary. */ + if (e && dest_fd >= 0 && !force) { + r = version_check(source_fd, source_path, dest_fd, dest_path); + if (r < 0) + /* Stash the error and fall through; the BOOT{arch}.EFI updates below still run. */ + ret = r; + else { + _cleanup_free_ char *fallback_name = strjoin("systemd-boot-fallback", e); + if (!fallback_name) + return log_oom(); + + _cleanup_free_ char *fallback_path = path_join(j, "/EFI/systemd", fallback_name); + if (!fallback_path) + return log_oom(); + + /* Leave the fallback alone if it already holds the currently booted product + * and version, so a known good binary stays as the fallback. If there is no + * fallback yet, LoaderInfo is unavailable, or there is a mismatch, then + * overwrite it with the current primary. */ + bool should_rotate = true; + _cleanup_close_ int fallback_fd = xopenat_full(dest_parent_fd, fallback_name, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (fallback_fd >= 0) { + _cleanup_free_ char *loader_info = NULL, *fallback_version = NULL; + + if (efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &loader_info) >= 0 && + get_file_version(fallback_fd, &fallback_version) >= 0) + should_rotate = compare_product(loader_info, fallback_version) != 0 || + compare_version(loader_info, fallback_version) != 0; + } + + if (should_rotate) { + r = copy_file_with_version_check(dest_path, dest_fd, fallback_path, dest_parent_fd, fallback_name, /* dest_fd= */ -EBADF, /* force= */ true); + if (r < 0) + log_warning_errno(r, "Failed to back up sd-boot binary to fallback path, continuing: %m"); + } + + ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, /* force= */ true); + } + } else + /* Note that if this fails we do the second copy anyway, but return this error code, + * so we stash it away in a separate variable. */ + ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, force); + if (e) { /* Create the EFI default boot loader name (specified for removable devices) */ From 6e0ebe437bc2281db5caa6c3c066badb00afb71f Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 18:46:13 -0700 Subject: [PATCH 2123/2155] bootctl: register fallback EFI Boot#### entry on install This adds a second install_boot_option call to register a Boot#### entry pointing at systemd-boot-fallback{arch}.efi, and place it immediately after the primary entry in BootOrder. The fallback file does not exist on the ESP on first install and is only created on first update when the existing primary binary is rotated to the fallback path. We register the variable anyway, so that the entry exists in the BootOrder once the fallback file shows up. Until then, firmware that reaches the fallback entry will fail to load it and fall through to the next entry in BootOrder, which is fine. install_boot_option gains a require_existing parameter so the existing early return on a missing ESP path can be skipped for the fallback, where a missing path is expected. This also does a bit of refactoring by splitting the bottom part of run_install() into a new install_variables() function that handles registering both the primary and fallback entries. --- src/bootctl/bootctl-install.c | 49 +++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index b20857375f0bd..b3f8be936b305 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1424,6 +1424,8 @@ static int install_boot_option( InstallContext *c, const char *path, const char *description, + bool require_existing, + uint16_t after_slot, uint16_t *ret_slot) { uint16_t slot; @@ -1447,9 +1449,9 @@ static int install_boot_option( CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, F_OK, /* ret_path= */ NULL); - if (r == -ENOENT) + if (r == -ENOENT && require_existing) return 0; - if (r < 0) + if (r < 0 && r != -ENOENT) return log_error_errno(r, "Cannot access \"%s/%s\": %m", j, skip_leading_slash(path)); r = find_slot(c->esp_uuid, path, &slot); @@ -1490,7 +1492,7 @@ static int install_boot_option( description); } - r = insert_into_order(c, slot, /* after_slot= */ UINT16_MAX); + r = insert_into_order(c, slot, after_slot); if (r < 0) return r; @@ -1608,6 +1610,38 @@ static int load_secure_boot_auto_enroll( } #endif +static int install_variables(InstallContext *c, const char *arch) { + int r; + + assert(c); + + const char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); + + _cleanup_free_ char *description = NULL; + r = pick_efi_boot_option_description(c->esp_fd, &description); + if (r < 0) + return r; + + uint16_t primary_slot = UINT16_MAX; + r = install_boot_option(c, path, description, /* require_existing= */ true, /* after_slot= */ UINT16_MAX, &primary_slot); + if (r < 0) + return r; + /* If primary registration was skipped (e.g. binary not on ESP), skip the fallback too + * or else it would land at position 0 in BootOrder with no primary ahead of it. */ + if (primary_slot == UINT16_MAX) + return 0; + + const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", arch, ".efi"); + + _cleanup_free_ char *fallback_description = strjoin("Fallback ", description); + if (!fallback_description) + return log_oom(); + + strshorten(fallback_description, EFI_BOOT_OPTION_DESCRIPTION_MAX); + + return install_boot_option(c, fallback_path, fallback_description, /* require_existing= */ false, /* after_slot= */ primary_slot, /* ret_slot= */ NULL); +} + static int run_install(InstallContext *c) { int r; @@ -1704,14 +1738,7 @@ static int run_install(InstallContext *c) { return 0; } - char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); - - _cleanup_free_ char *description = NULL; - r = pick_efi_boot_option_description(c->esp_fd, &description); - if (r < 0) - return r; - - return install_boot_option(c, path, description, /* ret_slot= */ NULL); + return install_variables(c, arch); } int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) { From 301b4193a0f595008699b8f191bd23dfa3cb4b4b Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 29 Apr 2026 20:32:19 -0700 Subject: [PATCH 2124/2155] bootctl: remove fallback EFI Boot#### variable on uninstall This cleans up the fallback Boot#### entry that was registered on install. The logic cleaning up variables was moved from verb_remove into a new remove_variables function, which mirrors the install side. --- src/bootctl/bootctl-install.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index b3f8be936b305..b609d8f227cd4 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1964,6 +1964,18 @@ static int remove_loader_variables(void) { return r; } +static int remove_variables(sd_id128_t uuid) { + int r = 0; + + const char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); + RET_GATHER(r, remove_boot_option(uuid, path, /* in_order= */ true)); + + const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", get_efi_arch(), ".efi"); + RET_GATHER(r, remove_boot_option(uuid, fallback_path, /* in_order= */ true)); + + return RET_GATHER(r, remove_loader_variables()); +} + int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -2031,9 +2043,7 @@ int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; } - char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); - RET_GATHER(r, remove_boot_option(c.esp_uuid, path, /* in_order= */ true)); - return RET_GATHER(r, remove_loader_variables()); + return remove_variables(c.esp_uuid); } int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata) { From 1d102a663e7e3a023c32a707cc43efed71f2b062 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 21 May 2026 17:51:07 +0000 Subject: [PATCH 2125/2155] meson: Add libucontext to libshared_deps Fixes #42236 --- src/shared/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/meson.build b/src/shared/meson.build index 4d11a2637f2cf..82072b06b64db 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -410,6 +410,7 @@ libshared_deps = [libacl_cflags, libqrencode_cflags, libseccomp_cflags, libselinux_cflags, + libucontext, libxenctrl_cflags, libxz_cflags, libzstd_cflags, From 0e07870f5944f4d4a6fda79e2bd6086f91a0b5a8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 20:42:27 +0900 Subject: [PATCH 2126/2155] test-dhcp-server: several cleanups No effective change, just rafactoring. --- src/libsystemd-network/test-dhcp-server.c | 79 +++++++---------------- 1 file changed, 23 insertions(+), 56 deletions(-) diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 34263f044d082..bbfb4f1c05d0a 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -11,20 +11,7 @@ #include "dhcp-server-internal.h" #include "tests.h" -static void test_pool(struct in_addr *address, unsigned size, int ret) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - ASSERT_OK(sd_dhcp_server_new(&server, 1)); - - if (ret >= 0) - ASSERT_RETURN_IS_CRITICAL(true, ASSERT_OK_EQ(sd_dhcp_server_configure_pool(server, address, 8, 0, size), ret)); - else - ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret)); -} - -static int test_basic(void) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; +TEST(basic) { struct in_addr address_lo = { .s_addr = htobe32(INADDR_LOOPBACK), }; @@ -33,21 +20,21 @@ static int test_basic(void) { }; int r; - log_debug("/* %s */", __func__); - + _cleanup_(sd_event_unrefp) sd_event *event = NULL; ASSERT_OK(sd_event_new(&event)); /* attach to loopback interface */ + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_NOT_NULL(server); - ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0)); - ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY)); - ASSERT_TRUE(sd_dhcp_server_get_event(server) == event); /* ASSERT_EQ() doesn't work here. */ + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL), EBUSY)); + ASSERT_PTR_EQ(sd_dhcp_server_get_event(server), event); ASSERT_OK(sd_dhcp_server_detach_event(server)); ASSERT_NULL(sd_dhcp_server_get_event(server)); - ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); - ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, 0), EBUSY)); + ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL), EBUSY)); ASSERT_TRUE(sd_dhcp_server_ref(server) == server); ASSERT_NULL(sd_dhcp_server_unref(server)); @@ -58,27 +45,23 @@ static int test_basic(void) { ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0), ERANGE)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); - - test_pool(&address_any, 1, -EINVAL); - test_pool(&address_lo, 1, 0); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_any, 8, 0, 1), EINVAL)); + ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 1)); 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 (IN_SET(r, -EPERM, -EAFNOSUPPORT)) - return r; + return (void) log_tests_skipped_errno(r, "cannot start dhcp server"); ASSERT_OK(r); ASSERT_OK(sd_dhcp_server_start(server)); ASSERT_OK(sd_dhcp_server_stop(server)); ASSERT_OK(sd_dhcp_server_stop(server)); ASSERT_OK(sd_dhcp_server_start(server)); - - return 0; } -static void test_message_handler(void) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; +TEST(dhcp_server_handle_message) { struct { DHCPMessageHeader header; struct { @@ -130,8 +113,10 @@ static void test_message_handler(void) { static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' }; int r; - log_debug("/* %s */", __func__); + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_new(&event)); + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); ASSERT_OK(sd_dhcp_server_set_static_lease( @@ -140,8 +125,11 @@ static void test_message_handler(void) { static_lease_client_id, ELEMENTSOF(static_lease_client_id), /* hostname= */ NULL)); - ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); - ASSERT_OK(sd_dhcp_server_start(server)); + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + r = sd_dhcp_server_start(server); + if (IN_SET(r, -EPERM, -EAFNOSUPPORT)) + return (void) log_tests_skipped_errno(r, "cannot start dhcp server"); + ASSERT_OK(r); r = dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL); if (r == -ENETDOWN) @@ -282,11 +270,8 @@ static void test_message_handler(void) { ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); } -static void test_static_lease(void) { +TEST(sd_dhcp_server_set_static_lease) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - log_debug("/* %s */", __func__); - ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_OK(sd_dhcp_server_set_static_lease( @@ -363,11 +348,8 @@ static void test_static_lease(void) { /* hostname= */ NULL)); } -static void test_domain_name(void) { +TEST(sd_dhcp_server_set_domain_name) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - log_debug("/* %s */", __func__); - ASSERT_OK(sd_dhcp_server_new(&server, 1)); /* Test setting domain name */ @@ -401,19 +383,4 @@ static void test_domain_name(void) { ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "local")); } -int main(int argc, char *argv[]) { - int r; - - test_setup_logging(LOG_DEBUG); - - test_static_lease(); - test_domain_name(); - - r = test_basic(); - if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server"); - - test_message_handler(); - - return 0; -} +DEFINE_TEST_MAIN(LOG_DEBUG); From 3f8d0004e9467daeed21b392bfabd53e65912191 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 13:24:33 +0900 Subject: [PATCH 2127/2155] sd-dhcp-server: split into small pieaces No functional change, just several functions are moved/split/renamed. --- src/libsystemd-network/dhcp-server-internal.h | 30 +- .../dhcp-server-lease-internal.h | 1 + src/libsystemd-network/dhcp-server-request.c | 536 +++++++++ src/libsystemd-network/dhcp-server-request.h | 31 + src/libsystemd-network/dhcp-server-send.c | 552 +++++++++ src/libsystemd-network/dhcp-server-send.h | 14 + src/libsystemd-network/meson.build | 2 + src/libsystemd-network/sd-dhcp-server.c | 1016 +---------------- src/libsystemd-network/test-dhcp-server.c | 1 + 9 files changed, 1147 insertions(+), 1036 deletions(-) create mode 100644 src/libsystemd-network/dhcp-server-request.c create mode 100644 src/libsystemd-network/dhcp-server-request.h create mode 100644 src/libsystemd-network/dhcp-server-send.c create mode 100644 src/libsystemd-network/dhcp-server-send.h diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index d4caae66d42b7..c03ce7dddbbdd 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -5,13 +5,11 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include "sd-dhcp-lease.h" #include "sd-dhcp-server.h" -#include "dhcp-client-id-internal.h" #include "dhcp-option.h" -#include "sd-forward.h" #include "network-common.h" +#include "sd-forward.h" #include "sparse-endian.h" #include "tlv-util.h" @@ -74,32 +72,12 @@ typedef struct sd_dhcp_server { char *lease_file; } sd_dhcp_server; -typedef struct DHCPRequest { - /* received message */ - DHCPMessage *message; - - /* options */ - sd_dhcp_client_id client_id; - size_t max_optlen; - be32_t server_id; - be32_t requested_ip; - usec_t lifetime; - const uint8_t *agent_info_option; - char *hostname; - const uint8_t *parameter_request_list; - size_t parameter_request_list_len; - bool rapid_commit; - triple_timestamp timestamp; -} DHCPRequest; - int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options); int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options); -int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, - size_t length, const triple_timestamp *timestamp); -int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset); +void dhcp_server_on_lease_change(sd_dhcp_server *server); +bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address); +bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address); #define log_dhcp_server_errno(server, error, fmt, ...) \ log_interface_prefix_full_errno( \ diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index aeb099ff81878..6d7eb4c21368a 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -5,6 +5,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-server-internal.h" +#include "dhcp-server-request.h" #include "sd-forward.h" typedef struct sd_dhcp_server_lease { diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c new file mode 100644 index 0000000000000..5cccc7c2f64af --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.c @@ -0,0 +1,536 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-network.h" +#include "dhcp-protocol.h" +#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" +#include "dhcp-server-send.h" +#include "errno-util.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "memory-util.h" +#include "siphash24.h" +#include "socket-util.h" +#include "string-util.h" +#include "unaligned.h" + +static DHCPRequest* dhcp_request_free(DHCPRequest *req) { + if (!req) + return NULL; + + free(req->hostname); + return mfree(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); + +static void dhcp_request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) { + assert(req); + + if (timestamp && triple_timestamp_is_set(timestamp)) + req->timestamp = *timestamp; + else + triple_timestamp_now(&req->timestamp); +} + +int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) { + assert(req); + assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock)); + assert(clock_supported(clock)); + assert(ret); + + if (req->lifetime <= 0) + return -ENODATA; + + if (!triple_timestamp_is_set(&req->timestamp)) + return -ENODATA; + + *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime); + return 0; +} + +static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { + int r; + + assert(req); + assert(message); + + req->message = message; + + if (message->hlen > sizeof(message->chaddr)) + return -EBADMSG; + + /* set client id based on MAC address if client did not send an explicit one */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) { + if (!client_id_data_size_is_valid(message->hlen)) + return -EBADMSG; + + r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen); + if (r < 0) + return r; + } + + if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { + uint8_t type; + const void *data; + size_t size; + + /* See RFC2131 section 4.1.1. + * hlen and chaddr may not be set for non-ethernet interface. + * Let's try to retrieve it from the client ID. */ + + if (!sd_dhcp_client_id_is_set(&req->client_id)) + return -EBADMSG; + + r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); + if (r < 0) + return r; + + if (type != 1) + return -EBADMSG; + + if (size > sizeof(message->chaddr)) + return -EBADMSG; + + memcpy(message->chaddr, data, size); + message->hlen = size; + } + + if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) + req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + + if (req->lifetime <= 0) + req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time); + + if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) + req->lifetime = server->max_lease_time; + + return 0; +} + +static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { + DHCPRequest *req = ASSERT_PTR(userdata); + int r; + + switch (code) { + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + if (len == 4) + req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true); + + break; + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: + if (len == 4) + memcpy(&req->requested_ip, option, sizeof(be32_t)); + + break; + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + if (len == 4) + memcpy(&req->server_id, option, sizeof(be32_t)); + + break; + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: + if (client_id_size_is_valid(len)) + (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); + + break; + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + + if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) + req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); + + break; + case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: + req->agent_info_option = (uint8_t*)option - 2; + + break; + case SD_DHCP_OPTION_HOST_NAME: { + _cleanup_free_ char *p = NULL; + + r = dhcp_option_parse_hostname(option, len, &p); + if (r < 0) + log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); + else + free_and_replace(req->hostname, p); + break; + } + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + req->parameter_request_list = option; + req->parameter_request_list_len = len; + break; + + case SD_DHCP_OPTION_RAPID_COMMIT: + req->rapid_commit = true; + break; + } + + return 0; +} + +static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, DHCPRequest **ret, char **ret_error_message) { + int r; + + assert(server); + assert(message); + assert(ret); + assert(ret_error_message); + + _cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1); + if (!req) + return -ENOMEM; + + _cleanup_free_ char *error_message = NULL; + r = dhcp_option_parse(message, length, parse_request, req, &error_message); + if (r < 0) + return r; + int type = r; + + r = ensure_sane_request(server, req, message); + if (r < 0) + return r; + + *ret = TAKE_PTR(req); + *ret_error_message = TAKE_PTR(error_message); + return type; +} + +static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { + usec_t expiration; + int r; + + assert(server); + assert(req); + assert(address != 0); + + r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); + if (r < 0) + return r; + + r = dhcp_server_set_lease(server, address, req, expiration); + if (r < 0) + return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); + + r = server_send_offer_or_ack(server, req, address, DHCP_ACK); + if (r < 0) + return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); + + log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); + + dhcp_server_on_lease_change(server); + + return DHCP_ACK; +} + +static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) { + int r; + + assert(server); + assert(req); + + sd_dhcp_server_lease + *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), + *static_lease = dhcp_server_get_static_lease(server, req); + + log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); + + if (server->pool_size == 0) + /* no pool allocated */ + return 0; + + be32_t address = INADDR_ANY; + + /* for now pick a random free address from the pool */ + if (static_lease) { + sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); + if (l && l != existing_lease) + /* The address is already assigned to another host. Refusing. */ + return 0; + + /* Found a matching static lease. */ + address = static_lease->address; + + } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address)) + + /* If we previously assigned an address to the host, then reuse it. */ + address = existing_lease->address; + + else { + struct siphash state; + uint64_t hash; + + /* Even with no persistence of leases, we try to offer the same client the same IP address. + * We do this by using the hash of the client ID as the offset into the pool of leases when + * finding the next free one. */ + +#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30) + + siphash24_init(&state, HASH_KEY.bytes); + client_id_hash_func(&req->client_id, &state); + hash = htole64(siphash24_finalize(&state)); + + for (unsigned i = 0; i < server->pool_size; i++) { + be32_t a = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size); + if (dhcp_server_address_available(server, a)) { + address = a; + break; + } + } + } + + if (address == INADDR_ANY) + /* no free addresses left */ + return 0; + + if (server->rapid_commit && req->rapid_commit) + return dhcp_server_ack(server, req, address); + + r = server_send_offer_or_ack(server, req, address, DHCP_OFFER); + if (r < 0) + /* this only fails on critical errors */ + return log_dhcp_server_errno(server, r, "Could not send offer: %m"); + + log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid)); + return DHCP_OFFER; +} + +static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) { + assert(server); + assert(req); + + sd_dhcp_server_lease + *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), + *static_lease = dhcp_server_get_static_lease(server, req); + + be32_t address; + bool init_reboot = false; + + /* see RFC 2131, section 4.3.2 */ + + if (req->server_id != 0) { + log_dhcp_server(server, "REQUEST (selecting) (0x%x)", + be32toh(req->message->xid)); + + /* SELECTING */ + if (req->server_id != server->address) + /* client did not pick us */ + return 0; + + if (req->message->ciaddr != 0) + /* this MUST be zero */ + return 0; + + if (req->requested_ip == 0) + /* this must be filled in with the yiaddr + from the chosen OFFER */ + return 0; + + address = req->requested_ip; + } else if (req->requested_ip != 0) { + log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", + be32toh(req->message->xid)); + + /* INIT-REBOOT */ + if (req->message->ciaddr != 0) + /* this MUST be zero */ + return 0; + + /* TODO: check more carefully if IP is correct */ + address = req->requested_ip; + init_reboot = true; + } else { + log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", + be32toh(req->message->xid)); + + /* REBINDING / RENEWING */ + if (req->message->ciaddr == 0) + /* this MUST be filled in with clients IP address */ + return 0; + + address = req->message->ciaddr; + } + + /* Silently ignore Rapid Commit option in REQUEST message. */ + req->rapid_commit = false; + + if (static_lease) { + if (static_lease->address != address) + /* The client requested an address which is different from the static lease. Refusing. */ + return server_send_nak_or_ignore(server, init_reboot, req); + + sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); + if (l && l != existing_lease) + /* The requested address is already assigned to another host. Refusing. */ + return server_send_nak_or_ignore(server, init_reboot, req); + + /* Found a static lease for the client ID. */ + return dhcp_server_ack(server, req, address); + } + + if (dhcp_server_address_is_in_pool(server, address)) + /* The requested address is in the pool. */ + return dhcp_server_ack(server, req, address); + + /* Refuse otherwise. */ + return server_send_nak_or_ignore(server, init_reboot, req); +} + +static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, const char *error_message) { + assert(server); + assert(req); + + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); + + /* TODO: make sure we don't offer this address again */ + + return 0; +} + +static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) { + assert(server); + assert(req); + + log_dhcp_server(server, "RELEASE (0x%x)", + be32toh(req->message->xid)); + + sd_dhcp_server_lease *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); + if (!existing_lease) + return 0; + + if (existing_lease->address != req->message->ciaddr) + return 0; + + sd_dhcp_server_lease_unref(existing_lease); + dhcp_server_on_lease_change(server); + + return 0; +} + +int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) { + int r; + + assert(server); + assert(message); + + if (message->op != BOOTREQUEST) + return 0; + + _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; + _cleanup_free_ char *error_message = NULL; + r = dhcp_server_parse_message(server, message, length, &req, &error_message); + if (r < 0) + return r; + int type = r; + + dhcp_request_set_timestamp(req, timestamp); + + r = dhcp_server_cleanup_expired_leases(server); + if (r < 0) + return r; + + switch (type) { + case DHCP_DISCOVER: + return dhcp_server_process_discover(server, req); + case DHCP_REQUEST: + return dhcp_server_process_request(server, req); + case DHCP_DECLINE: + return dhcp_server_process_decline(server, req, error_message); + case DHCP_RELEASE: + return dhcp_server_process_release(server, req); + default: + return 0; /* Unsupported DHCP message type? Ignore the message silently. */ + } +} + +static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ DHCPMessage *message = NULL; + /* This needs to be initialized with zero. See #20741. + * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ + CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + + CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + sd_dhcp_server *server = ASSERT_PTR(userdata); + struct iovec iov = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + ssize_t datagram_size, len; + int r; + + datagram_size = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size)) + return 0; + if (datagram_size < 0) { + log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + size_t buflen = datagram_size; + message = malloc0(buflen); + if (!message) + return -ENOMEM; + + iov = IOVEC_MAKE(message, datagram_size); + + len = recvmsg_safe(fd, &msg, 0); + if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) + return 0; + if (len < 0) { + log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m"); + return 0; + } + + if ((size_t) len < sizeof(DHCPMessage)) + return 0; + + /* TODO: figure out if this can be done as a filter on the socket, like for IPv6 */ + struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo); + if (info && info->ipi_ifindex != server->ifindex) + return 0; + + r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + if (r < 0) + log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); + + return 0; +} + +int dhcp_server_setup_io_event_source(sd_dhcp_server *server) { + int r; + + assert(server); + assert(server->event); + + r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (r < 0) { + r = -errno; + goto on_error; + } + server->fd_raw = r; + + r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); + if (r < 0) + goto on_error; + server->fd = r; + + r = sd_event_add_io(server->event, &server->receive_message, + server->fd, EPOLLIN, + server_receive_message, server); + if (r < 0) + goto on_error; + + r = sd_event_source_set_priority(server->receive_message, + server->event_priority); + if (r < 0) + goto on_error; + + return 0; + + on_error: + sd_dhcp_server_stop(server); + return r; +} diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h new file mode 100644 index 0000000000000..f1e827a0e13d0 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-client-id-internal.h" +#include "dhcp-protocol.h" +#include "sd-forward.h" +#include "sparse-endian.h" +#include "time-util.h" + +typedef struct DHCPRequest { + /* received message */ + DHCPMessage *message; + triple_timestamp timestamp; + + /* options */ + sd_dhcp_client_id client_id; + size_t max_optlen; + be32_t server_id; + be32_t requested_ip; + usec_t lifetime; + const uint8_t *agent_info_option; + char *hostname; + const uint8_t *parameter_request_list; + size_t parameter_request_list_len; + bool rapid_commit; +} DHCPRequest; + +int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret); + +int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp); +int dhcp_server_setup_io_event_source(sd_dhcp_server *server); diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c new file mode 100644 index 0000000000000..67fc390bca3b7 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.c @@ -0,0 +1,552 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "dhcp-network.h" +#include "dhcp-option.h" +#include "dhcp-packet.h" +#include "dhcp-server-lease-internal.h" +#include "dhcp-server-send.h" +#include "dns-domain.h" +#include "errno-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "socket-util.h" + +static int dhcp_server_send_unicast_raw( + sd_dhcp_server *server, + uint8_t hlen, + const uint8_t *chaddr, + DHCPPacket *packet, + size_t len) { + + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = server->ifindex, + .ll.sll_halen = hlen, + }; + int r; + + assert(server); + assert(server->ifindex > 0); + assert(server->address != 0); + assert(hlen > 0); + assert(chaddr); + assert(packet); + assert(len > sizeof(DHCPPacket)); + + memcpy(link.ll.sll_addr, chaddr, hlen); + + if (len > UINT16_MAX) + return -EOVERFLOW; + + 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, + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(packet, len), + .count = 1, + }); +} + +static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, + uint16_t destination_port, + DHCPMessage *message, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(destination_port), + .in.sin_addr.s_addr = destination, + }; + struct iovec iov = { + .iov_base = message, + .iov_len = len, + }; + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + struct msghdr msg = { + .msg_name = &dest, + .msg_namelen = sizeof(dest.in), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + struct in_pktinfo *pktinfo; + + assert(server); + assert(server->fd >= 0); + assert(message); + assert(len >= sizeof(DHCPMessage)); + + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); + + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); + assert(pktinfo); + + pktinfo->ipi_ifindex = server->ifindex; + pktinfo->ipi_spec_dst.s_addr = server->address; + + if (sendmsg(server->fd, &msg, 0) < 0) + return -errno; + + return 0; +} + +static bool requested_broadcast(DHCPMessage *message) { + assert(message); + return message->flags & htobe16(0x8000); +} + +static int dhcp_server_send( + sd_dhcp_server *server, + uint8_t hlen, + const uint8_t *chaddr, + be32_t destination, + uint16_t destination_port, + DHCPPacket *packet, + size_t optoffset, + bool l2_broadcast) { + + if (destination != INADDR_ANY) + return dhcp_server_send_udp(server, destination, + destination_port, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else if (l2_broadcast) + return dhcp_server_send_udp(server, INADDR_BROADCAST, + destination_port, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else + /* we cannot send UDP packet to specific MAC address when the + address is not yet configured, so must fall back to raw + packets */ + return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet, + sizeof(DHCPPacket) + optoffset); +} + +static int dhcp_server_send_packet(sd_dhcp_server *server, + DHCPRequest *req, DHCPPacket *packet, + int type, size_t optoffset) { + be32_t destination = INADDR_ANY; + uint16_t destination_port = DHCP_PORT_CLIENT; + int r; + + assert(server); + assert(req); + assert(req->max_optlen > 0); + assert(req->message); + assert(optoffset <= req->max_optlen); + assert(packet); + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &server->address); + if (r < 0) + return r; + + if (req->agent_info_option) { + size_t opt_full_length = *(req->agent_info_option + 1) + 2; + /* there must be space left for SD_DHCP_OPTION_END */ + if (optoffset + opt_full_length < req->max_optlen) { + memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length); + optoffset += opt_full_length; + } + } + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* RFC 2131 Section 4.1 + + If the ’giaddr’ field in a DHCP message from a client is non-zero, + the server sends any return messages to the ’DHCP server’ port on the + BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ + field is zero and the ’ciaddr’ field is nonzero, then the server + unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. + If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is + set, then the server broadcasts DHCPOFFER and DHCPACK messages to + 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and + ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK + messages to the client’s hardware address and ’yiaddr’ address. In + all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK + messages to 0xffffffff. + + Section 4.3.2 + + If ’giaddr’ is set in the DHCPREQUEST message, the client is on a + different subnet. The server MUST set the broadcast bit in the + DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the + client, because the client may not have a correct network address + or subnet mask, and the client may not be answering ARP requests. + */ + if (req->message->giaddr != 0) { + destination = req->message->giaddr; + destination_port = DHCP_PORT_SERVER; + if (type == DHCP_NAK) + packet->dhcp.flags = htobe16(0x8000); + } else if (req->message->ciaddr != 0 && type != DHCP_NAK) + destination = req->message->ciaddr; + + bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK; + return dhcp_server_send(server, req->message->hlen, req->message->chaddr, + destination, destination_port, packet, optoffset, l2_broadcast); +} + +static int server_message_init( + sd_dhcp_server *server, + DHCPPacket **ret, + uint8_t type, + size_t *ret_optoffset, + DHCPRequest *req) { + + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(ret); + assert(ret_optoffset); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + assert(req); + + packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, + be32toh(req->message->xid), + req->message->htype, req->message->hlen, req->message->chaddr, + type, req->max_optlen, &optoffset); + if (r < 0) + return r; + + packet->dhcp.flags = req->message->flags; + packet->dhcp.giaddr = req->message->giaddr; + + *ret_optoffset = optoffset; + *ret = TAKE_PTR(packet); + + return 0; +} + +static int dhcp_server_append_static_hostname( + sd_dhcp_server *server, + DHCPPacket *packet, + size_t *offset, + DHCPRequest *req) { + + sd_dhcp_server_lease *static_lease; + int r; + + assert(server); + assert(packet); + assert(offset); + assert(req); + + static_lease = dhcp_server_get_static_lease(server, req); + if (!static_lease || !static_lease->hostname) + return 0; + + if (dns_name_is_single_label(static_lease->hostname)) + /* Option 12 */ + return dhcp_option_append( + &packet->dhcp, + req->max_optlen, + offset, + /* overload= */ 0, + SD_DHCP_OPTION_HOST_NAME, + strlen(static_lease->hostname), + static_lease->hostname); + + + /* Option 81 */ + uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3]; + + /* Flags: S=0 (will not update RR), O=1 (are overriding client), + * E=1 (using DNS wire format), N=1 (will not update DNS) */ + buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N; + + /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on + * receipt. */ + buffer[1] = 255; + buffer[2] = 255; + + r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); + if (r < 0) + return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); + if (r > DHCP_MAX_FQDN_LENGTH) + return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long"); + + return dhcp_option_append( + &packet->dhcp, + req->max_optlen, + offset, + /* overload= */ 0, + SD_DHCP_OPTION_FQDN, + 3 + r, + buffer); +} + +static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { + assert(req); + + if (!req->parameter_request_list) + return false; + + return memchr(req->parameter_request_list, option, req->parameter_request_list_len); +} + +int server_send_offer_or_ack( + sd_dhcp_server *server, + DHCPRequest *req, + be32_t address, + uint8_t type) { + + static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { + [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, + [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, + [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, + [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, + [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, + }; + + _cleanup_free_ DHCPPacket *packet = NULL; + be32_t lease_time; + size_t offset; + int r; + + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); + + r = server_message_init(server, &packet, type, &offset, req); + if (r < 0) + return r; + + packet->dhcp.yiaddr = address; + packet->dhcp.siaddr = server->boot_server_address.s_addr; + + lease_time = usec_to_be32_sec(req->lifetime); + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + &lease_time); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + if (r < 0) + return r; + + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, + in4_addr_is_set(&server->router_address) ? + &server->router_address.s_addr : + &server->address); + if (r < 0) + return r; + } + + if (server->boot_server_name) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_BOOT_SERVER_NAME, + strlen(server->boot_server_name), server->boot_server_name); + if (r < 0) + return r; + } + + if (server->boot_filename) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_BOOT_FILENAME, + strlen(server->boot_filename), server->boot_filename); + if (r < 0) + return r; + } + + for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { + if (server->servers[k].size <= 0) + continue; + + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + option_map[k], + sizeof(struct in_addr) * server->servers[k].size, + server->servers[k].addr); + if (r < 0) + return r; + } + + if (server->timezone) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_TZDB_TIMEZONE, + strlen(server->timezone), server->timezone); + if (r < 0) + return r; + } + + if (server->domain_name) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_DOMAIN_NAME, + strlen(server->domain_name), server->domain_name); + if (r < 0) + return r; + } + + /* RFC 8925 section 3.3. DHCPv4 Server Behavior + * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if + * the option was not present in the Parameter Request List sent by the client. */ + if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && + server->ipv6_only_preferred_usec > 0) { + be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); + + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, + sizeof(sec), &sec); + if (r < 0) + return r; + } + + if (server->extra_options) { + void *key; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) { + uint32_t tag = PTR_TO_UINT32(key); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + tag, iov->iov_len, iov->iov_base); + if (r < 0) + return r; + } + } + } + + if (!tlv_isempty(server->vendor_options)) { + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(server->vendor_options, &iov); + if (r < 0) + return r; + + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + iov.iov_len, iov.iov_base); + if (r < 0) + return r; + } + + if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_RAPID_COMMIT, + 0, NULL); + if (r < 0) + return r; + } + + r = dhcp_server_append_static_hostname(server, packet, &offset, req); + if (r < 0) + return r; + + return dhcp_server_send_packet(server, req, packet, type, offset); +} + +int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + int r; + + /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the + * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the + * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */ + + if (!init_reboot) + return 0; + + r = server_message_init(server, &packet, DHCP_NAK, &offset, req); + if (r < 0) + return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m"); + + r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); + if (r < 0) + return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); + + log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid)); + return DHCP_NAK; +} + +static int server_send_forcerenew( + sd_dhcp_server *server, + be32_t address, + be32_t gateway, + uint8_t htype, + uint8_t hlen, + const uint8_t *chaddr) { + + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(address != INADDR_ANY); + assert(chaddr); + + packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, + htype, hlen, chaddr, DHCP_FORCERENEW, + DHCP_MIN_OPTIONS_SIZE, &optoffset); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, + &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); +} + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { + sd_dhcp_server_lease *lease; + int r = 0; + + assert_return(server, -EINVAL); + + log_dhcp_server(server, "FORCERENEW"); + + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) + RET_GATHER(r, + server_send_forcerenew(server, lease->address, lease->gateway, + lease->htype, lease->hlen, lease->chaddr)); + return r; +} diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h new file mode 100644 index 0000000000000..80fd9a6b94fe5 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "dhcp-server-request.h" + +int server_send_offer_or_ack( + sd_dhcp_server *server, + DHCPRequest *req, + be32_t address, + uint8_t type); + +int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 9b36f6edf2cf6..3981ecd7d5815 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -13,6 +13,8 @@ libsystemd_network_sources = files( 'dhcp-relay-interface.c', 'dhcp-relay-upstream.c', 'dhcp-route.c', + 'dhcp-server-request.c', + 'dhcp-server-send.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 07f15ef6dc20c..5267137540d21 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -5,33 +5,25 @@ #include "sd-dhcp-server.h" #include "sd-event.h" -#include "sd-id128.h" #include "alloc-util.h" -#include "dhcp-network.h" #include "dhcp-option.h" -#include "dhcp-packet.h" #include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" #include "dns-domain.h" -#include "errno-util.h" #include "fd-util.h" #include "hashmap.h" #include "in-addr-util.h" -#include "iovec-util.h" -#include "iovec-wrapper.h" -#include "memory-util.h" #include "network-common.h" #include "path-util.h" -#include "siphash24.h" #include "socket-util.h" #include "string-util.h" -#include "unaligned.h" #define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR #define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12) -static void server_on_lease_change(sd_dhcp_server *server) { +void dhcp_server_on_lease_change(sd_dhcp_server *server) { int r; assert(server); @@ -288,681 +280,7 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { return 0; } -static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { - assert(req); - - if (!req->parameter_request_list) - return false; - - return memchr(req->parameter_request_list, option, req->parameter_request_list_len); -} - -static int dhcp_server_send_unicast_raw( - sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, - DHCPPacket *packet, - size_t len) { - - union sockaddr_union link = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_IP), - .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hlen, - }; - int r; - - assert(server); - assert(server->ifindex > 0); - assert(server->address != 0); - assert(hlen > 0); - assert(chaddr); - assert(packet); - assert(len > sizeof(DHCPPacket)); - - memcpy(link.ll.sll_addr, chaddr, hlen); - - if (len > UINT16_MAX) - return -EOVERFLOW; - - 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, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(packet, len), - .count = 1, - }); -} - -static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, - uint16_t destination_port, - DHCPMessage *message, size_t len) { - union sockaddr_union dest = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(destination_port), - .in.sin_addr.s_addr = destination, - }; - struct iovec iov = { - .iov_base = message, - .iov_len = len, - }; - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - struct msghdr msg = { - .msg_name = &dest, - .msg_namelen = sizeof(dest.in), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - struct cmsghdr *cmsg; - struct in_pktinfo *pktinfo; - - assert(server); - assert(server->fd >= 0); - assert(message); - assert(len >= sizeof(DHCPMessage)); - - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); - - cmsg = CMSG_FIRSTHDR(&msg); - assert(cmsg); - - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - - pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); - assert(pktinfo); - - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - - if (sendmsg(server->fd, &msg, 0) < 0) - return -errno; - - return 0; -} - -static bool requested_broadcast(DHCPMessage *message) { - assert(message); - return message->flags & htobe16(0x8000); -} - -static int dhcp_server_send( - sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, - be32_t destination, - uint16_t destination_port, - DHCPPacket *packet, - size_t optoffset, - bool l2_broadcast) { - - if (destination != INADDR_ANY) - return dhcp_server_send_udp(server, destination, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else if (l2_broadcast) - return dhcp_server_send_udp(server, INADDR_BROADCAST, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else - /* we cannot send UDP packet to specific MAC address when the - address is not yet configured, so must fall back to raw - packets */ - return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet, - sizeof(DHCPPacket) + optoffset); -} - -int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset) { - be32_t destination = INADDR_ANY; - uint16_t destination_port = DHCP_PORT_CLIENT; - int r; - - assert(server); - assert(req); - assert(req->max_optlen > 0); - assert(req->message); - assert(optoffset <= req->max_optlen); - assert(packet); - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &server->address); - if (r < 0) - return r; - - if (req->agent_info_option) { - size_t opt_full_length = *(req->agent_info_option + 1) + 2; - /* there must be space left for SD_DHCP_OPTION_END */ - if (optoffset + opt_full_length < req->max_optlen) { - memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length); - optoffset += opt_full_length; - } - } - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - /* RFC 2131 Section 4.1 - - If the ’giaddr’ field in a DHCP message from a client is non-zero, - the server sends any return messages to the ’DHCP server’ port on the - BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ - field is zero and the ’ciaddr’ field is nonzero, then the server - unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. - If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is - set, then the server broadcasts DHCPOFFER and DHCPACK messages to - 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and - ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK - messages to the client’s hardware address and ’yiaddr’ address. In - all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK - messages to 0xffffffff. - - Section 4.3.2 - - If ’giaddr’ is set in the DHCPREQUEST message, the client is on a - different subnet. The server MUST set the broadcast bit in the - DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the - client, because the client may not have a correct network address - or subnet mask, and the client may not be answering ARP requests. - */ - if (req->message->giaddr != 0) { - destination = req->message->giaddr; - destination_port = DHCP_PORT_SERVER; - if (type == DHCP_NAK) - packet->dhcp.flags = htobe16(0x8000); - } else if (req->message->ciaddr != 0 && type != DHCP_NAK) - destination = req->message->ciaddr; - - bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK; - return dhcp_server_send(server, req->message->hlen, req->message->chaddr, - destination, destination_port, packet, optoffset, l2_broadcast); -} - -static int server_message_init( - sd_dhcp_server *server, - DHCPPacket **ret, - uint8_t type, - size_t *ret_optoffset, - DHCPRequest *req) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; - int r; - - assert(server); - assert(ret); - assert(ret_optoffset); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); - assert(req); - - packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->xid), - req->message->htype, req->message->hlen, req->message->chaddr, - type, req->max_optlen, &optoffset); - if (r < 0) - return r; - - packet->dhcp.flags = req->message->flags; - packet->dhcp.giaddr = req->message->giaddr; - - *ret_optoffset = optoffset; - *ret = TAKE_PTR(packet); - - return 0; -} - -static int dhcp_server_append_static_hostname( - sd_dhcp_server *server, - DHCPPacket *packet, - size_t *offset, - DHCPRequest *req) { - - sd_dhcp_server_lease *static_lease; - int r; - - assert(server); - assert(packet); - assert(offset); - assert(req); - - static_lease = dhcp_server_get_static_lease(server, req); - if (!static_lease || !static_lease->hostname) - return 0; - - if (dns_name_is_single_label(static_lease->hostname)) - /* Option 12 */ - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(static_lease->hostname), - static_lease->hostname); - - - /* Option 81 */ - uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3]; - - /* Flags: S=0 (will not update RR), O=1 (are overriding client), - * E=1 (using DNS wire format), N=1 (will not update DNS) */ - buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N; - - /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on - * receipt. */ - buffer[1] = 255; - buffer[2] = 255; - - r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); - if (r > DHCP_MAX_FQDN_LENGTH) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long"); - - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_FQDN, - 3 + r, - buffer); -} - -static int server_send_offer_or_ack( - sd_dhcp_server *server, - DHCPRequest *req, - be32_t address, - uint8_t type) { - - static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { - [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, - [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, - [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, - [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, - [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, - [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, - }; - - _cleanup_free_ DHCPPacket *packet = NULL; - be32_t lease_time; - size_t offset; - int r; - - assert(server); - assert(req); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); - - r = server_message_init(server, &packet, type, &offset, req); - if (r < 0) - return r; - - packet->dhcp.yiaddr = address; - packet->dhcp.siaddr = server->boot_server_address.s_addr; - - lease_time = usec_to_be32_sec(req->lifetime); - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, - &lease_time); - if (r < 0) - return r; - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); - if (r < 0) - return r; - - if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, - in4_addr_is_set(&server->router_address) ? - &server->router_address.s_addr : - &server->address); - if (r < 0) - return r; - } - - if (server->boot_server_name) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_SERVER_NAME, - strlen(server->boot_server_name), server->boot_server_name); - if (r < 0) - return r; - } - - if (server->boot_filename) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_FILENAME, - strlen(server->boot_filename), server->boot_filename); - if (r < 0) - return r; - } - - for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { - if (server->servers[k].size <= 0) - continue; - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - option_map[k], - sizeof(struct in_addr) * server->servers[k].size, - server->servers[k].addr); - if (r < 0) - return r; - } - - if (server->timezone) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_TZDB_TIMEZONE, - strlen(server->timezone), server->timezone); - if (r < 0) - return r; - } - - if (server->domain_name) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_DOMAIN_NAME, - strlen(server->domain_name), server->domain_name); - if (r < 0) - return r; - } - - /* RFC 8925 section 3.3. DHCPv4 Server Behavior - * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if - * the option was not present in the Parameter Request List sent by the client. */ - if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && - server->ipv6_only_preferred_usec > 0) { - be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, - sizeof(sec), &sec); - if (r < 0) - return r; - } - - if (server->extra_options) { - void *key; - struct iovec_wrapper *iovw; - HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) { - uint32_t tag = PTR_TO_UINT32(key); - - FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - tag, iov->iov_len, iov->iov_base); - if (r < 0) - return r; - } - } - } - - if (!tlv_isempty(server->vendor_options)) { - _cleanup_(iovec_done) struct iovec iov = {}; - r = tlv_build(server->vendor_options, &iov); - if (r < 0) - return r; - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - iov.iov_len, iov.iov_base); - if (r < 0) - return r; - } - - if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, - 0, NULL); - if (r < 0) - return r; - } - - r = dhcp_server_append_static_hostname(server, packet, &offset, req); - if (r < 0) - return r; - - return dhcp_server_send_packet(server, req, packet, type, offset); -} - -static int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t offset; - int r; - - /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the - * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the - * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */ - - if (!init_reboot) - return 0; - - r = server_message_init(server, &packet, DHCP_NAK, &offset, req); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m"); - - r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); - - log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid)); - return DHCP_NAK; -} - -static int server_send_forcerenew( - sd_dhcp_server *server, - be32_t address, - be32_t gateway, - uint8_t htype, - uint8_t hlen, - const uint8_t *chaddr) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; - int r; - - assert(server); - assert(address != INADDR_ANY); - assert(chaddr); - - packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - htype, hlen, chaddr, DHCP_FORCERENEW, - DHCP_MIN_OPTIONS_SIZE, &optoffset); - if (r < 0) - return r; - - r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); -} - -static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { - DHCPRequest *req = ASSERT_PTR(userdata); - int r; - - switch (code) { - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - if (len == 4) - req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true); - - break; - case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: - if (len == 4) - memcpy(&req->requested_ip, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - if (len == 4) - memcpy(&req->server_id, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: - if (client_id_size_is_valid(len)) - (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); - - break; - case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: - - if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) - req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); - - break; - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: - req->agent_info_option = (uint8_t*)option - 2; - - break; - case SD_DHCP_OPTION_HOST_NAME: { - _cleanup_free_ char *p = NULL; - - r = dhcp_option_parse_hostname(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - else - free_and_replace(req->hostname, p); - break; - } - case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: - req->parameter_request_list = option; - req->parameter_request_list_len = len; - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - req->rapid_commit = true; - break; - } - - return 0; -} - -static DHCPRequest* dhcp_request_free(DHCPRequest *req) { - if (!req) - return NULL; - - free(req->hostname); - return mfree(req); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); - -static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { - int r; - - assert(req); - assert(message); - - req->message = message; - - if (message->hlen > sizeof(message->chaddr)) - return -EBADMSG; - - /* set client id based on MAC address if client did not send an explicit one */ - if (!sd_dhcp_client_id_is_set(&req->client_id)) { - if (!client_id_data_size_is_valid(message->hlen)) - return -EBADMSG; - - r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen); - if (r < 0) - return r; - } - - if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { - uint8_t type; - const void *data; - size_t size; - - /* See RFC2131 section 4.1.1. - * hlen and chaddr may not be set for non-ethernet interface. - * Let's try to retrieve it from the client ID. */ - - if (!sd_dhcp_client_id_is_set(&req->client_id)) - return -EBADMSG; - - r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); - if (r < 0) - return r; - - if (type != 1) - return -EBADMSG; - - if (size > sizeof(message->chaddr)) - return -EBADMSG; - - memcpy(message->chaddr, data, size); - message->hlen = size; - } - - if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) - req->max_optlen = DHCP_MIN_OPTIONS_SIZE; - - if (req->lifetime <= 0) - req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time); - - if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) - req->lifetime = server->max_lease_time; - - return 0; -} - -static void request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) { - assert(req); - - if (timestamp && triple_timestamp_is_set(timestamp)) - req->timestamp = *timestamp; - else - triple_timestamp_now(&req->timestamp); -} - -static int request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) { - assert(req); - assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock)); - assert(clock_supported(clock)); - assert(ret); - - if (req->lifetime <= 0) - return -ENODATA; - - if (!triple_timestamp_is_set(&req->timestamp)) - return -ENODATA; - - *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime); - return 0; -} - -static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { +bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address) { assert(server); if (server->pool_size == 0) @@ -981,34 +299,7 @@ static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { return true; } -static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { - usec_t expiration; - int r; - - assert(server); - assert(req); - assert(address != 0); - - r = request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); - if (r < 0) - return r; - - r = dhcp_server_set_lease(server, address, req, expiration); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); - - r = server_send_offer_or_ack(server, req, address, DHCP_ACK); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); - - log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); - - server_on_lease_change(server); - - return DHCP_ACK; -} - -static bool address_available(sd_dhcp_server *server, be32_t address) { +bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address) { assert(server); if (hashmap_contains(server->bound_leases_by_address, UINT32_TO_PTR(address)) || @@ -1019,262 +310,6 @@ static bool address_available(sd_dhcp_server *server, be32_t address) { return true; } -#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30) - -int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) { - _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; - _cleanup_free_ char *error_message = NULL; - sd_dhcp_server_lease *existing_lease, *static_lease; - int type, r; - - assert(server); - assert(message); - - if (message->op != BOOTREQUEST) - return 0; - - req = new0(DHCPRequest, 1); - if (!req) - return -ENOMEM; - - type = dhcp_option_parse(message, length, parse_request, req, &error_message); - if (type < 0) - return type; - - r = ensure_sane_request(server, req, message); - if (r < 0) - return r; - - request_set_timestamp(req, timestamp); - - r = dhcp_server_cleanup_expired_leases(server); - if (r < 0) - return r; - - existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); - static_lease = dhcp_server_get_static_lease(server, req); - - switch (type) { - - case DHCP_DISCOVER: { - be32_t address = INADDR_ANY; - - log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); - - if (server->pool_size == 0) - /* no pool allocated */ - return 0; - - /* for now pick a random free address from the pool */ - if (static_lease) { - sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); - if (l && l != existing_lease) - /* The address is already assigned to another host. Refusing. */ - return 0; - - /* Found a matching static lease. */ - address = static_lease->address; - - } else if (existing_lease && address_is_in_pool(server, existing_lease->address)) - - /* If we previously assigned an address to the host, then reuse it. */ - address = existing_lease->address; - - else { - struct siphash state; - uint64_t hash; - - /* even with no persistence of leases, we try to offer the same client - the same IP address. we do this by using the hash of the client id - as the offset into the pool of leases when finding the next free one */ - - siphash24_init(&state, HASH_KEY.bytes); - client_id_hash_func(&req->client_id, &state); - hash = htole64(siphash24_finalize(&state)); - - for (unsigned i = 0; i < server->pool_size; i++) { - be32_t tmp_address; - - tmp_address = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size); - if (address_available(server, tmp_address)) { - address = tmp_address; - break; - } - } - } - - if (address == INADDR_ANY) - /* no free addresses left */ - return 0; - - if (server->rapid_commit && req->rapid_commit) - return server_ack_request(server, req, address); - - r = server_send_offer_or_ack(server, req, address, DHCP_OFFER); - if (r < 0) - /* this only fails on critical errors */ - return log_dhcp_server_errno(server, r, "Could not send offer: %m"); - - log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid)); - return DHCP_OFFER; - } - case DHCP_DECLINE: - log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); - - /* TODO: make sure we don't offer this address again */ - - return 1; - - case DHCP_REQUEST: { - be32_t address; - bool init_reboot = false; - - /* see RFC 2131, section 4.3.2 */ - - if (req->server_id != 0) { - log_dhcp_server(server, "REQUEST (selecting) (0x%x)", - be32toh(req->message->xid)); - - /* SELECTING */ - if (req->server_id != server->address) - /* client did not pick us */ - return 0; - - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; - - if (req->requested_ip == 0) - /* this must be filled in with the yiaddr - from the chosen OFFER */ - return 0; - - address = req->requested_ip; - } else if (req->requested_ip != 0) { - log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", - be32toh(req->message->xid)); - - /* INIT-REBOOT */ - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; - - /* TODO: check more carefully if IP is correct */ - address = req->requested_ip; - init_reboot = true; - } else { - log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", - be32toh(req->message->xid)); - - /* REBINDING / RENEWING */ - if (req->message->ciaddr == 0) - /* this MUST be filled in with clients IP address */ - return 0; - - address = req->message->ciaddr; - } - - /* Silently ignore Rapid Commit option in REQUEST message. */ - req->rapid_commit = false; - - if (static_lease) { - if (static_lease->address != address) - /* The client requested an address which is different from the static lease. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); - - sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); - if (l && l != existing_lease) - /* The requested address is already assigned to another host. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); - - /* Found a static lease for the client ID. */ - return server_ack_request(server, req, address); - } - - if (address_is_in_pool(server, address)) - /* The requested address is in the pool. */ - return server_ack_request(server, req, address); - - /* Refuse otherwise. */ - return server_send_nak_or_ignore(server, init_reboot, req); - } - - case DHCP_RELEASE: { - log_dhcp_server(server, "RELEASE (0x%x)", - be32toh(req->message->xid)); - - if (!existing_lease) - return 0; - - if (existing_lease->address != req->message->ciaddr) - return 0; - - sd_dhcp_server_lease_unref(existing_lease); - - server_on_lease_change(server); - - return 0; - }} - - return 0; -} - -static int server_receive_message(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { - _cleanup_free_ DHCPMessage *message = NULL; - /* This needs to be initialized with zero. See #20741. - * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ - CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + - CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - sd_dhcp_server *server = ASSERT_PTR(userdata); - struct iovec iov = {}; - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - ssize_t datagram_size, len; - int r; - - datagram_size = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size)) - return 0; - if (datagram_size < 0) { - log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } - - size_t buflen = datagram_size; - message = malloc0(buflen); - if (!message) - return -ENOMEM; - - iov = IOVEC_MAKE(message, datagram_size); - - len = recvmsg_safe(fd, &msg, 0); - if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) - return 0; - if (len < 0) { - log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m"); - return 0; - } - - if ((size_t) len < sizeof(DHCPMessage)) - return 0; - - /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */ - struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo); - if (info && info->ipi_ifindex != server->ifindex) - return 0; - - r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); - - return 0; -} - static void dhcp_server_update_lease_servers(sd_dhcp_server *server) { assert(server); assert(server->address != 0); @@ -1303,55 +338,16 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { dhcp_server_update_lease_servers(server); - r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (r < 0) { - r = -errno; - goto on_error; - } - server->fd_raw = r; - - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); + r = dhcp_server_setup_io_event_source(server); if (r < 0) - goto on_error; - server->fd = r; - - r = sd_event_add_io(server->event, &server->receive_message, - server->fd, EPOLLIN, - server_receive_message, server); - if (r < 0) - goto on_error; - - r = sd_event_source_set_priority(server->receive_message, - server->event_priority); - if (r < 0) - goto on_error; + return r; r = dhcp_server_load_leases(server); if (r < 0) log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); log_dhcp_server(server, "STARTED"); - return 0; - -on_error: - sd_dhcp_server_stop(server); - return r; -} - -int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { - sd_dhcp_server_lease *lease; - int r = 0; - - assert_return(server, -EINVAL); - - log_dhcp_server(server, "FORCERENEW"); - - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) - RET_GATHER(r, - server_send_forcerenew(server, lease->address, lease->gateway, - lease->htype, lease->hlen, lease->chaddr)); - return r; } int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index bbfb4f1c05d0a..86face3406c71 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -9,6 +9,7 @@ #include "sd-event.h" #include "dhcp-server-internal.h" +#include "dhcp-server-request.h" #include "tests.h" TEST(basic) { From 45a3a3b92c9d62613b23a1a18c2e60bf33b2abf4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 16:59:04 +0900 Subject: [PATCH 2128/2155] dhcp-server-request: modernize server_receive_message() --- src/libsystemd-network/dhcp-server-request.c | 40 +++++++++----------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 5cccc7c2f64af..555796d8a1e6d 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -445,38 +445,34 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz } static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ DHCPMessage *message = NULL; + sd_dhcp_server *server = ASSERT_PTR(userdata); + int r; + + ssize_t buflen = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + return 0; + if (buflen < 0) { + log_dhcp_server_errno(server, buflen, "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) + return -ENOMEM; + /* This needs to be initialized with zero. See #20741. * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - sd_dhcp_server *server = ASSERT_PTR(userdata); - struct iovec iov = {}; + struct iovec iov = IOVEC_MAKE(buf, buflen); struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), }; - ssize_t datagram_size, len; - int r; - - datagram_size = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size)) - return 0; - if (datagram_size < 0) { - log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } - - size_t buflen = datagram_size; - message = malloc0(buflen); - if (!message) - return -ENOMEM; - - iov = IOVEC_MAKE(message, datagram_size); - len = recvmsg_safe(fd, &msg, 0); + ssize_t len = recvmsg_safe(fd, &msg, 0); if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) return 0; if (len < 0) { @@ -492,7 +488,7 @@ static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, if (info && info->ipi_ifindex != server->ifindex) return 0; - r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + r = dhcp_server_handle_message(server, buf, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); if (r < 0) log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); From 27d99475b647022b498218715326c55bfac0c878 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 5 May 2026 06:57:33 +0900 Subject: [PATCH 2129/2155] dhcp-serve-request: move message size check to dhcp_server_handle_message() --- src/libsystemd-network/dhcp-server-request.c | 6 +++--- src/libsystemd-network/fuzz-dhcp-server.c | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 555796d8a1e6d..78d641141c33a 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -414,6 +414,9 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz assert(server); assert(message); + if (length < sizeof(DHCPMessage)) + return 0; + if (message->op != BOOTREQUEST) return 0; @@ -480,9 +483,6 @@ static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, return 0; } - if ((size_t) len < sizeof(DHCPMessage)) - return 0; - /* TODO: figure out if this can be done as a filter on the socket, like for IPv6 */ struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo); if (info && info->ipi_ifindex != server->ifindex) diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index 037acdec07996..9d927472e0030 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -63,9 +63,6 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) { int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - if (size < sizeof(DHCPMessage)) - return 0; - fuzz_setup_logging(); _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; From 0bb26684d042d92eaadd1ceb713c76ad6f8ba75b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 19:57:49 +0900 Subject: [PATCH 2130/2155] sd-dhcp-server: refactoring for socket fd handling This makes - UDP socket fd is owned by IO event source, - open RAW socket fd just before sending first packet, - set TOS and socket priority, - use AF_UNIX soxket pair in the unit test and fuzzer, so the unit test can now run by unprivileged user. --- src/libsystemd-network/dhcp-server-internal.h | 7 +- src/libsystemd-network/dhcp-server-request.c | 96 +++++++++++---- src/libsystemd-network/dhcp-server-send.c | 114 +++++++++++------- src/libsystemd-network/fuzz-dhcp-server.c | 6 +- src/libsystemd-network/sd-dhcp-server.c | 30 ++--- src/libsystemd-network/test-dhcp-server.c | 37 +++--- 6 files changed, 184 insertions(+), 106 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index c03ce7dddbbdd..76db80b77d5ae 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -29,9 +29,10 @@ typedef struct sd_dhcp_server { sd_event *event; int event_priority; - sd_event_source *receive_message; - int fd; - int fd_raw; + sd_event_source *io_event_source; + uint8_t ip_service_type; + int socket_fd; /* socket fd set externally, used by unit tests */ + int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */ int ifindex; char *ifname; diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 78d641141c33a..39c11e69c868e 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -3,13 +3,13 @@ #include "sd-event.h" #include "alloc-util.h" -#include "dhcp-network.h" #include "dhcp-protocol.h" #include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-request.h" #include "dhcp-server-send.h" #include "errno-util.h" +#include "fd-util.h" #include "hashmap.h" #include "iovec-util.h" #include "memory-util.h" @@ -495,38 +495,92 @@ static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, return 0; } +static int server_open_socket(sd_dhcp_server *server) { + int r; + + assert(server); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = socket_bind_to_ifindex(fd, server->ifindex); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type)); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, server->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(DHCP_PORT_SERVER), + .in.sin_addr.s_addr = htobe32(INADDR_ANY), + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + int dhcp_server_setup_io_event_source(sd_dhcp_server *server) { int r; assert(server); assert(server->event); - r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (r < 0) { - r = -errno; - goto on_error; + _cleanup_close_ int fd_close = -EBADF; + int fd; + if (server->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket + * even if we fail to set up the event source. */ + fd = server->socket_fd; + else { + fd = fd_close = server_open_socket(server); + if (fd < 0) + return fd; } - server->fd_raw = r; - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(server->event, &s, fd, EPOLLIN, server_receive_message, server); if (r < 0) - goto on_error; - server->fd = r; + return r; - r = sd_event_add_io(server->event, &server->receive_message, - server->fd, EPOLLIN, - server_receive_message, server); + r = sd_event_source_set_priority(s, server->event_priority); if (r < 0) - goto on_error; + return r; - r = sd_event_source_set_priority(server->receive_message, - server->event_priority); - if (r < 0) - goto on_error; + (void) sd_event_source_set_description(s, "dhcp-server-io"); - return 0; + if (fd_close >= 0) { + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + TAKE_FD(fd_close); + } - on_error: - sd_dhcp_server_stop(server); - return r; + sd_event_source_disable_unref(server->io_event_source); + server->io_event_source = TAKE_PTR(s); + return 0; } diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 67fc390bca3b7..11d553404c55e 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -1,19 +1,47 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-event.h" + #include "alloc-util.h" -#include "dhcp-network.h" #include "dhcp-option.h" #include "dhcp-packet.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-send.h" #include "dns-domain.h" #include "errno-util.h" +#include "fd-util.h" #include "hashmap.h" #include "in-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "socket-util.h" +static int server_acquire_raw_socket(sd_dhcp_server *server) { + int r; + + assert(server); + + if (server->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket. */ + return server->socket_fd; + + if (server->raw_socket_fd >= 0) + /* Already opened. */ + return server->raw_socket_fd; + + /* This is a send-only socket, hence it is opened with protocol=0, and do not call bind(). + * The interface binding will be done on send. */ + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type)); + if (r < 0) + return r; + + return server->raw_socket_fd = TAKE_FD(fd); +} + static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, uint8_t hlen, @@ -21,12 +49,6 @@ static int dhcp_server_send_unicast_raw( DHCPPacket *packet, size_t len) { - union sockaddr_union link = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_IP), - .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hlen, - }; int r; assert(server); @@ -37,11 +59,13 @@ static int dhcp_server_send_unicast_raw( assert(packet); assert(len > sizeof(DHCPPacket)); - memcpy(link.ll.sll_addr, chaddr, hlen); - if (len > UINT16_MAX) return -EOVERFLOW; + int fd = server_acquire_raw_socket(server); + if (fd < 0) + return fd; + r = dhcp_packet_append_ip_headers( packet, server->address, @@ -49,63 +73,69 @@ static int dhcp_server_send_unicast_raw( packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len, - /* ip_service_type= */ -1); + server->ip_service_type); if (r < 0) return r; - return dhcp_network_send_raw_socket( - server->fd_raw, - &link, - &(struct iovec_wrapper) { - .iovec = &IOVEC_MAKE(packet, len), - .count = 1, - }); + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = server->ifindex, + .ll.sll_halen = hlen, + }; + + memcpy(sa.ll.sll_addr, chaddr, hlen); + + struct msghdr mh = { + .msg_name = &sa.sa, + .msg_namelen = sockaddr_ll_len(&sa.ll), + .msg_iov = &IOVEC_MAKE(packet, len), + .msg_iovlen = 1, + }; + + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; } static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, uint16_t destination_port, DHCPMessage *message, size_t len) { - union sockaddr_union dest = { + + assert(server); + assert(message); + assert(len >= sizeof(DHCPMessage)); + + int fd = sd_event_source_get_io_fd(server->io_event_source); + if (fd < 0) + return fd; + + union sockaddr_union sa = { .in.sin_family = AF_INET, .in.sin_port = htobe16(destination_port), .in.sin_addr.s_addr = destination, }; - struct iovec iov = { - .iov_base = message, - .iov_len = len, - }; CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; struct msghdr msg = { - .msg_name = &dest, - .msg_namelen = sizeof(dest.in), - .msg_iov = &iov, + .msg_name = &sa, + .msg_namelen = sizeof(sa.in), + .msg_iov = &IOVEC_MAKE(message, len), .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), }; - struct cmsghdr *cmsg; - struct in_pktinfo *pktinfo; - - assert(server); - assert(server->fd >= 0); - assert(message); - assert(len >= sizeof(DHCPMessage)); - - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); - - cmsg = CMSG_FIRSTHDR(&msg); - assert(cmsg); + struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); - assert(pktinfo); - + struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); pktinfo->ipi_ifindex = server->ifindex; pktinfo->ipi_spec_dst.s_addr = server->address; - if (sendmsg(server->fd, &msg, 0) < 0) + if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) return -errno; return 0; diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index 9d927472e0030..ac006bc2aa54f 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -68,13 +68,16 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; _cleanup_close_ int dir_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + _cleanup_(sd_event_unrefp) sd_event *event = NULL; ASSERT_OK(sd_event_new(&event)); _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); - server->fd = ASSERT_OK_ERRNO(open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY)); + server->socket_fd = TAKE_FD(socket_fd[0]); ASSERT_OK(sd_dhcp_server_set_lease_file(server, dir_fd, "leases")); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0)); @@ -87,6 +90,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ASSERT_OK(add_static_lease(server, 4)); _cleanup_free_ uint8_t *duped = ASSERT_NOT_NULL(memdup(data, size)); + ASSERT_OK(sd_dhcp_server_start(server)); (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL); ASSERT_OK(dhcp_server_save_leases(server)); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 5267137540d21..772bba4838213 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -3,6 +3,8 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ +#include + #include "sd-dhcp-server.h" #include "sd-event.h" @@ -99,10 +101,7 @@ int sd_dhcp_server_configure_pool( } int sd_dhcp_server_is_running(sd_dhcp_server *server) { - if (!server) - return false; - - return !!server->receive_message; + return server && sd_event_source_get_enabled(server->io_event_source, /* ret= */ NULL) > 0; } static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { @@ -111,6 +110,8 @@ static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { sd_dhcp_server_stop(server); sd_event_unref(server->event); + safe_close(server->socket_fd); + safe_close(server->raw_socket_fd); free(server->boot_server_name); free(server->boot_filename); @@ -149,8 +150,9 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { *server = (sd_dhcp_server) { .n_ref = 1, - .fd_raw = -EBADF, - .fd = -EBADF, + .ip_service_type = IPTOS_CLASS_CS6, + .socket_fd = -EBADF, + .raw_socket_fd = -EBADF, .address = htobe32(INADDR_ANY), .netmask = htobe32(INADDR_ANY), .ifindex = ifindex, @@ -262,17 +264,13 @@ int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filenam } int sd_dhcp_server_stop(sd_dhcp_server *server) { - bool running; - if (!server) return 0; - running = sd_dhcp_server_is_running(server); - - server->receive_message = sd_event_source_disable_unref(server->receive_message); + bool running = sd_dhcp_server_is_running(server); - server->fd_raw = safe_close(server->fd_raw); - server->fd = safe_close(server->fd); + server->raw_socket_fd = safe_close(server->raw_socket_fd); + server->io_event_source = sd_event_source_disable_unref(server->io_event_source); if (running) log_dhcp_server(server, "STOPPED"); @@ -327,15 +325,11 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { assert_return(server, -EINVAL); assert_return(server->event, -EINVAL); + assert_return(server->address != INADDR_ANY, -EUNATCH); if (sd_dhcp_server_is_running(server)) return 0; - assert_return(!server->receive_message, -EBUSY); - assert_return(server->fd_raw < 0, -EBUSY); - assert_return(server->fd < 0, -EBUSY); - assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH); - dhcp_server_update_lease_servers(server); r = dhcp_server_setup_io_event_source(server); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 86face3406c71..bc9ab1e9ac2d8 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -10,6 +10,7 @@ #include "dhcp-server-internal.h" #include "dhcp-server-request.h" +#include "fd-util.h" #include "tests.h" TEST(basic) { @@ -19,14 +20,12 @@ TEST(basic) { struct in_addr address_any = { .s_addr = htobe32(INADDR_ANY), }; - int r; _cleanup_(sd_event_unrefp) sd_event *event = NULL; ASSERT_OK(sd_event_new(&event)); - /* attach to loopback interface */ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_NOT_NULL(server); ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); @@ -49,13 +48,12 @@ TEST(basic) { ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_any, 8, 0, 1), EINVAL)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 1)); - 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 (IN_SET(r, -EPERM, -EAFNOSUPPORT)) - return (void) log_tests_skipped_errno(r, "cannot start dhcp server"); - ASSERT_OK(r); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + server->socket_fd = TAKE_FD(socket_fd[0]); + + ASSERT_OK(sd_dhcp_server_start(server)); ASSERT_OK(sd_dhcp_server_start(server)); ASSERT_OK(sd_dhcp_server_stop(server)); ASSERT_OK(sd_dhcp_server_stop(server)); @@ -112,13 +110,15 @@ TEST(dhcp_server_handle_message) { .s_addr = htobe32(INADDR_LOOPBACK + 42), }; static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' }; - int r; + + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); _cleanup_(sd_event_unrefp) sd_event *event = NULL; ASSERT_OK(sd_event_new(&event)); _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); ASSERT_OK(sd_dhcp_server_set_static_lease( server, @@ -127,15 +127,10 @@ TEST(dhcp_server_handle_message) { ELEMENTSOF(static_lease_client_id), /* hostname= */ NULL)); ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); - r = sd_dhcp_server_start(server); - if (IN_SET(r, -EPERM, -EAFNOSUPPORT)) - return (void) log_tests_skipped_errno(r, "cannot start dhcp server"); - ASSERT_OK(r); + server->socket_fd = TAKE_FD(socket_fd[0]); + ASSERT_OK(sd_dhcp_server_start(server)); - r = dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL); - if (r == -ENETDOWN) - return (void) log_tests_skipped("Network is not available"); - ASSERT_OK_EQ(r, DHCP_OFFER); + ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); test.end = 0; /* TODO, shouldn't this fail? */ @@ -273,7 +268,7 @@ TEST(dhcp_server_handle_message) { TEST(sd_dhcp_server_set_static_lease) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_OK(sd_dhcp_server_set_static_lease( server, @@ -351,7 +346,7 @@ TEST(sd_dhcp_server_set_static_lease) { TEST(sd_dhcp_server_set_domain_name) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); /* Test setting domain name */ ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "example.com")); From 29b662184be0debbd54f097423a8e39dca1c9a37 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 7 May 2026 12:41:38 +0900 Subject: [PATCH 2131/2155] sd-dhcp-server: make IP service type (TOS) configurable --- src/libsystemd-network/sd-dhcp-server.c | 8 ++++++++ src/systemd/sd-dhcp-server.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 772bba4838213..274f0af988e4a 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -225,6 +225,14 @@ sd_event* sd_dhcp_server_get_event(sd_dhcp_server *server) { return server->event; } +int sd_dhcp_server_set_ip_service_type(sd_dhcp_server *server, uint8_t type) { + assert_return(server, -EINVAL); + assert_return(!sd_dhcp_server_is_running(server), -EBUSY); + + server->ip_service_type = type; + return 0; +} + int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address) { assert_return(server, -EINVAL); diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 8c3dbab9b4238..15741f96b6ef3 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -53,6 +53,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server); int sd_dhcp_server_stop(sd_dhcp_server *server); int sd_dhcp_server_configure_pool(sd_dhcp_server *server, const struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); +int sd_dhcp_server_set_ip_service_type(sd_dhcp_server *server, uint8_t type); int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address); int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name); From dae51cc08e2a273481f8563ea56a52083cee53ae Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 22:08:40 +0900 Subject: [PATCH 2132/2155] dhcp-server-send: rework sending DHCP reply message This should mostly not change anything, except for some corner cases. Just refactoring. --- src/libsystemd-network/dhcp-server-send.c | 130 +++++++++++----------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 11d553404c55e..a7b827f56e636 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -141,42 +141,79 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, return 0; } -static bool requested_broadcast(DHCPMessage *message) { - assert(message); - return message->flags & htobe16(0x8000); -} - -static int dhcp_server_send( +static int dhcp_server_send_message( sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, - be32_t destination, - uint16_t destination_port, + DHCPRequest *req, + uint8_t type, DHCPPacket *packet, - size_t optoffset, - bool l2_broadcast) { - - if (destination != INADDR_ANY) - return dhcp_server_send_udp(server, destination, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else if (l2_broadcast) - return dhcp_server_send_udp(server, INADDR_BROADCAST, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else - /* we cannot send UDP packet to specific MAC address when the - address is not yet configured, so must fall back to raw - packets */ - return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet, - sizeof(DHCPPacket) + optoffset); + size_t optoffset) { + + assert(server); + assert(req); + assert(req->message); + assert(packet); + + /* RFC 2131 Section 4.1 */ + + /* If the ’giaddr’ field in a DHCP message from a client is non-zero, the server sends any + * return messages to the ’DHCP server’ port on the BOOTP relay agent whose address appears + * in ’giaddr’. */ + if (req->message->giaddr != INADDR_ANY) + return dhcp_server_send_udp( + server, + req->message->giaddr, + DHCP_PORT_SERVER, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + + /* when ’giaddr’ is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */ + if (type == DHCP_NAK) + return dhcp_server_send_udp( + server, + INADDR_BROADCAST, + DHCP_PORT_CLIENT, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + + /* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts + * DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */ + if (req->message->ciaddr != INADDR_ANY) + return dhcp_server_send_udp( + server, + req->message->ciaddr, + DHCP_PORT_CLIENT, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + + /* If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is set, then the server + * broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff. + * + * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. + * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast + * the message if 'yiaddr' is zero.) */ + if (FLAGS_SET(be16toh(req->message->flags), 0x8000) || + req->message->hlen == 0 || memeqzero(req->message->chaddr, req->message->hlen) || + packet->dhcp.yiaddr == INADDR_ANY) + return dhcp_server_send_udp( + server, + INADDR_BROADCAST, + DHCP_PORT_CLIENT, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + + /* If the broadcast bit is not set and ’giaddr’ is zero and ’ciaddr’ is zero, then the server + * unicasts DHCPOFFER and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ + return dhcp_server_send_unicast_raw( + server, + req->message->hlen, + req->message->chaddr, + packet, + sizeof(DHCPPacket) + optoffset); } static int dhcp_server_send_packet(sd_dhcp_server *server, DHCPRequest *req, DHCPPacket *packet, int type, size_t optoffset) { - be32_t destination = INADDR_ANY; - uint16_t destination_port = DHCP_PORT_CLIENT; int r; assert(server); @@ -206,40 +243,7 @@ static int dhcp_server_send_packet(sd_dhcp_server *server, if (r < 0) return r; - /* RFC 2131 Section 4.1 - - If the ’giaddr’ field in a DHCP message from a client is non-zero, - the server sends any return messages to the ’DHCP server’ port on the - BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ - field is zero and the ’ciaddr’ field is nonzero, then the server - unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. - If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is - set, then the server broadcasts DHCPOFFER and DHCPACK messages to - 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and - ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK - messages to the client’s hardware address and ’yiaddr’ address. In - all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK - messages to 0xffffffff. - - Section 4.3.2 - - If ’giaddr’ is set in the DHCPREQUEST message, the client is on a - different subnet. The server MUST set the broadcast bit in the - DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the - client, because the client may not have a correct network address - or subnet mask, and the client may not be answering ARP requests. - */ - if (req->message->giaddr != 0) { - destination = req->message->giaddr; - destination_port = DHCP_PORT_SERVER; - if (type == DHCP_NAK) - packet->dhcp.flags = htobe16(0x8000); - } else if (req->message->ciaddr != 0 && type != DHCP_NAK) - destination = req->message->ciaddr; - - bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK; - return dhcp_server_send(server, req->message->hlen, req->message->chaddr, - destination, destination_port, packet, optoffset, l2_broadcast); + return dhcp_server_send_message(server, req, type, packet, optoffset); } static int server_message_init( From 252f3855fe8e571f76954d106fb6b5a2d8d84fb0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 5 May 2026 04:55:03 +0900 Subject: [PATCH 2133/2155] sd-dhcp-server: use struct hw_addr_data to manage client hardware address parsed from DHCP message Then, this drops garbage in DHCP server lease in DBus and Varlink message. This also drops fallback to use client ID as hardware address when chaddr field is not set. In that case, we should broadcast reply. --- .../dhcp-server-lease-internal.h | 4 +- src/libsystemd-network/dhcp-server-request.c | 57 +++----- src/libsystemd-network/dhcp-server-request.h | 4 + src/libsystemd-network/dhcp-server-send.c | 36 ++--- src/libsystemd-network/fuzz-dhcp-server.c | 4 +- src/libsystemd-network/sd-dhcp-server-lease.c | 128 +++++++++--------- src/libsystemd-network/test-dhcp-server.c | 3 +- src/network/networkd-dhcp-server-bus.c | 2 +- 8 files changed, 109 insertions(+), 129 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index 6d7eb4c21368a..024279ccbd3fa 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -6,6 +6,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-server-internal.h" #include "dhcp-server-request.h" +#include "ether-addr-util.h" #include "sd-forward.h" typedef struct sd_dhcp_server_lease { @@ -16,10 +17,9 @@ typedef struct sd_dhcp_server_lease { sd_dhcp_client_id client_id; uint8_t htype; /* e.g. ARPHRD_ETHER */ - uint8_t hlen; /* e.g. ETH_ALEN */ + struct hw_addr_data hw_addr; be32_t address; be32_t gateway; - uint8_t chaddr[16]; usec_t expiration; char *hostname; } sd_dhcp_server_lease; diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 39c11e69c868e..768677e65e52a 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -54,8 +54,6 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_ } static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { - int r; - assert(req); assert(message); @@ -64,41 +62,26 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes if (message->hlen > sizeof(message->chaddr)) return -EBADMSG; - /* set client id based on MAC address if client did not send an explicit one */ - if (!sd_dhcp_client_id_is_set(&req->client_id)) { - if (!client_id_data_size_is_valid(message->hlen)) - return -EBADMSG; - - r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen); - if (r < 0) - return r; - } - - if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { - uint8_t type; - const void *data; - size_t size; - - /* See RFC2131 section 4.1.1. - * hlen and chaddr may not be set for non-ethernet interface. - * Let's try to retrieve it from the client ID. */ - - if (!sd_dhcp_client_id_is_set(&req->client_id)) - return -EBADMSG; - - r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); - if (r < 0) - return r; - - if (type != 1) - return -EBADMSG; - - if (size > sizeof(message->chaddr)) - return -EBADMSG; - - memcpy(message->chaddr, data, size); - message->hlen = size; - } + req->hw_addr.length = req->message->hlen; + memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen); + + /* Fake client ID generated from the DHCP header. + * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255. + * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */ + if (!IN_SET(req->message->htype, 0, UINT8_MAX)) + (void) sd_dhcp_client_id_set( + &req->client_id_by_header, + req->message->htype, + req->message->chaddr, + req->message->hlen); + + /* If Client Identifier option is unspecified, use the generated one. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + req->client_id = req->client_id_by_header; + + /* We manage bound leases by client ID. Hence, at least one of them is necessary. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + return -EBADMSG; if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) req->max_optlen = DHCP_MIN_OPTIONS_SIZE; diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h index f1e827a0e13d0..c9ffe2aba8ee8 100644 --- a/src/libsystemd-network/dhcp-server-request.h +++ b/src/libsystemd-network/dhcp-server-request.h @@ -3,6 +3,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-protocol.h" +#include "ether-addr-util.h" #include "sd-forward.h" #include "sparse-endian.h" #include "time-util.h" @@ -10,10 +11,13 @@ typedef struct DHCPRequest { /* received message */ DHCPMessage *message; + /* sender hardware address, may not be set for non-ethernet interface */ + struct hw_addr_data hw_addr; triple_timestamp timestamp; /* options */ sd_dhcp_client_id client_id; + sd_dhcp_client_id client_id_by_header; size_t max_optlen; be32_t server_id; be32_t requested_ip; diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index a7b827f56e636..3fd548392da9e 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -44,8 +44,7 @@ static int server_acquire_raw_socket(sd_dhcp_server *server) { static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, + const struct hw_addr_data *hw_addr, DHCPPacket *packet, size_t len) { @@ -54,8 +53,7 @@ static int dhcp_server_send_unicast_raw( assert(server); assert(server->ifindex > 0); assert(server->address != 0); - assert(hlen > 0); - assert(chaddr); + assert(hw_addr); assert(packet); assert(len > sizeof(DHCPPacket)); @@ -81,10 +79,10 @@ static int dhcp_server_send_unicast_raw( .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETH_P_IP), .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hlen, + .ll.sll_halen = hw_addr->length, }; - memcpy(sa.ll.sll_addr, chaddr, hlen); + memcpy_safe(sa.ll.sll_addr, hw_addr->bytes, hw_addr->length); struct msghdr mh = { .msg_name = &sa.sa, @@ -192,7 +190,7 @@ static int dhcp_server_send_message( * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast * the message if 'yiaddr' is zero.) */ if (FLAGS_SET(be16toh(req->message->flags), 0x8000) || - req->message->hlen == 0 || memeqzero(req->message->chaddr, req->message->hlen) || + hw_addr_is_null(&req->hw_addr) || packet->dhcp.yiaddr == INADDR_ANY) return dhcp_server_send_udp( server, @@ -205,8 +203,7 @@ static int dhcp_server_send_message( * unicasts DHCPOFFER and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ return dhcp_server_send_unicast_raw( server, - req->message->hlen, - req->message->chaddr, + &req->hw_addr, packet, sizeof(DHCPPacket) + optoffset); } @@ -269,7 +266,7 @@ static int server_message_init( r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid), - req->message->htype, req->message->hlen, req->message->chaddr, + req->message->htype, req->hw_addr.length, req->hw_addr.bytes, type, req->max_optlen, &optoffset); if (r < 0) return r; @@ -534,28 +531,23 @@ int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequ return DHCP_NAK; } -static int server_send_forcerenew( +static int dhcp_server_send_forcerenew( sd_dhcp_server *server, - be32_t address, - be32_t gateway, - uint8_t htype, - uint8_t hlen, - const uint8_t *chaddr) { + sd_dhcp_server_lease *lease) { _cleanup_free_ DHCPPacket *packet = NULL; size_t optoffset = 0; int r; assert(server); - assert(address != INADDR_ANY); - assert(chaddr); + assert(lease); packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); if (!packet) return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - htype, hlen, chaddr, DHCP_FORCERENEW, + lease->htype, lease->hw_addr.length, lease->hw_addr.bytes, DHCP_FORCERENEW, DHCP_MIN_OPTIONS_SIZE, &optoffset); if (r < 0) return r; @@ -565,7 +557,7 @@ static int server_send_forcerenew( if (r < 0) return r; - return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, + return dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, &packet->dhcp, sizeof(DHCPMessage) + optoffset); } @@ -579,8 +571,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { log_dhcp_server(server, "FORCERENEW"); HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) - RET_GATHER(r, - server_send_forcerenew(server, lease->address, lease->gateway, - lease->htype, lease->hlen, lease->chaddr)); + RET_GATHER(r, dhcp_server_send_forcerenew(server, lease)); return r; } diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index ac006bc2aa54f..34c1f8371a9fd 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -27,10 +27,10 @@ static int add_lease(sd_dhcp_server *server, const struct in_addr *server_addres *lease = (sd_dhcp_server_lease) { .n_ref = 1, .address = htobe32(UINT32_C(10) << 24 | i), - .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + .hw_addr.length = ETH_ALEN, + .hw_addr.bytes = { 3, 3, 3, 3, 3, 3, }, .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY), .gateway = server_address->s_addr, - .hlen = ETH_ALEN, .htype = ARPHRD_ETHER, .client_id.size = 2, diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index a7d9947c294c0..58b5831e44119 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -16,7 +16,6 @@ #include "iovec-util.h" #include "json-util.h" #include "mkdir.h" -#include "string-util.h" #include "tmpfile-util.h" static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) { @@ -103,12 +102,11 @@ int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *r .address = address, .client_id = req->client_id, .htype = req->message->htype, - .hlen = req->message->hlen, .gateway = req->message->giaddr, .expiration = expiration, }; - memcpy(lease->chaddr, req->message->chaddr, req->message->hlen); + lease->hw_addr = req->hw_addr; if (req->hostname) { lease->hostname = strdup(req->hostname); @@ -146,27 +144,17 @@ int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) { sd_dhcp_server_lease *static_lease; - sd_dhcp_client_id client_id; assert(server); assert(req); static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id); - if (static_lease) - goto verify; - - /* when no lease is found based on the client id fall back to chaddr */ - if (!client_id_data_size_is_valid(req->message->hlen)) - return NULL; - - if (sd_dhcp_client_id_set(&client_id, /* type= */ 1, req->message->chaddr, req->message->hlen) < 0) - return NULL; - - static_lease = hashmap_get(server->static_leases_by_client_id, &client_id); + if (!static_lease && sd_dhcp_client_id_is_set(&req->client_id_by_header)) + /* when no lease is found, fall back to use the fake client ID generated from the header. */ + static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id_by_header); if (!static_lease) return NULL; -verify: /* Check if the address is in the same subnet. */ if ((static_lease->address & server->netmask) != server->subnet) return NULL; @@ -247,8 +235,8 @@ static int dhcp_server_lease_build_json(sd_dhcp_server_lease *lease, sd_json_var JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }), JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressType", lease->htype), - SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hlen), - SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->chaddr, sizeof(lease->chaddr))); + SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hw_addr.length), + SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->hw_addr.bytes, lease->hw_addr.length)); } int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant **v) { @@ -376,70 +364,67 @@ int dhcp_server_save_leases(sd_dhcp_server *server) { return 0; } -static int json_dispatch_chaddr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - uint8_t* address = ASSERT_PTR(userdata); - _cleanup_(iovec_done) struct iovec iov = {}; - int r; - - r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); - if (r < 0) - return r; +typedef struct LeaseParam { + sd_dhcp_client_id client_id; + uint8_t htype; + uint8_t hlen; + struct iovec hw_addr; + struct in_addr address; + usec_t exp_b; + usec_t exp_r; + char *hostname; +} LeaseParam; - if (iov.iov_len != 16) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); +static void lease_param_done(LeaseParam *p) { + assert(p); - memcpy(address, iov.iov_base, iov.iov_len); - return 0; + iovec_done(&p->hw_addr); + free(p->hostname); } static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, bool use_boottime) { - static const sd_json_dispatch_field dispatch_table_boottime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - {} - }, dispatch_table_realtime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, + static const sd_json_dispatch_field dispatch_table[] = { + { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(LeaseParam, client_id), SD_JSON_MANDATORY }, + { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, htype), 0 }, + { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, hlen), 0 }, + { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseParam, hw_addr), 0 }, + { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(LeaseParam, address), SD_JSON_MANDATORY }, + { "AddressString", SD_JSON_VARIANT_STRING, NULL, 0, 0 }, + { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_b), SD_JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_r), SD_JSON_MANDATORY }, + { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LeaseParam, hostname), 0 }, {} }; - _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; - usec_t now_b; int r; assert(server); assert(v); - lease = new(sd_dhcp_server_lease, 1); - if (!lease) - return -ENOMEM; - - *lease = (sd_dhcp_server_lease) { - .n_ref = 1, - }; - - r = sd_json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, SD_JSON_ALLOW_EXTENSIONS, lease); + _cleanup_(lease_param_done) LeaseParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) return r; + if (p.hlen > HW_ADDR_MAX_SIZE) + return -EINVAL; + + if (p.hlen > p.hw_addr.iov_len) + return -EINVAL; + + if (!in4_addr_is_set(&p.address)) + return -EINVAL; + + if (!sd_dhcp_client_id_is_set(&p.client_id)) + return -EINVAL; + + usec_t now_b; r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); if (r < 0) return r; if (use_boottime) { - if (lease->expiration < now_b) + if (p.exp_b < now_b) return 0; /* already expired */ } else { usec_t now_r; @@ -448,12 +433,29 @@ static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, if (r < 0) return r; - if (lease->expiration < now_r) + if (p.exp_r < now_r) return 0; /* already expired */ - lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b); + p.exp_b = map_clock_usec_raw(p.exp_r, now_r, now_b); } + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + + .client_id = p.client_id, + .htype = p.htype, + .hw_addr.length = p.hlen, + .address = p.address.s_addr, + .expiration = p.exp_b, + .hostname = TAKE_PTR(p.hostname), + }; + + memcpy_safe(lease->hw_addr.bytes, p.hw_addr.iov_base, p.hlen); + r = dhcp_server_put_lease(server, lease, /* is_static= */ false); if (r == -EEXIST) return 0; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index bc9ab1e9ac2d8..a4f025bd412b1 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -152,8 +152,9 @@ TEST(dhcp_server_handle_message) { test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + /* Neither client ID nor hardware type is set. There is no way to manage the bound lease for the request. */ test.header.htype = 0; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); test.header.htype = ARPHRD_ETHER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index 58e0a26f22fc1..d5829b6dfbb3f 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -55,7 +55,7 @@ static int property_get_leases( if (r < 0) return r; - r = sd_bus_message_append_array(reply, 'y', &lease->chaddr, sizeof(lease->chaddr)); + r = sd_bus_message_append_array(reply, 'y', &lease->hw_addr.bytes, lease->hw_addr.length); if (r < 0) return r; From e359c6df6589da33c0b7b0448355a95dcb70a8bd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 7 May 2026 11:59:23 +0900 Subject: [PATCH 2134/2155] sd-dhcp-server: store more information in DHCPRequest This makes DHCPRequest stores - the message type of the received message, - acquired address, - found static DHCP lease, This also moves call of dhcp_request_get_lifetime_timestamp() from dhcp_server_ack() to dhcp_server_set_lease(), and rename DHCPRequest.server_id -> .server_address. No functional change, just refactoring. --- .../dhcp-server-lease-internal.h | 2 +- src/libsystemd-network/dhcp-server-request.c | 53 +++++++++---------- src/libsystemd-network/dhcp-server-request.h | 9 +++- src/libsystemd-network/dhcp-server-send.c | 15 +++--- src/libsystemd-network/dhcp-server-send.h | 1 - src/libsystemd-network/sd-dhcp-server-lease.c | 16 +++--- 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index 024279ccbd3fa..f347837bd847e 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -28,7 +28,7 @@ extern const struct hash_ops dhcp_server_lease_hash_ops; int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static); -int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration); +int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req); int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server); sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req); diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 768677e65e52a..202a8b888cd05 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -112,7 +112,7 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us break; case SD_DHCP_OPTION_SERVER_IDENTIFIER: if (len == 4) - memcpy(&req->server_id, option, sizeof(be32_t)); + memcpy(&req->server_address, option, sizeof(be32_t)); break; case SD_DHCP_OPTION_CLIENT_IDENTIFIER: @@ -169,7 +169,7 @@ static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *messag r = dhcp_option_parse(message, length, parse_request, req, &error_message); if (r < 0) return r; - int type = r; + req->type = r; r = ensure_sane_request(server, req, message); if (r < 0) @@ -177,26 +177,21 @@ static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *messag *ret = TAKE_PTR(req); *ret_error_message = TAKE_PTR(error_message); - return type; + return 0; } -static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { - usec_t expiration; +static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) { int r; assert(server); assert(req); - assert(address != 0); - - r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); - if (r < 0) - return r; + assert(req->address != INADDR_ANY); - r = dhcp_server_set_lease(server, address, req, expiration); + r = dhcp_server_set_lease(server, req); if (r < 0) return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); - r = server_send_offer_or_ack(server, req, address, DHCP_ACK); + r = server_send_offer_or_ack(server, req, DHCP_ACK); if (r < 0) return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); @@ -223,8 +218,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req /* no pool allocated */ return 0; - be32_t address = INADDR_ANY; - /* for now pick a random free address from the pool */ if (static_lease) { sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); @@ -233,12 +226,13 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req return 0; /* Found a matching static lease. */ - address = static_lease->address; + req->static_lease = static_lease; + req->address = static_lease->address; } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address)) /* If we previously assigned an address to the host, then reuse it. */ - address = existing_lease->address; + req->address = existing_lease->address; else { struct siphash state; @@ -257,20 +251,20 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req for (unsigned i = 0; i < server->pool_size; i++) { be32_t a = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size); if (dhcp_server_address_available(server, a)) { - address = a; + req->address = a; break; } } } - if (address == INADDR_ANY) + if (req->address == INADDR_ANY) /* no free addresses left */ return 0; if (server->rapid_commit && req->rapid_commit) - return dhcp_server_ack(server, req, address); + return dhcp_server_ack(server, req); - r = server_send_offer_or_ack(server, req, address, DHCP_OFFER); + r = server_send_offer_or_ack(server, req, DHCP_OFFER); if (r < 0) /* this only fails on critical errors */ return log_dhcp_server_errno(server, r, "Could not send offer: %m"); @@ -292,12 +286,12 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) /* see RFC 2131, section 4.3.2 */ - if (req->server_id != 0) { + if (req->server_address != INADDR_ANY) { log_dhcp_server(server, "REQUEST (selecting) (0x%x)", be32toh(req->message->xid)); /* SELECTING */ - if (req->server_id != server->address) + if (req->server_address != server->address) /* client did not pick us */ return 0; @@ -348,13 +342,19 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) /* The requested address is already assigned to another host. Refusing. */ return server_send_nak_or_ignore(server, init_reboot, req); + req->static_lease = static_lease; + req->address = address; + /* Found a static lease for the client ID. */ - return dhcp_server_ack(server, req, address); + return dhcp_server_ack(server, req); } - if (dhcp_server_address_is_in_pool(server, address)) + if (dhcp_server_address_is_in_pool(server, address)) { /* The requested address is in the pool. */ - return dhcp_server_ack(server, req, address); + req->address = address; + + return dhcp_server_ack(server, req); + } /* Refuse otherwise. */ return server_send_nak_or_ignore(server, init_reboot, req); @@ -408,7 +408,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz r = dhcp_server_parse_message(server, message, length, &req, &error_message); if (r < 0) return r; - int type = r; dhcp_request_set_timestamp(req, timestamp); @@ -416,7 +415,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz if (r < 0) return r; - switch (type) { + switch (req->type) { case DHCP_DISCOVER: return dhcp_server_process_discover(server, req); case DHCP_REQUEST: diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h index c9ffe2aba8ee8..e5738978a2ddc 100644 --- a/src/libsystemd-network/dhcp-server-request.h +++ b/src/libsystemd-network/dhcp-server-request.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dhcp-server-lease.h" + #include "dhcp-client-id-internal.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" @@ -16,10 +18,11 @@ typedef struct DHCPRequest { triple_timestamp timestamp; /* options */ + uint8_t type; sd_dhcp_client_id client_id; sd_dhcp_client_id client_id_by_header; size_t max_optlen; - be32_t server_id; + be32_t server_address; be32_t requested_ip; usec_t lifetime; const uint8_t *agent_info_option; @@ -27,6 +30,10 @@ typedef struct DHCPRequest { const uint8_t *parameter_request_list; size_t parameter_request_list_len; bool rapid_commit; + + /* acquired data */ + sd_dhcp_server_lease *static_lease; + be32_t address; } DHCPRequest; int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret); diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 3fd548392da9e..9f52ff4cc8df9 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -286,7 +286,6 @@ static int dhcp_server_append_static_hostname( size_t *offset, DHCPRequest *req) { - sd_dhcp_server_lease *static_lease; int r; assert(server); @@ -294,11 +293,10 @@ static int dhcp_server_append_static_hostname( assert(offset); assert(req); - static_lease = dhcp_server_get_static_lease(server, req); - if (!static_lease || !static_lease->hostname) + if (!req->static_lease || !req->static_lease->hostname) return 0; - if (dns_name_is_single_label(static_lease->hostname)) + if (dns_name_is_single_label(req->static_lease->hostname)) /* Option 12 */ return dhcp_option_append( &packet->dhcp, @@ -306,8 +304,8 @@ static int dhcp_server_append_static_hostname( offset, /* overload= */ 0, SD_DHCP_OPTION_HOST_NAME, - strlen(static_lease->hostname), - static_lease->hostname); + strlen(req->static_lease->hostname), + req->static_lease->hostname); /* Option 81 */ @@ -322,7 +320,7 @@ static int dhcp_server_append_static_hostname( buffer[1] = 255; buffer[2] = 255; - r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); + r = dns_name_to_wire_format(req->static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); if (r < 0) return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); if (r > DHCP_MAX_FQDN_LENGTH) @@ -350,7 +348,6 @@ static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { int server_send_offer_or_ack( sd_dhcp_server *server, DHCPRequest *req, - be32_t address, uint8_t type) { static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { @@ -375,7 +372,7 @@ int server_send_offer_or_ack( if (r < 0) return r; - packet->dhcp.yiaddr = address; + packet->dhcp.yiaddr = req->address; packet->dhcp.siaddr = server->boot_server_address.s_addr; lease_time = usec_to_be32_sec(req->lifetime); diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h index 80fd9a6b94fe5..27801b64ec204 100644 --- a/src/libsystemd-network/dhcp-server-send.h +++ b/src/libsystemd-network/dhcp-server-send.h @@ -8,7 +8,6 @@ int server_send_offer_or_ack( sd_dhcp_server *server, DHCPRequest *req, - be32_t address, uint8_t type); int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req); diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 58b5831e44119..4ab789726407d 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -64,21 +64,25 @@ int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, b return 0; } -int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) { +int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; int r; assert(server); - assert(address != 0); assert(req); - assert(expiration != 0); + assert(req->address != INADDR_ANY); + + usec_t expiration; + r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); + if (r < 0) + return r; /* If a lease for the host already exists, update it. */ lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); if (lease) { - if (lease->address != address) { + if (lease->address != req->address) { hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease); - lease->address = address; + lease->address = req->address; r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); if (r < 0) @@ -99,7 +103,7 @@ int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *r *lease = (sd_dhcp_server_lease) { .n_ref = 1, - .address = address, + .address = req->address, .client_id = req->client_id, .htype = req->message->htype, .gateway = req->message->giaddr, From d40d021ca5e2b178ac542c70052b6621e7baec97 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 7 May 2026 12:19:25 +0900 Subject: [PATCH 2135/2155] dhcp-server-request: check server address in DHCPDECLINE and DHCPRELEASE Otherwise, we may do something wrong by messages for another DHCP server. Let's silently ignore messages with unmatching server identifier. Also, logs something when we receive DHCPRELEASE but found lease does not match the reported address. --- src/libsystemd-network/dhcp-server-request.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 202a8b888cd05..6ce68dab40d30 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -364,10 +364,12 @@ static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, assert(server); assert(req); - log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); + if (req->server_address != server->address) + return 0; /* The message is not for us. Let's silently ignore the packet. */ - /* TODO: make sure we don't offer this address again */ + /* TODO: make sure we don't offer this address again for a while. */ + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); return 0; } @@ -375,19 +377,20 @@ static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) assert(server); assert(req); - log_dhcp_server(server, "RELEASE (0x%x)", - be32toh(req->message->xid)); + if (req->server_address != server->address) + return 0; /* The message is not for us. Let's silently ignore the packet. */ sd_dhcp_server_lease *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); if (!existing_lease) return 0; if (existing_lease->address != req->message->ciaddr) - return 0; + return -EBADMSG; sd_dhcp_server_lease_unref(existing_lease); dhcp_server_on_lease_change(server); + log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->xid)); return 0; } From c7655af547c27a3928a769789ff65b6760f11698 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 19:21:19 +0100 Subject: [PATCH 2136/2155] ci: ignore failures to chown journal in GHA jobs Otherwise when the build fails, this fails, and the GUI jumps to the chown failure instead of the actual failure Follow-up for 35bf1c826454bfcaa3c93cae950d36fa216ac3ce --- .github/workflows/coverage.yml | 2 +- .github/workflows/mkosi.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5143ea73e16c5..f9195e1811f20 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -131,7 +131,7 @@ jobs: - name: Fix journal ownership if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') - run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs || true - name: Archive failed test journals uses: actions/upload-artifact@v7 diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index de6673c9575c3..2895b42772861 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -320,7 +320,7 @@ jobs: - name: Fix journal ownership if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') - run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs || true - name: Archive failed test journals uses: actions/upload-artifact@v7 From 2b8ba0e05aa02a72c3c34a4e0f682a512c76b6ec Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 21:13:10 +0100 Subject: [PATCH 2137/2155] Update NEWS --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 5297923421cfa..f5b2fe1a388e6 100644 --- a/NEWS +++ b/NEWS @@ -638,6 +638,12 @@ CHANGES WITH 261 in spe: basic.target). Note that D-Bus only becomes available later, and it can hence only be contacted via Varlink that early. + * systemd-hostnamed and /etc/machine-info now support a new Tags= key, + which can be used to tag a machine with an arbitrary set of strings. + Units can match on these tags via the new ConditionMachineTag= setting, + and systemd-firstboot can set the tags via command line parameters or + credentials. + * JSON user database records may now optionally carry a birth date field to close the gap with LDAP/OpenID/FreeIPA/etc. homectl gained a new switch --birth-date= to set it. From c3d8c2d25cdcdf4c11b711761ed8423921e37d4f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 17:26:20 +0200 Subject: [PATCH 2138/2155] docs: document the new smbios measurements --- docs/BOOT_LOADER_INTERFACE.md | 13 ++++++++++ docs/TPM2_PCR_MEASUREMENTS.md | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/docs/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md index 36380f38f3328..efe5c1cce29fa 100644 --- a/docs/BOOT_LOADER_INTERFACE.md +++ b/docs/BOOT_LOADER_INTERFACE.md @@ -145,6 +145,8 @@ Variables will be listed below using the Linux efivarfs naming, * `1 << 19` → The boot loader supports the `LoaderEntryPreferred` variable when set. * `1 << 20` → The boot loader reports the firmware-configured keyboard layout in the EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. + * `1 << 21` → The boot loader measures SMBIOS information into a TPM2 PCR and reports the PCR index in the + EFI variable `LoaderPcrSMBIOS-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * The EFI variable `LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` contains binary random data, @@ -184,6 +186,17 @@ Variables will be listed below using the Linux efivarfs naming, uses this as a lowest-priority fallback keyboard layout when no explicit configuration is provided. +* The EFI variable `LoaderPcrSMBIOS-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` + contains the index of the TPM2 PCR (as a decimal ASCII string formatted as a + NUL-terminated UTF-16 string, e.g. `1`) into which the boot loader measured + select SMBIOS structures: type 1 (system information, with the volatile + "Wake-up Type" field zeroed out), type 2 (baseboard information) and type 11 + (OEM strings). This is a volatile (non-persistent) variable, set only if a + measurement was successfully completed, and remains unset otherwise. Both + `systemd-boot` and `systemd-stub` perform this measurement; whichever runs + first sets the variable, and its presence suppresses a second measurement of + the same data into the same PCR during the same boot. + If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze` will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set, systemd will mount the ESP that was used for the boot to `/boot`, but only if diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index 5d0801a9073ec..8bdcd6979e3c8 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -91,6 +91,45 @@ must be employed when designing a system that uses this feature. ## PCR Measurements Made by `systemd-boot` (UEFI) +### PCR 1, `EV_EVENT_TAG`, SMBIOS information + +Select SMBIOS structures provided by the firmware are measured into PCR 1 (the +TCG-defined register for platform configuration data), one tagged event per +structure: + +* SMBIOS type 1 (system information). The volatile "Wake-up Type" field is + zeroed before measuring, since it varies depending on how the machine was + powered on (cold boot, resume from sleep, AC restore, …) and would otherwise + make the measurement non-reproducible. +* SMBIOS type 2 (baseboard information). +* SMBIOS type 11 (OEM strings). There may be more than one such structure; all + are measured. + +Note that these measurements are – strictly speaking – redundant, since +firmwares are supposed to measure SMBIOS data anyway on their own. However, it +has been found this is not the case on many real-life implementations. Since in +particular SMBIOS type 11 may carry highly relevant input for the OS +(e.g. system credentials), an explicit measurement is made here to ensure all +parameters for the OS are comprehensively measured even on flaky firmwares. + +→ **Event Tag** `0xd5cb7cbc` for type 1, `0xe0d47bc8` for type 2, `0xc0b3bd23` +for type 11. + +→ **Description** in the event log record is `smbios:type1`, `smbios:type2` or +`smbios:type11` respectively, in UTF-16. + +→ **Measured hash** covers the raw bytes of the SMBIOS structure (formatted area +plus trailing string set), with the type 1 "Wake-up Type" field zeroed out as +described above. + +This measurement is also performed by `systemd-stub` (see below), so that systems +that boot a UKI directly, bypassing `systemd-boot`, still get it. Whichever +component runs first performs the measurement and sets the volatile +`LoaderPcrSMBIOS` EFI variable to the PCR index used; its presence suppresses a +second measurement of the same data into the same PCR during the same boot. Note +that the firmware itself typically also extends PCR 1, so its final value is not +solely determined by this measurement. + ### PCR 5, `EV_EVENT_TAG`, `loader.conf` The content of `systemd-boot`'s configuration file, `loader/loader.conf`, is @@ -120,6 +159,14 @@ trailing NUL bytes). ## PCR Measurements Made by `systemd-stub` (UEFI) +### PCR 1, `EV_EVENT_TAG`, SMBIOS information + +Identical to the SMBIOS measurement described above for `systemd-boot`. When +`systemd-stub` is invoked by `systemd-boot`, the measurement has typically already +been made (tracked via the `LoaderPcrSMBIOS` EFI variable) and is not repeated; +when the UKI is booted directly by the firmware, `systemd-stub` performs it +itself. + ### PCR 11, `EV_IPL`, PE section name A measurement is made for each PE section of the UKI that is defined by the From e4f0935f19a8ffdf9be937a3820f639dd49ad611 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 17:27:13 +0200 Subject: [PATCH 2139/2155] bootctl: show SMBIOS feature flags --- src/bootctl/bootctl-status.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 4aac25a74dc42..f1c75c4a65abc 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -408,6 +408,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, { EFI_LOADER_FEATURE_KEYBOARD_LAYOUT, "Loader reports firmware keyboard layout" }, + { EFI_LOADER_FEATURE_SMBIOS_MEASURED, "Loader measures SMBIOS information" }, }; static const struct { uint64_t flag; @@ -425,6 +426,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, { EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" }, + { EFI_STUB_FEATURE_SMBIOS_MEASURED, "Stub measures SMBIOS information" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, *current_entry = NULL, *oneshot_entry = NULL, *preferred_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, From 8cc5dbe9cd2150cd2ead4374628893801a3cd7f2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 18:16:45 +0200 Subject: [PATCH 2140/2155] pcrlock: decode new smbios events --- src/pcrlock/pcrlock.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index fa2ee54c5756d..cfdf0f59612d8 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -744,6 +744,23 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { break; } + /* SMBIOS structures measured by sd-boot/sd-stub. The tagged event payload is just a + * constant identifying string ("smbios:typeN"), hence don't show it. */ + case SMBIOS_TYPE1_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS system information (type 1)")) + return log_oom(); + break; + + case SMBIOS_TYPE2_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS baseboard information (type 2)")) + return log_oom(); + break; + + case SMBIOS_TYPE11_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS OEM strings (type 11)")) + return log_oom(); + break; + default: { _cleanup_free_ char *s = NULL; From 14a05bd2080f6f8b68d455caa462cbf20139cf26 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 18:16:32 +0200 Subject: [PATCH 2141/2155] pcrlock: port --help to help-util.[ch] apis --- src/pcrlock/pcrlock.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index cfdf0f59612d8..a0d415209ce97 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -8,6 +8,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "bitfield.h" #include "blockdev-util.h" @@ -29,7 +30,9 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" #include "gpt.h" +#include "help-util.h" #include "hexdecoct.h" #include "initrd-util.h" #include "json-util.h" @@ -45,7 +48,6 @@ #include "pcrextend-util.h" #include "pcrlock-firmware.h" #include "pe-binary.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "recovery-key.h" #include "sort-util.h" @@ -5149,14 +5151,9 @@ 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(); - r = verbs_get_help_table(&commands); if (r < 0) return r; @@ -5171,28 +5168,25 @@ static int help(void) { (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, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Manage a TPM2 PCR lock."); - printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); + help_section("Commands"); r = table_print_or_warn(commands); if (r < 0) return r; - printf("\n%sProtections:%s\n", ansi_underline(), ansi_normal()); + help_section("Protections"); r = table_print_or_warn(protections); if (r < 0) return r; - printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + 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-pcrlock", "8"); return 0; } From 0761c9f4930b9cdf13e49a5030bb281799fdd951 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 May 2026 17:13:40 +0200 Subject: [PATCH 2142/2155] update TODO --- TODO.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/TODO.md b/TODO.md index 5c8f92ac42172..8988e9e89d5fd 100644 --- a/TODO.md +++ b/TODO.md @@ -1226,12 +1226,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit request, so that policies can match against command lines. -- 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. - - in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) - in sd-stub: optionally add support for a new PE section .keyring or so that @@ -1759,8 +1753,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - measure all log-in attempts into a new nvpcr -- measure credentials picked up from SMBIOS to some suitable PCR - - measure GPT and LUKS headers somewhere when we use them (i.e. in systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) From 818397761e473d2bfb4d167df329045b8444e64b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 21:49:22 +0100 Subject: [PATCH 2143/2155] Update NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index f5b2fe1a388e6..1c5d8835c3d3a 100644 --- a/NEWS +++ b/NEWS @@ -225,6 +225,10 @@ CHANGES WITH 261 in spe: disk's ESP. This provides at least some protection, and reasonable persistency from initrd on. + * systemd-boot and systemd-stub will now measure SMBIOS Type 1, Type 2 + and Type 11 in PCR 1, since some firmwares do not measure them, even + though they are supposed to. + Changes in systemd-tmpfiles and systemd-sysusers: * A new tmpfiles.d/root.conf has been added that sets permissions on From b256396e2a8679d2feffc012ada595a2251c324d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 21:59:17 +0100 Subject: [PATCH 2144/2155] Update NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 1c5d8835c3d3a..7f37ce45512e4 100644 --- a/NEWS +++ b/NEWS @@ -397,6 +397,10 @@ CHANGES WITH 261 in spe: * bootctl's 'unlink' verb is now also accessible via a Varlink API. + * bootctl now stores the existing systemd-boot binary as a fallback when + installing a new version, and installs a fallback UEFI boot entry, to + allow a system to recover from a non-working version being installed. + Changes in systemd-repart: * A new EncryptKDF= setting controls the KDF used for LUKS2 From 3115496c8c8c1ae37e611eadffc3911baaab3229 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 22 May 2026 07:10:46 +0900 Subject: [PATCH 2145/2155] sd-dhcp-relay: use forward declarations --- src/systemd/sd-dhcp-relay.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systemd/sd-dhcp-relay.h b/src/systemd/sd-dhcp-relay.h index 47c42a21ddb75..ae8c8c8007ddf 100644 --- a/src/systemd/sd-dhcp-relay.h +++ b/src/systemd/sd-dhcp-relay.h @@ -17,13 +17,13 @@ along with systemd; If not, see . ***/ -#include -#include - #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; +struct in_addr; +struct iovec; + typedef struct sd_event sd_event; typedef struct sd_dhcp_relay sd_dhcp_relay; typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; From 05c295d2698392e41adae56170e3687cce9880ae Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 10 May 2026 23:13:07 +0900 Subject: [PATCH 2146/2155] dhcp: move definition of enum DHCPState to dhcp-protocol.[ch] It will be also used in DHCP server, later. --- src/libsystemd-network/dhcp-client-internal.h | 17 +---------------- src/libsystemd-network/dhcp-protocol.c | 14 ++++++++++++++ src/libsystemd-network/dhcp-protocol.h | 16 ++++++++++++++++ src/libsystemd-network/fuzz-dhcp-client.c | 2 +- src/libsystemd-network/sd-dhcp-client.c | 15 --------------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index bd9308e2d1d78..4adaeeec22081 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -4,6 +4,7 @@ #include "sd-dhcp-client.h" #include "dhcp-client-id-internal.h" +#include "dhcp-protocol.h" #include "ether-addr-util.h" #include "iovec-wrapper.h" #include "network-common.h" @@ -11,22 +12,6 @@ #include "socket-util.h" #include "tlv-util.h" -typedef enum DHCPState { - DHCP_STATE_STOPPED, - DHCP_STATE_INIT, - DHCP_STATE_SELECTING, - DHCP_STATE_INIT_REBOOT, - DHCP_STATE_REBOOTING, - DHCP_STATE_REQUESTING, - DHCP_STATE_BOUND, - DHCP_STATE_RENEWING, - DHCP_STATE_REBINDING, - _DHCP_STATE_MAX, - _DHCP_STATE_INVALID = -EINVAL, -} DHCPState; - -DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); - struct sd_dhcp_client { unsigned n_ref; diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c index caf92c494ef34..3a42087766b0d 100644 --- a/src/libsystemd-network/dhcp-protocol.c +++ b/src/libsystemd-network/dhcp-protocol.c @@ -188,3 +188,17 @@ static const char * const dhcp_option_code_table[] = { }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); + +static const char* const dhcp_state_table[_DHCP_STATE_MAX] = { + [DHCP_STATE_STOPPED] = "stopped", + [DHCP_STATE_INIT] = "initialization", + [DHCP_STATE_SELECTING] = "selecting", + [DHCP_STATE_INIT_REBOOT] = "init-reboot", + [DHCP_STATE_REBOOTING] = "rebooting", + [DHCP_STATE_REQUESTING] = "requesting", + [DHCP_STATE_BOUND] = "bound", + [DHCP_STATE_RENEWING] = "renewing", + [DHCP_STATE_REBINDING] = "rebinding", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 2acdb3693cb49..eb4799ba1c28f 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -138,3 +138,19 @@ enum { }; DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); + +typedef enum DHCPState { + DHCP_STATE_STOPPED, + DHCP_STATE_INIT, + DHCP_STATE_SELECTING, + DHCP_STATE_INIT_REBOOT, + DHCP_STATE_REBOOTING, + DHCP_STATE_REQUESTING, + DHCP_STATE_BOUND, + DHCP_STATE_RENEWING, + DHCP_STATE_REBINDING, + _DHCP_STATE_MAX, + _DHCP_STATE_INVALID = -EINVAL, +} DHCPState; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 0737e0c2cd9b8..82c92c914b6e7 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -6,7 +6,7 @@ #include "sd-event.h" #include "sd-json.h" -#include "dhcp-client-internal.h" +#include "dhcp-client-internal.h" /* IWYU pragma: keep */ #include "dhcp-lease-internal.h" #include "dhcp-message.h" #include "fd-util.h" diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index d94e6057d5a43..5675c7d78755b 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -24,7 +24,6 @@ #include "random-util.h" #include "set.h" #include "socket-util.h" -#include "string-table.h" #include "string-util.h" #include "time-util.h" #include "web-util.h" @@ -1443,17 +1442,3 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { return 0; } - -static const char* const dhcp_state_table[_DHCP_STATE_MAX] = { - [DHCP_STATE_STOPPED] = "stopped", - [DHCP_STATE_INIT] = "initialization", - [DHCP_STATE_SELECTING] = "selecting", - [DHCP_STATE_INIT_REBOOT] = "init-reboot", - [DHCP_STATE_REBOOTING] = "rebooting", - [DHCP_STATE_REQUESTING] = "requesting", - [DHCP_STATE_BOUND] = "bound", - [DHCP_STATE_RENEWING] = "renewing", - [DHCP_STATE_REBINDING] = "rebinding", -}; - -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); From 8c09b8f12840173730c1931326843e7d21e8dfdf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 18 May 2026 09:37:27 +0900 Subject: [PATCH 2147/2155] dhcp-server-request: drop invalid short-cut logic Even if no pool is allocated, the server may have a static lease matching with the DHCPDISCOVER message. --- src/libsystemd-network/dhcp-server-request.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 6ce68dab40d30..9aab83d0dd4e5 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -214,10 +214,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); - if (server->pool_size == 0) - /* no pool allocated */ - return 0; - /* for now pick a random free address from the pool */ if (static_lease) { sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); From 5e08ff899e88ea60d75f9548ba1cb9012849fad1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 7 May 2026 13:30:00 +0900 Subject: [PATCH 2148/2155] sd-dhcp-server: use sd_dhcp_message to parse received messages This is mostly refactoring. This does not change basic behavior, but changes/fixes some minor/corner cases, e.g. - extend the minimum default lease time from 1 second to 30 seconds, as 1 second is too short and causes the network unstable (though 30 seconds is stll too short, but hopefully that does not make the network unstable). - error code on broken/malicious message received may be changed. --- src/libsystemd-network/dhcp-server-request.c | 292 ++++++++++-------- src/libsystemd-network/dhcp-server-request.h | 11 +- src/libsystemd-network/dhcp-server-send.c | 49 ++- src/libsystemd-network/sd-dhcp-server-lease.c | 13 +- src/libsystemd-network/test-dhcp-server.c | 9 +- 5 files changed, 200 insertions(+), 174 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 9aab83d0dd4e5..b83cb01d8b8d7 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "alloc-util.h" -#include "dhcp-protocol.h" +#include "dhcp-message.h" #include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-request.h" @@ -12,17 +14,18 @@ #include "fd-util.h" #include "hashmap.h" #include "iovec-util.h" -#include "memory-util.h" +#include "ip-util.h" +#include "set.h" #include "siphash24.h" #include "socket-util.h" #include "string-util.h" -#include "unaligned.h" static DHCPRequest* dhcp_request_free(DHCPRequest *req) { if (!req) return NULL; - free(req->hostname); + sd_dhcp_message_unref(req->message); + set_free(req->parameter_request_list); return mfree(req); } @@ -53,27 +56,22 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_ return 0; } -static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { +static int dhcp_request_set_client_id(DHCPRequest *req) { assert(req); - assert(message); - - req->message = message; + assert(req->message); - if (message->hlen > sizeof(message->chaddr)) - return -EBADMSG; - - req->hw_addr.length = req->message->hlen; - memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen); + /* Genuine client ID from Client Identifier option. The option may not be set. */ + (void) dhcp_message_get_option_client_id(req->message, &req->client_id); /* Fake client ID generated from the DHCP header. * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255. * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */ - if (!IN_SET(req->message->htype, 0, UINT8_MAX)) + if (!IN_SET(req->message->header.htype, 0, UINT8_MAX)) (void) sd_dhcp_client_id_set( &req->client_id_by_header, - req->message->htype, - req->message->chaddr, - req->message->hlen); + req->message->header.htype, + req->message->header.chaddr, + req->message->header.hlen); /* If Client Identifier option is unspecified, use the generated one. */ if (!sd_dhcp_client_id_is_set(&req->client_id)) @@ -83,100 +81,140 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes if (!sd_dhcp_client_id_is_set(&req->client_id)) return -EBADMSG; - if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) - req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + return 0; +} - if (req->lifetime <= 0) - req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time); +static int dhcp_request_set_server_identifier(DHCPRequest *req) { + int r; - if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) - req->lifetime = server->max_lease_time; + assert(req); + assert(req->message); + + bool mandatory = IN_SET(req->type, DHCP_RELEASE, DHCP_DECLINE); + be32_t a; + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a); + if (r < 0) + return mandatory ? r : 0; + + req->server_address = a; return 0; } -static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { - DHCPRequest *req = ASSERT_PTR(userdata); +static int dhcp_request_set_maximum_message_size(DHCPRequest *req) { int r; - switch (code) { - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - if (len == 4) - req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true); - - break; - case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: - if (len == 4) - memcpy(&req->requested_ip, option, sizeof(be32_t)); + assert(req); + assert(req->message); - break; - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - if (len == 4) - memcpy(&req->server_address, option, sizeof(be32_t)); + uint16_t sz; + r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz); + if (r < 0) + return r; - break; - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: - if (client_id_size_is_valid(len)) - (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + if (sz < IPV4_MIN_REASSEMBLY_SIZE) + return -EBADMSG; - break; - case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + req->max_message_size = sz; + return 0; +} - if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) - req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); +static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) { + assert(req); + assert(req->message); + assert(server); - break; - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: - req->agent_info_option = (uint8_t*)option - 2; + (void) dhcp_message_get_option_sec( + req->message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + /* max_as_infinity= */ true, + &req->lifetime); - break; - case SD_DHCP_OPTION_HOST_NAME: { - _cleanup_free_ char *p = NULL; + /* If unset (or zero is specified...), use the default lease time. */ + if (req->lifetime <= 0) + req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time); - r = dhcp_option_parse_hostname(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - else - free_and_replace(req->hostname, p); - break; - } - case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: - req->parameter_request_list = option; - req->parameter_request_list_len = len; - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - req->rapid_commit = true; - break; - } + /* If the requested lifetime is too long, then cap it with the maximum lease time. */ + if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) + req->lifetime = server->max_lease_time; return 0; } -static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, DHCPRequest **ret, char **ret_error_message) { +static int dhcp_server_parse_message(sd_dhcp_server *server, const struct iovec *iov, DHCPRequest **ret) { int r; assert(server); - assert(message); + assert(iov); assert(ret); - assert(ret_error_message); - _cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* A DHCP relay agent is running on the interface with the same address?? + * Should be malicious message. */ + if (message->header.giaddr == server->address) + return -EBADMSG; + + _cleanup_(dhcp_request_freep) DHCPRequest *req = new(DHCPRequest, 1); if (!req) return -ENOMEM; - _cleanup_free_ char *error_message = NULL; - r = dhcp_option_parse(message, length, parse_request, req, &error_message); + *req = (DHCPRequest) { + .message = sd_dhcp_message_ref(message), + + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + .max_message_size = IPV4_MIN_REASSEMBLY_SIZE, + }; + + /* client hardware address + * Note, hlen and chaddr may not be set for non-ethernet interface. + * See RFC2131 section 4.1. */ + r = dhcp_message_get_hw_addr(message, &req->hw_addr); + if (r < 0) + return r; + + /* Message Type: mandatory */ + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &req->type); if (r < 0) return r; - req->type = r; - r = ensure_sane_request(server, req, message); + /* Client Identifier: Mandatory. If not set, fall back to use chaddr. */ + r = dhcp_request_set_client_id(req); if (r < 0) return r; + /* Server Identifier */ + r = dhcp_request_set_server_identifier(req); + if (r < 0) + return r; + + /* Maximum Message Size: optional */ + (void) dhcp_request_set_maximum_message_size(req); + + if (req->max_message_size >= sizeof(DHCPPacket)) + req->max_optlen = req->max_message_size - sizeof(DHCPPacket); + else + req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + + /* Lifetime: optional */ + (void) dhcp_request_set_lifetime(req, server); + + /* Parameter Request List: optional */ + (void) dhcp_message_get_option_parameter_request_list(message, &req->parameter_request_list); + *ret = TAKE_PTR(req); - *ret_error_message = TAKE_PTR(error_message); return 0; } @@ -195,10 +233,9 @@ static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) { if (r < 0) return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); - log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->header.xid)); dhcp_server_on_lease_change(server); - return DHCP_ACK; } @@ -212,7 +249,7 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), *static_lease = dhcp_server_get_static_lease(server, req); - log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->header.xid)); /* for now pick a random free address from the pool */ if (static_lease) { @@ -226,7 +263,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req req->address = static_lease->address; } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address)) - /* If we previously assigned an address to the host, then reuse it. */ req->address = existing_lease->address; @@ -257,7 +293,8 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req /* no free addresses left */ return 0; - if (server->rapid_commit && req->rapid_commit) + if (server->rapid_commit && + dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0) return dhcp_server_ack(server, req); r = server_send_offer_or_ack(server, req, DHCP_OFFER); @@ -265,11 +302,13 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req /* this only fails on critical errors */ return log_dhcp_server_errno(server, r, "Could not send offer: %m"); - log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->header.xid)); return DHCP_OFFER; } static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) { + int r; + assert(server); assert(req); @@ -281,54 +320,57 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) bool init_reboot = false; /* see RFC 2131, section 4.3.2 */ - if (req->server_address != INADDR_ANY) { log_dhcp_server(server, "REQUEST (selecting) (0x%x)", - be32toh(req->message->xid)); + be32toh(req->message->header.xid)); /* SELECTING */ if (req->server_address != server->address) - /* client did not pick us */ - return 0; - - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; + return 0; /* The message is not for us. Let's silently ignore the packet. */ - if (req->requested_ip == 0) - /* this must be filled in with the yiaddr - from the chosen OFFER */ + if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */ return 0; - address = req->requested_ip; - } else if (req->requested_ip != 0) { - log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", - be32toh(req->message->xid)); + /* this must be filled in with the yiaddr from the chosen OFFER */ + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; - /* INIT-REBOOT */ - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; + if (address == INADDR_ANY) + return -EBADMSG; - /* TODO: check more carefully if IP is correct */ - address = req->requested_ip; - init_reboot = true; - } else { + } else if (req->message->header.ciaddr != INADDR_ANY) { log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", - be32toh(req->message->xid)); + be32toh(req->message->header.xid)); /* REBINDING / RENEWING */ - if (req->message->ciaddr == 0) - /* this MUST be filled in with clients IP address */ - return 0; - address = req->message->ciaddr; - } + /* this must NOT be filled */ + if (dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, /* ret= */ NULL) >= 0) + return -EBADMSG; - /* Silently ignore Rapid Commit option in REQUEST message. */ - req->rapid_commit = false; + address = req->message->header.ciaddr; + } else { + log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", + be32toh(req->message->header.xid)); + + /* INIT-REBOOT */ + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; + + if (address == INADDR_ANY) + return -EBADMSG; + + init_reboot = true; + } + + /* Check if the request is consistent with the static lease. */ if (static_lease) { + /* Found a static lease for the client ID. In this case, the server is explicitly configured + * to manage the host. Hence, send NAK when the request is invalid. */ + if (static_lease->address != address) /* The client requested an address which is different from the static lease. Refusing. */ return server_send_nak_or_ignore(server, init_reboot, req); @@ -341,7 +383,6 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) req->static_lease = static_lease; req->address = address; - /* Found a static lease for the client ID. */ return dhcp_server_ack(server, req); } @@ -356,7 +397,7 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) return server_send_nak_or_ignore(server, init_reboot, req); } -static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, const char *error_message) { +static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) { assert(server); assert(req); @@ -365,7 +406,9 @@ static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, /* TODO: make sure we don't offer this address again for a while. */ - log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); + _cleanup_free_ char *e = NULL; + (void) dhcp_message_get_option_string(req->message, SD_DHCP_OPTION_ERROR_MESSAGE, &e); + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->header.xid), strna(e)); return 0; } @@ -380,13 +423,13 @@ static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) if (!existing_lease) return 0; - if (existing_lease->address != req->message->ciaddr) + if (existing_lease->address != req->message->header.ciaddr) return -EBADMSG; sd_dhcp_server_lease_unref(existing_lease); dhcp_server_on_lease_change(server); - log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->header.xid)); return 0; } @@ -396,15 +439,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz assert(server); assert(message); - if (length < sizeof(DHCPMessage)) - return 0; - - if (message->op != BOOTREQUEST) - return 0; - _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; - _cleanup_free_ char *error_message = NULL; - r = dhcp_server_parse_message(server, message, length, &req, &error_message); + r = dhcp_server_parse_message(server, &IOVEC_MAKE(message, length), &req); if (r < 0) return r; @@ -420,7 +456,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz case DHCP_REQUEST: return dhcp_server_process_request(server, req); case DHCP_DECLINE: - return dhcp_server_process_decline(server, req, error_message); + return dhcp_server_process_decline(server, req); case DHCP_RELEASE: return dhcp_server_process_release(server, req); default: diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h index e5738978a2ddc..f37d81bd145a6 100644 --- a/src/libsystemd-network/dhcp-server-request.h +++ b/src/libsystemd-network/dhcp-server-request.h @@ -4,6 +4,7 @@ #include "sd-dhcp-server-lease.h" #include "dhcp-client-id-internal.h" +#include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" #include "sd-forward.h" @@ -12,7 +13,7 @@ typedef struct DHCPRequest { /* received message */ - DHCPMessage *message; + sd_dhcp_message *message; /* sender hardware address, may not be set for non-ethernet interface */ struct hw_addr_data hw_addr; triple_timestamp timestamp; @@ -22,14 +23,10 @@ typedef struct DHCPRequest { sd_dhcp_client_id client_id; sd_dhcp_client_id client_id_by_header; size_t max_optlen; + size_t max_message_size; /* maximum message size, including IP and UDP headers */ be32_t server_address; - be32_t requested_ip; usec_t lifetime; - const uint8_t *agent_info_option; - char *hostname; - const uint8_t *parameter_request_list; - size_t parameter_request_list_len; - bool rapid_commit; + Set *parameter_request_list; /* acquired data */ sd_dhcp_server_lease *static_lease; diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 9f52ff4cc8df9..27bfc7f32aa02 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -14,6 +14,7 @@ #include "in-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" +#include "set.h" #include "socket-util.h" static int server_acquire_raw_socket(sd_dhcp_server *server) { @@ -156,10 +157,10 @@ static int dhcp_server_send_message( /* If the ’giaddr’ field in a DHCP message from a client is non-zero, the server sends any * return messages to the ’DHCP server’ port on the BOOTP relay agent whose address appears * in ’giaddr’. */ - if (req->message->giaddr != INADDR_ANY) + if (req->message->header.giaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->giaddr, + req->message->header.giaddr, DHCP_PORT_SERVER, &packet->dhcp, sizeof(DHCPMessage) + optoffset); @@ -175,10 +176,10 @@ static int dhcp_server_send_message( /* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts * DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */ - if (req->message->ciaddr != INADDR_ANY) + if (req->message->header.ciaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->ciaddr, + req->message->header.ciaddr, DHCP_PORT_CLIENT, &packet->dhcp, sizeof(DHCPMessage) + optoffset); @@ -189,7 +190,7 @@ static int dhcp_server_send_message( * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast * the message if 'yiaddr' is zero.) */ - if (FLAGS_SET(be16toh(req->message->flags), 0x8000) || + if (dhcp_message_has_broadcast_flag(req->message) || hw_addr_is_null(&req->hw_addr) || packet->dhcp.yiaddr == INADDR_ANY) return dhcp_server_send_udp( @@ -226,14 +227,12 @@ static int dhcp_server_send_packet(sd_dhcp_server *server, if (r < 0) return r; - if (req->agent_info_option) { - size_t opt_full_length = *(req->agent_info_option + 1) + 2; - /* there must be space left for SD_DHCP_OPTION_END */ - if (optoffset + opt_full_length < req->max_optlen) { - memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length); - optoffset += opt_full_length; - } - } + _cleanup_(iovec_done) struct iovec iov = {}; + if (dhcp_message_get_option_alloc(req->message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &iov) >= 0 && + iov.iov_len <= UINT8_MAX) + (void) dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + iov.iov_len, iov.iov_base); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); @@ -265,14 +264,14 @@ static int server_message_init( return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->xid), - req->message->htype, req->hw_addr.length, req->hw_addr.bytes, + be32toh(req->message->header.xid), + req->message->header.htype, req->hw_addr.length, req->hw_addr.bytes, type, req->max_optlen, &optoffset); if (r < 0) return r; - packet->dhcp.flags = req->message->flags; - packet->dhcp.giaddr = req->message->giaddr; + packet->dhcp.flags = req->message->header.flags; + packet->dhcp.giaddr = req->message->header.giaddr; *ret_optoffset = optoffset; *ret = TAKE_PTR(packet); @@ -336,15 +335,6 @@ static int dhcp_server_append_static_hostname( buffer); } -static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { - assert(req); - - if (!req->parameter_request_list) - return false; - - return memchr(req->parameter_request_list, option, req->parameter_request_list_len); -} - int server_send_offer_or_ack( sd_dhcp_server *server, DHCPRequest *req, @@ -447,7 +437,7 @@ int server_send_offer_or_ack( /* RFC 8925 section 3.3. DHCPv4 Server Behavior * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if * the option was not present in the Parameter Request List sent by the client. */ - if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && + if (set_contains(req->parameter_request_list, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) && server->ipv6_only_preferred_usec > 0) { be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); @@ -488,7 +478,8 @@ int server_send_offer_or_ack( return r; } - if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) { + if (type == DHCP_ACK && req->type == DHCP_DISCOVER) { + assert(server->rapid_commit); r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, SD_DHCP_OPTION_RAPID_COMMIT, @@ -524,7 +515,7 @@ int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequ if (r < 0) return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); - log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->header.xid)); return DHCP_NAK; } diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 4ab789726407d..3fe40537585bf 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -70,6 +70,7 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { assert(server); assert(req); + assert(req->message); assert(req->address != INADDR_ANY); usec_t expiration; @@ -105,18 +106,16 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { .n_ref = 1, .address = req->address, .client_id = req->client_id, - .htype = req->message->htype, - .gateway = req->message->giaddr, + .htype = req->message->header.htype, + .gateway = req->message->header.giaddr, .expiration = expiration, }; lease->hw_addr = req->hw_addr; - if (req->hostname) { - lease->hostname = strdup(req->hostname); - if (!lease->hostname) - return -ENOMEM; - } + char *hostname; + if (dhcp_message_get_option_hostname(req->message, &hostname) >= 0) + free_and_replace(lease->hostname, hostname); r = dhcp_server_put_lease(server, lease, /* is_static= */ false); if (r < 0) diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index a4f025bd412b1..a112c0632ad07 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -95,6 +95,7 @@ TEST(dhcp_server_handle_message) { .header.hlen = ETHER_ADDR_LEN, .header.xid = htobe32(0x12345678), .header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + .header.magic = htobe32(DHCP_MAGIC_COOKIE), .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, @@ -141,14 +142,14 @@ TEST(dhcp_server_handle_message) { test.option_type.code = 0; test.option_type.length = 0; test.option_type.type = 0; - ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA); test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE; test.option_type.length = 1; test.option_type.type = DHCP_DISCOVER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); test.header.op = 0; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); @@ -163,8 +164,10 @@ TEST(dhcp_server_handle_message) { test.header.hlen = ETHER_ADDR_LEN; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + /* DHCPREQUEST (init-boot) without requested IP */ test.option_type.type = DHCP_REQUEST; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA); + test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS; test.option_requested_ip.length = 4; test.option_requested_ip.address = htobe32(0x12345678); From 15103b697d926ceef02c2735d1bff065c3932ddd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 10 May 2026 22:17:05 +0900 Subject: [PATCH 2149/2155] sd-dhcp-server: use sd_dhcp_message object on sending reply This also makes the conditions in dhcp_server_send_message() uses the message that will be sent, rather than we received. This does not change basic functionality, but changes/fixes several minor behaviors, e.g. - fix when the broadcast flag assignment, - set server identifier in DHCPFORCERENEW. --- src/libsystemd-network/dhcp-server-request.c | 29 +- src/libsystemd-network/dhcp-server-send.c | 536 ++++++++----------- src/libsystemd-network/dhcp-server-send.h | 4 +- 3 files changed, 224 insertions(+), 345 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index b83cb01d8b8d7..a553fc43b626e 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -227,21 +227,17 @@ static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) { r = dhcp_server_set_lease(server, req); if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); + return r; - r = server_send_offer_or_ack(server, req, DHCP_ACK); + r = dhcp_server_send_reply(server, req, DHCP_ACK); if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); - - log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->header.xid)); + return r; dhcp_server_on_lease_change(server); - return DHCP_ACK; + return r; } static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) { - int r; - assert(server); assert(req); @@ -297,13 +293,7 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0) return dhcp_server_ack(server, req); - r = server_send_offer_or_ack(server, req, DHCP_OFFER); - if (r < 0) - /* this only fails on critical errors */ - return log_dhcp_server_errno(server, r, "Could not send offer: %m"); - - log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->header.xid)); - return DHCP_OFFER; + return dhcp_server_send_reply(server, req, DHCP_OFFER); } static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) { @@ -373,12 +363,12 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) if (static_lease->address != address) /* The client requested an address which is different from the static lease. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); + return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); if (l && l != existing_lease) /* The requested address is already assigned to another host. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); + return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; req->static_lease = static_lease; req->address = address; @@ -394,7 +384,10 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) } /* Refuse otherwise. */ - return server_send_nak_or_ignore(server, init_reboot, req); + if (init_reboot) + return dhcp_server_send_reply(server, req, DHCP_NAK); + + return 0; } static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) { diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 27bfc7f32aa02..cc0f9d667b010 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -2,18 +2,13 @@ #include "sd-event.h" -#include "alloc-util.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-send.h" -#include "dns-domain.h" #include "errno-util.h" #include "fd-util.h" -#include "hashmap.h" #include "in-addr-util.h" -#include "iovec-util.h" -#include "iovec-wrapper.h" +#include "random-util.h" #include "set.h" #include "socket-util.h" @@ -46,124 +41,72 @@ static int server_acquire_raw_socket(sd_dhcp_server *server) { static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, const struct hw_addr_data *hw_addr, - DHCPPacket *packet, - size_t len) { - - int r; + sd_dhcp_message *message) { assert(server); assert(server->ifindex > 0); assert(server->address != 0); assert(hw_addr); - assert(packet); - assert(len > sizeof(DHCPPacket)); - - if (len > UINT16_MAX) - return -EOVERFLOW; + assert(message); int fd = server_acquire_raw_socket(server); if (fd < 0) return fd; - r = dhcp_packet_append_ip_headers( - packet, + return dhcp_message_send_raw( + message, + fd, + server->ifindex, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, + hw_addr, + message->header.yiaddr, DHCP_PORT_CLIENT, - len, server->ip_service_type); - if (r < 0) - return r; - - union sockaddr_union sa = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_IP), - .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hw_addr->length, - }; - - memcpy_safe(sa.ll.sll_addr, hw_addr->bytes, hw_addr->length); - - struct msghdr mh = { - .msg_name = &sa.sa, - .msg_namelen = sockaddr_ll_len(&sa.ll), - .msg_iov = &IOVEC_MAKE(packet, len), - .msg_iovlen = 1, - }; - - if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; } -static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, - uint16_t destination_port, - DHCPMessage *message, size_t len) { +static int dhcp_server_send_udp( + sd_dhcp_server *server, + be32_t address, + uint16_t port, + sd_dhcp_message *message) { assert(server); assert(message); - assert(len >= sizeof(DHCPMessage)); int fd = sd_event_source_get_io_fd(server->io_event_source); if (fd < 0) return fd; - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(destination_port), - .in.sin_addr.s_addr = destination, - }; - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa.in), - .msg_iov = &IOVEC_MAKE(message, len), - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - - struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - - struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - - if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; + return dhcp_message_send_udp( + message, + fd, + server->address, + address, + port); } static int dhcp_server_send_message( sd_dhcp_server *server, - DHCPRequest *req, uint8_t type, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message) { + + int r; assert(server); - assert(req); - assert(req->message); - assert(packet); + assert(message); /* RFC 2131 Section 4.1 */ /* If the ’giaddr’ field in a DHCP message from a client is non-zero, the server sends any * return messages to the ’DHCP server’ port on the BOOTP relay agent whose address appears * in ’giaddr’. */ - if (req->message->header.giaddr != INADDR_ANY) + if (message->header.giaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->header.giaddr, + message->header.giaddr, DHCP_PORT_SERVER, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* when ’giaddr’ is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */ if (type == DHCP_NAK) @@ -171,18 +114,16 @@ static int dhcp_server_send_message( server, INADDR_BROADCAST, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts * DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */ - if (req->message->header.ciaddr != INADDR_ANY) + if (message->header.ciaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->header.ciaddr, + message->header.ciaddr, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is set, then the server * broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff. @@ -190,246 +131,174 @@ static int dhcp_server_send_message( * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast * the message if 'yiaddr' is zero.) */ - if (dhcp_message_has_broadcast_flag(req->message) || - hw_addr_is_null(&req->hw_addr) || - packet->dhcp.yiaddr == INADDR_ANY) + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) return dhcp_server_send_udp( server, INADDR_BROADCAST, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* If the broadcast bit is not set and ’giaddr’ is zero and ’ciaddr’ is zero, then the server * unicasts DHCPOFFER and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ return dhcp_server_send_unicast_raw( server, - &req->hw_addr, - packet, - sizeof(DHCPPacket) + optoffset); + &hw_addr, + message); } -static int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset) { - int r; - - assert(server); - assert(req); - assert(req->max_optlen > 0); - assert(req->message); - assert(optoffset <= req->max_optlen); - assert(packet); - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &server->address); - if (r < 0) - return r; - - _cleanup_(iovec_done) struct iovec iov = {}; - if (dhcp_message_get_option_alloc(req->message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &iov) >= 0 && - iov.iov_len <= UINT8_MAX) - (void) dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, - iov.iov_len, iov.iov_base); - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - return dhcp_server_send_message(server, req, type, packet, optoffset); -} - -static int server_message_init( +static int dhcp_server_new_reply( sd_dhcp_server *server, - DHCPPacket **ret, + DHCPRequest *req, uint8_t type, - size_t *ret_optoffset, - DHCPRequest *req) { + sd_dhcp_message **ret) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; int r; assert(server); - assert(ret); - assert(ret_optoffset); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + assert(ret); - packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->header.xid), - req->message->header.htype, req->hw_addr.length, req->hw_addr.bytes, - type, req->max_optlen, &optoffset); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); if (r < 0) return r; - packet->dhcp.flags = req->message->header.flags; - packet->dhcp.giaddr = req->message->header.giaddr; - - *ret_optoffset = optoffset; - *ret = TAKE_PTR(packet); - - return 0; -} - -static int dhcp_server_append_static_hostname( - sd_dhcp_server *server, - DHCPPacket *packet, - size_t *offset, - DHCPRequest *req) { - - int r; - - assert(server); - assert(packet); - assert(offset); - assert(req); - - if (!req->static_lease || !req->static_lease->hostname) - return 0; - - if (dns_name_is_single_label(req->static_lease->hostname)) - /* Option 12 */ - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(req->static_lease->hostname), - req->static_lease->hostname); - - - /* Option 81 */ - uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3]; - - /* Flags: S=0 (will not update RR), O=1 (are overriding client), - * E=1 (using DNS wire format), N=1 (will not update DNS) */ - buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N; - - /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on - * receipt. */ - buffer[1] = 255; - buffer[2] = 255; - - r = dns_name_to_wire_format(req->static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); + r = dhcp_message_init_header( + message, + BOOTREPLY, + be32toh(req->message->header.xid), + req->message->header.htype, + &req->hw_addr); if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); - if (r > DHCP_MAX_FQDN_LENGTH) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long"); - - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_FQDN, - 3 + r, - buffer); -} - -int server_send_offer_or_ack( - sd_dhcp_server *server, - DHCPRequest *req, - uint8_t type) { - - static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { - [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, - [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, - [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, - [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, - [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, - [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, - }; + return r; - _cleanup_free_ DHCPPacket *packet = NULL; - be32_t lease_time; - size_t offset; - int r; + message->header.giaddr = req->message->header.giaddr; - assert(server); - assert(req); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); + /* RFC 2131 Section 4.3.2 + * + * If ’giaddr’ is set in the DHCPREQUEST message, the client is on a different subnet. The server + * MUST set the broadcast bit in the DHCPNAK, so that the relay agent will broadcast the DHCPNAK to + * the client, because the client may not have a correct network address or subnet mask, and the + * client may not be answering ARP requests. */ + dhcp_message_set_broadcast_flag( + message, + dhcp_message_has_broadcast_flag(req->message) || + req->message->header.giaddr != INADDR_ANY || + type == DHCP_NAK); + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; - r = server_message_init(server, &packet, type, &offset, req); + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); if (r < 0) return r; - packet->dhcp.yiaddr = req->address; - packet->dhcp.siaddr = server->boot_server_address.s_addr; + if (type == DHCP_NAK) { + *ret = TAKE_PTR(message); + return 0; + } + + assert(req->address != INADDR_ANY); + message->header.yiaddr = req->address; + message->header.siaddr = server->boot_server_address.s_addr; - lease_time = usec_to_be32_sec(req->lifetime); - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, - &lease_time); + if (type == DHCP_ACK) + message->header.ciaddr = req->message->header.ciaddr; + + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + usec_to_be32_sec(req->lifetime)); if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SUBNET_MASK, + server->netmask); if (r < 0) return r; if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, - in4_addr_is_set(&server->router_address) ? - &server->router_address.s_addr : - &server->address); + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_ROUTER, + in4_addr_is_set(&server->router_address) ? + server->router_address.s_addr : + server->address); if (r < 0) return r; } if (server->boot_server_name) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_SERVER_NAME, - strlen(server->boot_server_name), server->boot_server_name); + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_SERVER_NAME, + server->boot_server_name); if (r < 0) return r; } if (server->boot_filename) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_FILENAME, - strlen(server->boot_filename), server->boot_filename); + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_FILENAME, + server->boot_filename); if (r < 0) return r; } + static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { + [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, + [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, + [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, + [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, + [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, + }; + for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { if (server->servers[k].size <= 0) continue; - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_addresses( + message, option_map[k], - sizeof(struct in_addr) * server->servers[k].size, + server->servers[k].size, server->servers[k].addr); if (r < 0) return r; } if (server->timezone) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_string( + message, SD_DHCP_OPTION_TZDB_TIMEZONE, - strlen(server->timezone), server->timezone); + server->timezone); if (r < 0) return r; } if (server->domain_name) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_string( + message, SD_DHCP_OPTION_DOMAIN_NAME, - strlen(server->domain_name), server->domain_name); + server->domain_name); if (r < 0) return r; } @@ -439,115 +308,136 @@ int server_send_offer_or_ack( * the option was not present in the Parameter Request List sent by the client. */ if (set_contains(req->parameter_request_list, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) && server->ipv6_only_preferred_usec > 0) { - be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_be32( + message, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, - sizeof(sec), &sec); + usec_to_be32_sec(server->ipv6_only_preferred_usec)); if (r < 0) return r; } - if (server->extra_options) { - void *key; - struct iovec_wrapper *iovw; - HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) { - uint32_t tag = PTR_TO_UINT32(key); - - FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - tag, iov->iov_len, iov->iov_base); - if (r < 0) - return r; - } - } - } + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + server->vendor_options); + if (r < 0) + return r; - if (!tlv_isempty(server->vendor_options)) { - _cleanup_(iovec_done) struct iovec iov = {}; - r = tlv_build(server->vendor_options, &iov); + if (req->static_lease) { + /* Hostname (12) or FQDN (81) + * Flags: S=0 (will not update RR), O=1 (are overriding client), N=1 (will not update DNS) */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_N, + /* is_client= */ false, + req->static_lease->hostname); if (r < 0) return r; + } - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - iov.iov_len, iov.iov_base); + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + req->message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + log_dhcp_server_errno(server, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + if (agent_info) { + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + agent_info); if (r < 0) return r; } if (type == DHCP_ACK && req->type == DHCP_DISCOVER) { assert(server->rapid_commit); - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, - 0, NULL); + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); if (r < 0) return r; } - r = dhcp_server_append_static_hostname(server, packet, &offset, req); + r = dhcp_message_append_option_tlv(message, server->extra_options); if (r < 0) return r; - return dhcp_server_send_packet(server, req, packet, type, offset); + *ret = TAKE_PTR(message); + return 0; } -int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t offset; - int r; +int dhcp_server_send_reply( + sd_dhcp_server *server, + DHCPRequest *req, + uint8_t type) { - /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the - * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the - * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */ + int r; - if (!init_reboot) - return 0; + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); - r = server_message_init(server, &packet, DHCP_NAK, &offset, req); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_server_new_reply(server, req, type, &message); if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m"); + return r; - r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); + if (dhcp_message_packet_size(message) > req->max_message_size) + return -E2BIG; + + r = dhcp_server_send_message(server, type, message); if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); + return r; - log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->header.xid)); - return DHCP_NAK; + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(type), be32toh(message->header.xid)); + return type; /* Return the sent message type. To make the test easier. */ } static int dhcp_server_send_forcerenew( sd_dhcp_server *server, sd_dhcp_server_lease *lease) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; int r; assert(server); assert(lease); - packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); - if (!packet) - return -ENOMEM; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREPLY, + random_u32(), + lease->htype, + &lease->hw_addr); + if (r < 0) + return r; + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_FORCERENEW); + if (r < 0) + return r; - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - lease->htype, lease->hw_addr.length, lease->hw_addr.bytes, DHCP_FORCERENEW, - DHCP_MIN_OPTIONS_SIZE, &optoffset); + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); + r = dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, message); if (r < 0) return r; - return dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(DHCP_FORCERENEW), be32toh(message->header.xid)); + return 0; } int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { @@ -556,8 +446,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { assert_return(server, -EINVAL); - log_dhcp_server(server, "FORCERENEW"); - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) RET_GATHER(r, dhcp_server_send_forcerenew(server, lease)); return r; diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h index 27801b64ec204..2dde23b2c2c31 100644 --- a/src/libsystemd-network/dhcp-server-send.h +++ b/src/libsystemd-network/dhcp-server-send.h @@ -5,9 +5,7 @@ #include "dhcp-server-request.h" -int server_send_offer_or_ack( +int dhcp_server_send_reply( sd_dhcp_server *server, DHCPRequest *req, uint8_t type); - -int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req); From 30200eee52327759d92261180a7f810bc7d3789b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 22 May 2026 09:43:11 +0900 Subject: [PATCH 2150/2155] sd-dhcp-server: drop unused enum Follow-up for 2e5580a8c12427ddf421b7fee7be3677ff41bd5b. --- src/libsystemd-network/dhcp-server-internal.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 76db80b77d5ae..f4bb61fc19820 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -13,17 +13,6 @@ #include "sparse-endian.h" #include "tlv-util.h" -typedef enum DHCPRawOption { - DHCP_RAW_OPTION_DATA_UINT8, - DHCP_RAW_OPTION_DATA_UINT16, - DHCP_RAW_OPTION_DATA_UINT32, - DHCP_RAW_OPTION_DATA_STRING, - DHCP_RAW_OPTION_DATA_IPV4ADDRESS, - DHCP_RAW_OPTION_DATA_IPV6ADDRESS, - _DHCP_RAW_OPTION_DATA_MAX, - _DHCP_RAW_OPTION_DATA_INVALID, -} DHCPRawOption; - typedef struct sd_dhcp_server { unsigned n_ref; From b45a897edc1a11b8cf2f876f00dbb73f82fe5b6a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 23:24:32 +0100 Subject: [PATCH 2151/2155] hwdb: reject out-of-bounds fnmatch prefixes Ensure that fnmatch traversal doesn't go off the hwdb.bin in case of a corrupted file Originally reported on yeswehack.com as #YWH-PGM9780-70 For the unit test: Co-developed-by: GitHub Copilot --- src/libsystemd/sd-hwdb/sd-hwdb.c | 13 +++++- src/libsystemd/sd-hwdb/test-sd-hwdb.c | 66 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index 1197c4f954c80..996f253fd908a 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -180,9 +180,18 @@ static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t const char *prefix; int err; + assert(hwdb); + assert(node); + + /* Ensure the prefix is within bounds */ + uint64_t file_size = hwdb->st.st_size, prefix_off = le64toh(node->prefix_off); + if (prefix_off >= file_size || p >= file_size - prefix_off) + return -EINVAL; + prefix = trie_string(hwdb, node->prefix_off); - len = strlen(prefix + p); - linebuf_add(buf, prefix + p, len); + len = strnlen(prefix + p, file_size - prefix_off - p); + if (!linebuf_add(buf, prefix + p, len)) + return -EINVAL; for (i = 0; i < node->children_count; i++) { const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i); diff --git a/src/libsystemd/sd-hwdb/test-sd-hwdb.c b/src/libsystemd/sd-hwdb/test-sd-hwdb.c index 0030096a7c4fd..dff29c01e8fe0 100644 --- a/src/libsystemd/sd-hwdb/test-sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/test-sd-hwdb.c @@ -1,11 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include + #include "sd-hwdb.h" #include "errno-util.h" +#include "fd-util.h" #include "hwdb-internal.h" #include "nulstr-util.h" +#include "path-util.h" +#include "rm-rf.h" #include "tests.h" +#include "tmpfile-util.h" TEST(failed_enumerate) { _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; @@ -69,6 +76,65 @@ TEST(sd_hwdb_new_from_path) { assert_se(r >= 0); } +TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + _cleanup_free_ char *path = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_child_entry_f child1; + struct trie_node_f mid_node; + struct trie_child_entry_f child2; + struct trie_node_f crash_node; + char strings[STRLEN("usb:") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb:") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .children_count = 1, + }, + .child1 = { + .c = 'v', + .child_off = htole64(offsetof(struct invalid_hwdb, mid_node)), + }, + .mid_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child2 = { + .c = '*', + .child_off = htole64(offsetof(struct invalid_hwdb, crash_node)), + }, + .crash_node = { + .prefix_off = htole64(0xDEAD0000), + }, + .strings = "usb:", + }; + + ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); + ASSERT_NOT_NULL(path = path_join(tmp, "hwdb.bin")); + + _cleanup_close_ int fd = ASSERT_OK_ERRNO(open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC, 0644)); + assert_se(write(fd, &data, sizeof(data)) == (ssize_t) sizeof(data)); + fd = safe_close(fd); + + ASSERT_OK(sd_hwdb_new_from_path(path, &hwdb)); + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EINVAL); +} + static int intro(void) { _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; int r; From 24319a9c2cd392df38819ecc7cb235d384445fcb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 21 May 2026 22:00:28 +0000 Subject: [PATCH 2152/2155] efi-api: fix unaligned access in efi_guid_to_id128() EFI_GUID requires 4-byte alignment due to its uint32_t Data1 field, but callers may pass pointers at arbitrary offsets into serialized EFI variable buffers (e.g. bootctl walking BootXXXX entries). UBSan flagged the misaligned member access; the old comment claiming the struct was packed was wrong. Copy the bytes into an aligned local first. Co-developed-by: Claude Opus 4.7 --- src/shared/efi-api.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index c205ffb9002b9..56b055a0c8a94 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -645,21 +645,25 @@ bool efi_has_tpm2(void) { } sd_id128_t efi_guid_to_id128(const void *guid) { - const EFI_GUID *uuid = ASSERT_PTR(guid); /* cast is safe, because struct efi_guid is packed */ + EFI_GUID uuid; sd_id128_t id128; - id128.bytes[0] = (uuid->Data1 >> 24) & 0xff; - id128.bytes[1] = (uuid->Data1 >> 16) & 0xff; - id128.bytes[2] = (uuid->Data1 >> 8) & 0xff; - id128.bytes[3] = uuid->Data1 & 0xff; + /* The input pointer is not guaranteed to be aligned (e.g. when pointing into a serialized EFI + * variable buffer), so copy into a properly aligned local first. */ + memcpy(&uuid, ASSERT_PTR(guid), sizeof(uuid)); - id128.bytes[4] = (uuid->Data2 >> 8) & 0xff; - id128.bytes[5] = uuid->Data2 & 0xff; + id128.bytes[0] = (uuid.Data1 >> 24) & 0xff; + id128.bytes[1] = (uuid.Data1 >> 16) & 0xff; + id128.bytes[2] = (uuid.Data1 >> 8) & 0xff; + id128.bytes[3] = uuid.Data1 & 0xff; - id128.bytes[6] = (uuid->Data3 >> 8) & 0xff; - id128.bytes[7] = uuid->Data3 & 0xff; + id128.bytes[4] = (uuid.Data2 >> 8) & 0xff; + id128.bytes[5] = uuid.Data2 & 0xff; - memcpy(&id128.bytes[8], uuid->Data4, sizeof(uuid->Data4)); + id128.bytes[6] = (uuid.Data3 >> 8) & 0xff; + id128.bytes[7] = uuid.Data3 & 0xff; + + memcpy(&id128.bytes[8], uuid.Data4, sizeof(uuid.Data4)); return id128; } From d284e8ab4393400794ea01579e5109d503532f13 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 20 May 2026 17:08:03 +0100 Subject: [PATCH 2153/2155] sysupdate: Add separate polkit actions for cancellation This allows us to have a separate, more permissive, policy for cancelling ongoing sysupdate jobs. The new default policy for cancellation actions is to allow them for the active user, without admin authentication, because typically the user can just pull the plug on the computer to cancel a job anyway. Signed-off-by: Philip Withnall Fixes: https://github.com/systemd/systemd/issues/38568 --- man/org.freedesktop.sysupdate1.xml | 19 +++++--- .../org.freedesktop.sysupdate1.policy | 43 +++++++++++++++++++ src/sysupdate/sysupdated.c | 8 ++-- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/man/org.freedesktop.sysupdate1.xml b/man/org.freedesktop.sysupdate1.xml index 6c0e86df9f29f..fea301167e84a 100644 --- a/man/org.freedesktop.sysupdate1.xml +++ b/man/org.freedesktop.sysupdate1.xml @@ -412,26 +412,35 @@ node /org/freedesktop/sysupdate1/target/host { List(), Describe(), and CheckNew() use the polkit action org.freedesktop.sysupdate1.check. - By default, this action is permitted without administrator authentication. + By default, this action is permitted without administrator authentication. Cancellation of these + methods uses the polkit action org.freedesktop.sysupdate1.cancel-check. + By default, this cancellation action is permitted without administrator authentication. Acquire() and Install() use the polkit action org.freedesktop.sysupdate1.update when no version is specified. By default, this action is permitted without administrator authentication. When a version is specified, org.freedesktop.sysupdate1.update-to-version is - used instead. By default, this alternate action requires administrator authentication. + used instead. By default, this alternate action requires administrator authentication. Cancellation of + these methods uses the polkit actions + org.freedesktop.sysupdate1.cancel-update and + org.freedesktop.sysupdate1.cancel-update-to-version. + By default, these cancellation actions are permitted without administrator authentication. Vacuum() uses the polkit action org.freedesktop.sysupdate1.vacuum. By default, this action requires - administrator authentication. + administrator authentication. Cancellation of this method uses the polkit action + org.freedesktop.sysupdate1.cancel-vacuum. + By default, this cancellation action is permitted without administrator authentication. SetFeatureEnabled() uses the polkit action org.freedesktop.sysupdate1.manage-features. By default, this action - requires administrator authentication. + requires administrator authentication. Cancellation is not supported for this method. GetAppStream(), GetVersion(), ListFeatures(), and DescribeFeature() - are unauthenticated and may be called by anybody. + are unauthenticated and may be called by anybody. Cancellation is not supported for these methods, or + is always allowed without administrator authentication. All methods called on this interface expose additional variables to the polkit rules. class contains the class of the Target being acted upon, and name diff --git a/src/sysupdate/org.freedesktop.sysupdate1.policy b/src/sysupdate/org.freedesktop.sysupdate1.policy index 8b7fa420c27ae..2d3abdc7420d2 100644 --- a/src/sysupdate/org.freedesktop.sysupdate1.policy +++ b/src/sysupdate/org.freedesktop.sysupdate1.policy @@ -29,6 +29,9 @@ a user at the console, and rpm-ostree (generally) needs an "administrative user" at the computer. Without this default, distributions hoping to use sysupdate as an update mechanism will have to set the policy to it anyhow. + + Cancellation actions are always allowed by default, as the user can typically pull the plug on + the computer to prevent an action completing anyway. --> @@ -41,6 +44,16 @@ + + Cancel checking for system updates + Authentication is required to cancel checking for system updates. + + auth_admin + auth_admin + yes + + + Install system updates Authentication is required to install system updates. @@ -51,6 +64,16 @@ + + Cancel installing system updates + Authentication is required to cancel installing system updates. + + auth_admin + auth_admin + yes + + + Install specific system version Authentication is required to update the system to a specific (possibly old) version. @@ -61,6 +84,16 @@ + + Cancel installing specific system version + Authentication is required to cancel updating the system to a specific (possibly old) version. + + auth_admin + auth_admin + yes + + + Cleanup old system updates Authentication is required to cleanup old system updates. @@ -71,6 +104,16 @@ + + Cancel cleaning up old system updates + Authentication is required to cancel cleanup of old system updates. + + auth_admin + auth_admin + yes + + + Manage optional features Authentication is required to manage optional features. diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index 3d696baa75dab..359fb50b91657 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -595,19 +595,19 @@ static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error * case JOB_LIST: case JOB_DESCRIBE: case JOB_CHECK_NEW: - action = "org.freedesktop.sysupdate1.check"; + action = "org.freedesktop.sysupdate1.cancel-check"; break; case JOB_ACQUIRE: case JOB_INSTALL: if (j->version) - action = "org.freedesktop.sysupdate1.update-to-version"; + action = "org.freedesktop.sysupdate1.cancel-update-to-version"; else - action = "org.freedesktop.sysupdate1.update"; + action = "org.freedesktop.sysupdate1.cancel-update"; break; case JOB_VACUUM: - action = "org.freedesktop.sysupdate1.vacuum"; + action = "org.freedesktop.sysupdate1.cancel-vacuum"; break; case JOB_DESCRIBE_FEATURE: From af4e2a9d53961a50f54ad5b4fe9a621ebe38ba94 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 20 May 2026 17:10:06 +0100 Subject: [PATCH 2154/2155] sysupdate: Fix a minor typo in the polkit policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘cleanup’ is a noun; ‘clean up’ is a verb. Signed-off-by: Philip Withnall --- src/sysupdate/org.freedesktop.sysupdate1.policy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sysupdate/org.freedesktop.sysupdate1.policy b/src/sysupdate/org.freedesktop.sysupdate1.policy index 2d3abdc7420d2..d74cf2066c8a4 100644 --- a/src/sysupdate/org.freedesktop.sysupdate1.policy +++ b/src/sysupdate/org.freedesktop.sysupdate1.policy @@ -95,8 +95,8 @@ - Cleanup old system updates - Authentication is required to cleanup old system updates. + Clean up old system updates + Authentication is required to clean up old system updates. auth_admin auth_admin From 0ded5c09a562b32b162a06926cc3d2b412e0c278 Mon Sep 17 00:00:00 2001 From: Simran Singh Date: Wed, 10 Dec 2025 06:14:53 +0530 Subject: [PATCH 2155/2155] 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-92-CLONESETUP/meson.build | 8 + test/integration-tests/meson.build | 1 + test/units/TEST-92-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-92-CLONESETUP/meson.build create mode 100755 test/units/TEST-92-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 e0a99b1a21c9e..d1973b0567a2b 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -18,6 +18,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', @@ -1018,6 +1019,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 7294b9b70c399..3dfd4ecb225b3 100644 --- a/meson.build +++ b/meson.build @@ -292,6 +292,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) @@ -1981,6 +1982,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..b413cd89a4639 --- /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" +#include "strv.h" /* strv_skip */ + +/* 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; +} + +VERB(verb_add, "add", "NAME SOURCE DEST METADATA [OPTIONS]", 5, 6, 0, "Create a dm-clone device"); + +/* 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; +} + +VERB(verb_remove, "remove", "NAME", 2, 2, 0, "Remove a dm-clone device"); + +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; + + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ 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-92-CLONESETUP/meson.build b/test/integration-tests/TEST-92-CLONESETUP/meson.build new file mode 100644 index 0000000000000..77370ce4588c4 --- /dev/null +++ b/test/integration-tests/TEST-92-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 867fdecda2d37..4837a40fe2cae 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -104,6 +104,7 @@ foreach dirname : [ 'TEST-89-RESOLVED-MDNS', 'TEST-90-RESTRICT-FSACCESS', 'TEST-91-LIVEUPDATE', + 'TEST-92-CLONESETUP', ] subdir(dirname) endforeach diff --git a/test/units/TEST-92-CLONESETUP.sh b/test/units/TEST-92-CLONESETUP.sh new file mode 100755 index 0000000000000..757916c4c2dc4 --- /dev/null +++ b/test/units/TEST-92-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 <